Compare commits

...

85 Commits
v0.4 ... master

Author SHA1 Message Date
Marc André Tanner 8c32909a15 Tweak README 2020-04-30 15:28:49 +02:00
Marc André Tanner 936602106a Remove Travis CI integration 2020-04-20 15:51:13 +02:00
Marc André Tanner bf6587209f Add Github Action for macOS 2020-04-20 15:49:19 +02:00
Marc André Tanner a75f3e7261 Add Github Action to produce HTML manual page 2020-04-20 12:51:11 +02:00
Marc André Tanner 2fc2671808 Add Github Action to periodically run Coverity Scan 2020-04-20 12:49:52 +02:00
Marc André Tanner c31eb7fad9 Add fall through comment to silence compiler warning 2020-04-20 12:47:51 +02:00
Marc André Tanner 6ff68d3d5a Explicitly wait for intermediate process 2020-04-20 11:51:56 +02:00
Marc André Tanner 62b6fce308 Tweak README 2020-04-19 13:08:07 +02:00
Marc André Tanner 042fa2255b Add Sourcehub CI integration 2020-04-19 13:08:07 +02:00
Marc André Tanner 4a2c9ded43 Cleanup gitignore 2020-04-19 13:08:07 +02:00
Marc André Tanner b47b66188f Use awk instead of seq in test suite 2020-04-19 13:08:07 +02:00
Marc André Tanner 4742cdcfca Set test suite exit status depending on test results 2020-04-19 13:08:07 +02:00
Marc André Tanner 58fee22707 Make test suite a bit more robust 2020-04-19 13:08:07 +02:00
Marc André Tanner b2ada74d43 Manual page style fixes 2020-04-19 13:08:07 +02:00
Marc André Tanner 5bf6b3178b Exit if changing to root directory fails 2020-04-19 13:00:33 +02:00
Marc André Tanner 32a839d76a Do not install zsh completion by default
Provide dedicated install-completion Makefile target instead.
2020-04-19 12:54:53 +02:00
Marc André Tanner cef844ec9d Move zsh completion to contrib folder 2020-04-19 12:54:21 +02:00
Marc André Tanner 7d0e9d91b6 Create leading directories when installing
Note: install(1) is not really specified and the -D option does not
seem to be portable. For example the FreeBSD manual page documents a
different behavior.

We therefore use a somewhat ugly two step process with an installdirs
target.
2020-04-19 12:43:59 +02:00
Marc André Tanner 070e34b502 Use strip explicitly instead of install -s
Some install(1) implementations don't support -s or treat it as a NOP.
2020-04-19 12:43:59 +02:00
zsugabubus fcac3b17ed
Add Zsh completion 2020-02-05 10:39:03 +01:00
zsugabubus c3ea8f5bde
Makefile: use `install` instead of `cp` 2020-02-05 10:00:27 +01:00
Marc André Tanner 8f80aa8044 Do not implicilty enable passthrough mode if no session name is given
Even when no session name was provided the passthrough flag was still
implicitly enabled in case stdin was not a terminal. This behavior
prevented the session list from being printed i.e.

  abduco < /dev/null

would not work as expected. Only enable implicit passthrough if stdin
is not a terminal *and* a session name is given.

Fix #34
2018-05-16 10:57:39 +02:00
Marc André Tanner c33ee37b43 Expose current session socket as $ABDUCO_SOCKET
This is an absolute path to the Unix domain socket representing the
current session.
2018-05-15 17:10:18 +02:00
Marc André Tanner 4c3e2c8c70 Expose current session name as $ABDUCO_SESSION
The supervised command can use it to determine in which session it
is running. It is not necessarily identical to the file name of the
underlying socket as stored in the file system.
2018-05-15 17:08:44 +02:00
Marc André Tanner 0681dc8daf Constify maximum socket name length 2018-05-15 13:57:27 +02:00
Marc André Tanner 35b3f61015 Expand build matrix on Travis CI 2018-03-19 19:03:50 +01:00
Marc André Tanner 5eb7317971 Convert manual page to mdoc(7) format, restructure content 2018-03-18 15:06:37 +01:00
Marc André Tanner 884e3bb2ca Print server process ID in session list 2018-03-18 15:06:37 +01:00
Marc André Tanner e48ea73ce0 Move server_accept_client function around 2018-03-18 15:06:37 +01:00
Marc André Tanner bb7e4e77f4 Remove MSG_REDRAW it was actually never sent by the client 2018-03-18 15:06:31 +01:00
Marc André Tanner 17b386e93f Print MSG_EXIT packet exit code in debug output 2018-03-18 15:05:27 +01:00
Marc André Tanner 52d5c7d098 Update test suite to account for ignored output
If stdin is not a terminal no command output will be received.
2018-03-17 13:30:52 +01:00
Marc André Tanner fd34b98db7 Add explicit option for command pass through
This allows commands to be send interactively (i.e. when stdin refers
to a terminal). Also imply attach action if none is given, hence:

    $ abduco -p session
    cowsay hi
    ^D

can also be used to send input to a session.
2018-03-17 13:30:52 +01:00
Marc André Tanner c901f2bbb8 If stdin is not a terminal pass-through its data to the session
This allows to send commands to a running session:

    echo cowsay hi | abduco -a session

See #8 for initial discussion of the feature.
2018-03-17 13:30:52 +01:00
Marc André Tanner 5a46912d62 Add -q (quiet) option to disable unnecessary output 2018-03-17 13:30:52 +01:00
Marc André Tanner 3e3308d4e3 Only manipulate output terminal state if it is a terminal 2018-03-17 13:30:51 +01:00
Marc André Tanner fdbda93131 Correct EOF handling on client stdin
Previously we would end in an infinite loop which might be the cause
of #15.
2018-03-17 13:30:51 +01:00
Marc André Tanner 315338a572 Update year numbers 2018-03-17 13:30:25 +01:00
Marc André Tanner 650f2ec348 Merge branch 'patch-1' of https://github.com/waldyrious/abduco
Conflicts:
	LICENSE
2018-03-17 13:28:28 +01:00
Marc André Tanner a1db9e2dae Check return value of chdir(2) where it makes sense 2017-06-27 10:09:32 +02:00
Marc André Tanner 66a17ed03d Add configure script to detect compiler flags and feature test macros
This is the same musl originated configure script as vis uses.
2017-06-27 10:09:32 +02:00
Marc André Tanner 1f1c02a3b0 Avoid use of BUFSIZ to guarantee system independent package size 2017-06-27 08:50:10 +02:00
Marc André Tanner 482237919b Fix length of MSG_RESIZE packet
We no longer send the complete struct winsize.
2017-06-27 08:49:22 +02:00
Marc André Tanner 7ee5e79cf5 Update year numbers 2017-06-21 23:12:55 +02:00
Marc André Tanner 8964601fe1 Use fixed size integer types in protocol messages
This should make it possible to connect with a 32bit client to a 64bit
server. This might also make it possible to forward the abduco socket
over SSH as described in #25. Different endianness are not supported at
this time.

This is a breaking protocol change. Make sure to use the same version
as client and server (anything else is unsupported anyway!).
2017-06-21 22:52:14 +02:00
Marc André Tanner 6375556846 Use defines instead of numeric values for standard file descriptors 2017-06-21 22:51:54 +02:00
Waldir Pimenta 228b609361 add license title
It's not strictly required, but it's useful metadata, and part of the recommended license template text:
- http://choosealicense.com/licenses/isc/
- https://opensource.org/licenses/isc-license
- http://spdx.org/licenses/ISC.html#licenseText
2016-07-29 09:43:02 +01:00
Marc André Tanner df780cf6cc Mention release in README 2016-03-24 15:50:08 +01:00
Marc André Tanner e76729a2df Set version to 0.6 2016-03-24 14:48:23 +01:00
Marc André Tanner a0d8e45cfe Update year to 2016 2016-03-24 14:48:23 +01:00
Stefano Frabetti c6b2e7da31 Clarify KEY_REDRAW default value (i.e. disabled) 2016-01-31 19:08:52 +01:00
Marc André Tanner e671c024c9 Only test for redraw key if it is actually configured
This should make CTRL-Space work by default.
2016-01-31 11:53:43 +01:00
Marc André Tanner 5b9fcf5e52 Revert "Set sticky bit on socket"
This reverts commit 15ae398ea5.

Setting the sticky bit on a file is not really portable and
causes more problems than it solves.

The reason why this was done in the first place, is that the
XDG Base Directory Specification states that files with the
sticky bit set should not be touched by periodic cleanup
procedures.

Close #13
2016-01-25 08:17:57 +01:00
Marc André Tanner f71f02e6d6 Merge branch 'env' of https://github.com/Pyrohh/abduco 2016-01-25 08:17:20 +01:00
Michael Reed 463f7ee3f5 testsuite: Remove bashisms and switch to sh(1)
The BSDs don't have bash installed by default, but just about every
POSIX system has a /bin/sh.
2016-01-25 01:46:27 -05:00
Michael Reed 25d64fa93c Fix manual page markup
The space between `-e detachkey` was being underlined, when only
`detachkey` should be.

Tested with mandoc from OpenBSD -current and groff 1.22.3, the latter of
which is used by the vast majority of Linux distributions for formatting
manual pages.
2016-01-25 01:26:10 -05:00
Marc André Tanner a6418af4dd Properly handle EOF when reading from underlying application 2016-01-20 12:50:22 +01:00
Marc André Tanner 586d751d7e Mention release in README 2016-01-15 12:51:28 +01:00
Marc André Tanner 250288cff2 Set version to 0.5 2016-01-09 12:04:33 +01:00
Marc André Tanner 25d9df6fea Make default command configurable via config.def.h 2016-01-09 11:55:55 +01:00
Marc André Tanner 42bb83bbb6 Change default command handling to not use out of scope variable
Should fix CID 121406.
2016-01-09 11:55:52 +01:00
Marc André Tanner 62d31e784c Add recommendation to run applications within dvtm under abduco to README 2016-01-09 09:57:13 +01:00
Marc André Tanner fdd0c4163f Use the shell to execute default command given by $ABDUCO_CMD
This allows to specify a default command with arguments as in:

 $ ABDUCO_CMD="dvtm -m ^b" abduco -c demo
2016-01-07 23:50:47 +01:00
Marc André Tanner 598fdc396f Print session list if neither action nor session name was given
In particular

 $ abduco -e ^q

will now also print the session list which is useful if the
default detach key is changed by means of a shell alias.

Closes #10
2016-01-07 23:50:42 +01:00
Marc André Tanner f7b65db3ca Make cursor visible after client termination
For complex interactive application it is recommended to run them
under dvtm as in:

 $ abduco -c demo dvtm [your-fancy-app]
2016-01-07 13:26:02 +01:00
Marc André Tanner 4d80fccaa4 Remove trailing white spaces - no functional changes 2016-01-06 17:23:41 +01:00
Marc André Tanner a64c6a7dd1 Add some debugging instructions to README 2016-01-06 17:18:45 +01:00
Marc André Tanner 160de0ceb7 Print protocol attach flags in debug output 2016-01-06 17:18:44 +01:00
Marc André Tanner 3e9a3fdf4d Make socket directory location configurable via config.def.h
Introduce $ABDUCO_SOCKET_DIR environment variable to set a
default location.
2016-01-06 17:18:44 +01:00
Marc André Tanner 21dd6ad6b7 Update year in copyright clause 2016-01-03 12:07:57 +01:00
Marc André Tanner 7e4802e6c1 Update test suite to match new dvtm termination key sequence 2016-01-03 12:03:15 +01:00
Marc André Tanner 577ae6ae7a Slightly tweek communication protocol for -l option 2016-01-03 12:03:02 +01:00
Marc André Tanner 75dc9a52fa Update usage message to include -l option 2016-01-03 12:02:39 +01:00
Luke Clifton 1fcd6e4f44 Added -l option to move clients to the bottom of the stack.
This means it wont take control over the window size until
it is the last remaining one, unless another client connects
with the -l option set at a later time.
2016-01-02 20:01:03 +00:00
Marc André Tanner c0cb653e76 Properly indent shell commands in README to make markdown happy 2015-10-15 00:12:59 +02:00
Marc André Tanner a61e88767c Mention socat(1) as a way to provide read only sessions 2015-10-14 23:48:36 +02:00
Marc André Tanner 57e7acba69 Display more meaningful time in session list
Due to the stale session detection logic we will always access
the socket thus essentially printing the current time which
does not seem useful.
2015-08-09 10:36:47 +02:00
David Phillips e08d7acc3a Allow squashing of command line flags together 2015-08-07 17:01:47 +02:00
Marc André Tanner 15ae398ea5 Set sticky bit on socket
According to the XDG Base Directory Specification this should
prevent the file from being deleted by temporary cleanup
procedures.
2015-08-03 12:48:26 +02:00
Marc André Tanner 42962ff827 Cleanup socket directory creation code 2015-08-03 12:28:09 +02:00
Marc André Tanner 4ab9cb7fcc Fix display of status flags in session list 2015-07-30 12:20:41 +02:00
Marc André Tanner 64713a3219 Only delete stale sockets if they are sockets in the first place 2015-07-30 12:19:06 +02:00
Marc André Tanner 277f4d154d Improve detection and handling of stale sessions
With this it should be possible to create sessions for which a
dead left over socket still exists. Also the session list as
printed by abduco without any arguments will delete those stale
sockets.
2015-07-30 11:52:46 +02:00
Ross Mohn 91feaaa9f9 Fix strange behavior with abduco -A
Here are the steps:

1) Run 'abduco -A foo ping suckless.org'

2) Press 'CTRL+\' to detach

3) Run 'abduco -A foo' to reattach

4) Press 'CTRL+\' and it flashes the screen but does not detach

5) Press 'CTRL+\' a second time and it detaches this time

Here's what the output looks like:

    $ abduco -A foo ping suckless.org
    abduco: foo: detached
    $ abduco -A foo
    abduco: foo: detached
    abduco: foo: detached
    $

Also, oddly, I am only seeing this behavior on my Ubuntu Linux host, not
my AIX hosts.
2015-06-23 13:04:16 +02:00
Marc André Tanner 33a637bd2a Mention 0.4 release in README 2015-03-18 20:24:53 +01:00
22 changed files with 1084 additions and 392 deletions

11
.builds/alpine.yml Normal file
View File

@ -0,0 +1,11 @@
image: alpine/edge
sources:
- https://github.com/martanne/abduco
tasks:
- build: |
cd abduco
./configure
make
- test: |
cd abduco
./testsuite.sh

11
.builds/debian.yml Normal file
View File

@ -0,0 +1,11 @@
image: debian/stable
sources:
- https://github.com/martanne/abduco
tasks:
- build: |
cd abduco
./configure
make
- test: |
cd abduco
./testsuite.sh

11
.builds/freebsd.yml Normal file
View File

@ -0,0 +1,11 @@
image: freebsd/latest
sources:
- https://github.com/martanne/abduco
tasks:
- build: |
cd abduco
./configure
make
- test: |
cd abduco
./testsuite.sh

11
.builds/openbsd.yml Normal file
View File

@ -0,0 +1,11 @@
image: openbsd/latest
sources:
- https://github.com/martanne/abduco
tasks:
- build: |
cd abduco
./configure
make
- test: |
cd abduco
./testsuite.sh

46
.github/workflows/coverity-scan.yml vendored Normal file
View File

@ -0,0 +1,46 @@
name: Coverity Scan
env:
PROJECT: abduco
on:
schedule:
- cron: '0 0 * * 0' # once a week
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Download Coverity Build Tool
run: |
wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=$TOKEN&project=martanne/${PROJECT}" -O cov-analysis-linux64.tar.gz
mkdir cov-analysis-linux64
tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64
env:
TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
- name: Configure
run: ./configure
- name: Build with cov-build
run: |
export PATH=$(pwd)/cov-analysis-linux64/bin:$PATH
cov-build --dir cov-int make
- name: Submit the result to Coverity Scan
run: |
tar czvf ${PROJECT}.tgz cov-int
curl \
--form project=martanne/${PROJECT} \
--form token=$TOKEN \
--form email=mat@brain-dump.org \
--form file=@${PROJECT}.tgz \
--form version=trunk \
--form description="`./${PROJECT} -v`" \
https://scan.coverity.com/builds?project=martanne/${PROJECT}
env:
TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}

24
.github/workflows/macos.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: macOS
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
os:
- macos-10.15
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build
run: |
./configure
make
- name: Test
run: |
./testsuite.sh

44
.github/workflows/man.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Manual
env:
PROJECT: abduco
on:
push:
paths:
- '*.1'
jobs:
man:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Dependency
run: sudo apt install mandoc
- name: Manual generation
run: |
mkdir man
sed -e "s/VERSION/$(git describe --always)/" ${PROJECT}.1 | \
mandoc -W warning -T utf8 -T html -O man=%N.%S.html -O style=mandoc.css 1> \
"man/${PROJECT}.1.html" || true
wget 'https://cvsweb.bsd.lv/~checkout~/mandoc/mandoc.css?rev=1.46&content-type=text/plain' -O man/mandoc.css
ln -sf "${PROJECT}.1.html" man/index.html
- name: Upload
env:
DEPLOY_TOKEN: ${{ secrets.GIT_DEPLOY_TOKEN }}
run: |
git clone --depth=1 --single-branch --branch gh-pages "https://x-access-token:${DEPLOY_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" /tmp/gh-pages
git config --global user.name "${GITHUB_ACTOR}"
git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"
mkdir -p /tmp/gh-pages/man
rm -f /tmp/gh-pages/man/*
cp -av man/*.html /tmp/gh-pages/man/
cp -av man/*.css /tmp/gh-pages/man/
cd /tmp/gh-pages
git add -A && git commit --allow-empty -am "Publishing from ${GITHUB_REPOSITORY} ${GITHUB_SHA}"
git push origin gh-pages

16
.gitignore vendored
View File

@ -1,7 +1,9 @@
# normal ignores
.*
*.[ao]
*.lo
*.so
tags
!.gitignore
/config.h
/config.mk
/abduco
*.css
*.gcda
*.gcno
*.gcov
*.html
*.o

View File

@ -1,8 +0,0 @@
language: c
compiler:
- gcc
- clang
env:
- DEBUG=
- DEBUG=debug
script: make $DEBUG

View File

@ -1,4 +1,6 @@
Copyright (c) 2013-2015 Marc André Tanner <mat at brain-dump.org>
ISC License
Copyright (c) 2013-2018 Marc André Tanner <mat at brain-dump.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above

View File

@ -1,62 +1,67 @@
include config.mk
-include config.mk
VERSION = 0.6
CFLAGS_STD ?= -std=c99 -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -DNDEBUG
CFLAGS_STD += -DVERSION=\"${VERSION}\"
LDFLAGS_STD ?= -lc -lutil
STRIP ?= strip
INSTALL ?= install
PREFIX ?= /usr/local
SHAREDIR ?= ${PREFIX}/share
SRC = abduco.c
OBJ = ${SRC:.c=.o}
all: clean options abduco
options:
@echo abduco build options:
@echo "CFLAGS = ${CFLAGS}"
@echo "LDFLAGS = ${LDFLAGS}"
@echo "CC = ${CC}"
all: abduco
config.h:
cp config.def.h config.h
.c.o:
@echo CC $<
@${CC} -c ${CFLAGS} $<
config.mk:
@touch $@
${OBJ}: config.h config.mk
abduco: ${OBJ}
@echo CC -o $@
@${CC} -o $@ ${OBJ} ${LDFLAGS}
abduco: config.h config.mk *.c
${CC} ${CFLAGS} ${CFLAGS_STD} ${CFLAGS_AUTO} ${CFLAGS_EXTRA} ${SRC} ${LDFLAGS} ${LDFLAGS_STD} ${LDFLAGS_AUTO} -o $@
debug: clean
@make CFLAGS='${DEBUG_CFLAGS}'
make CFLAGS_EXTRA='${CFLAGS_DEBUG}'
clean:
@echo cleaning
@rm -f abduco ${OBJ} abduco-${VERSION}.tar.gz
@rm -f abduco abduco-*.tar.gz
dist: clean
@echo creating dist tarball
@mkdir -p abduco-${VERSION}
@cp -R LICENSE Makefile README.md testsuite.sh config.def.h config.mk \
${SRC} debug.c client.c server.c forkpty-aix.c forkpty-sunos.c \
abduco.1 abduco-${VERSION}
@tar -cf abduco-${VERSION}.tar abduco-${VERSION}
@gzip abduco-${VERSION}.tar
@rm -rf abduco-${VERSION}
@git archive --prefix=abduco-${VERSION}/ -o abduco-${VERSION}.tar.gz HEAD
install: abduco
@echo stripping executable
@${STRIP} abduco
installdirs:
@${INSTALL} -d ${DESTDIR}${PREFIX}/bin \
${DESTDIR}${MANPREFIX}/man1
install: abduco installdirs
@echo installing executable file to ${DESTDIR}${PREFIX}/bin
@mkdir -p ${DESTDIR}${PREFIX}/bin
@cp -f abduco ${DESTDIR}${PREFIX}/bin
@chmod 755 ${DESTDIR}${PREFIX}/bin/abduco
@${INSTALL} -m 0755 abduco ${DESTDIR}${PREFIX}/bin
@echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
@mkdir -p ${DESTDIR}${MANPREFIX}/man1
@sed "s/VERSION/${VERSION}/g" < abduco.1 > ${DESTDIR}${MANPREFIX}/man1/abduco.1
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/abduco.1
install-strip: install
${STRIP} ${DESTDIR}${PREFIX}/bin/abduco
install-completion:
@echo installing zsh completion file to ${DESTDIR}${SHAREDIR}/zsh/site-functions
@install -Dm644 contrib/abduco.zsh ${DESTDIR}${SHAREDIR}/zsh/site-functions/_abduco
uninstall:
@echo removing executable file from ${DESTDIR}${PREFIX}/bin
@rm -f ${DESTDIR}${PREFIX}/bin/abduco
@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
@rm -f ${DESTDIR}${MANPREFIX}/man1/abduco.1
@echo removing zsh completion file from ${DESTDIR}${SHAREDIR}/zsh/site-functions
@rm -f ${DESTDIR}${SHAREDIR}/zsh/site-functions/_abduco
.PHONY: all options clean dist install uninstall debug
.PHONY: all clean dist install installdirs install-strip install-completion uninstall debug

120
README.md
View File

@ -1,15 +1,15 @@
# abduco a tool for session {at,de}tach support
[abduco](http://www.brain-dump.org/projects/abduco) provides
[abduco](https://www.brain-dump.org/projects/abduco) provides
session management i.e. it allows programs to be run independently
from their controlling terminal. That is programs can be detached -
run in the background - and then later reattached. Together with
[dvtm](http://www.brain-dump.org/projects/dvtm) it provides a
[dvtm](https://www.brain-dump.org/projects/dvtm) it provides a
simpler and cleaner alternative to tmux or screen.
![abduco+dvtm demo](https://raw.githubusercontent.com/martanne/abduco/gh-pages/screencast.gif)
![abduco+dvtm demo](https://raw.githubusercontent.com/martanne/abduco/gh-pages/screencast.gif#center)
abduco is in many ways very similar to [dtach]("http://dtach.sf.net)
abduco is in many ways very similar to [dtach](http://dtach.sf.net)
but is a completely independent implementation which is actively maintained,
contains no legacy code, provides a few additional features, has a
cleaner, more robust implementation and is distributed under the
@ -17,34 +17,30 @@ cleaner, more robust implementation and is distributed under the
## News
* [abduco-0.3](http://www.brain-dump.org/projects/abduco/abduco-0.3.tar.gz)
[released](http://lists.suckless.org/dev/1502/25557.html) (19.02.2015)
* [abduco-0.2](http://www.brain-dump.org/projects/abduco/abduco-0.2.tar.gz)
[released](http://lists.suckless.org/dev/1411/24447.html) (15.11.2014)
* [abduco-0.1](http://www.brain-dump.org/projects/abduco/abduco-0.1.tar.gz)
[released](http://lists.suckless.org/dev/1407/22703.html) (05.07.2014)
* [Initial announcement](http://lists.suckless.org/dev/1403/20372.html)
* [abduco-0.6](https://www.brain-dump.org/projects/abduco/abduco-0.6.tar.gz)
[released](https://lists.suckless.org/dev/1603/28589.html) (24.03.2016)
* [abduco-0.5](https://www.brain-dump.org/projects/abduco/abduco-0.5.tar.gz)
[released](https://lists.suckless.org/dev/1601/28094.html) (09.01.2016)
* [abduco-0.4](https://www.brain-dump.org/projects/abduco/abduco-0.4.tar.gz)
[released](https://lists.suckless.org/dev/1503/26027.html) (18.03.2015)
* [abduco-0.3](https://www.brain-dump.org/projects/abduco/abduco-0.3.tar.gz)
[released](https://lists.suckless.org/dev/1502/25557.html) (19.02.2015)
* [abduco-0.2](https://www.brain-dump.org/projects/abduco/abduco-0.2.tar.gz)
[released](https://lists.suckless.org/dev/1411/24447.html) (15.11.2014)
* [abduco-0.1](https://www.brain-dump.org/projects/abduco/abduco-0.1.tar.gz)
[released](https://lists.suckless.org/dev/1407/22703.html) (05.07.2014)
* [Initial announcement](https://lists.suckless.org/dev/1403/20372.html)
on the suckless development mailing list (08.03.2014)
## Download
Either download the latest source tarball
[abduco-0.3.tar.gz](http://www.brain-dump.org/projects/abduco/abduco-0.3.tar.gz)
with sha1sum
175b2c0eaf2a8b7fb044f1454d018dac4ec31293 abduco-0.3.tar.gz
Either download the latest [source tarball](https://github.com/martanne/abduco/releases),
compile and install it
$EDITOR config.mk && make && sudo make install
./configure && make && sudo make install
or use one of the distribution provided binary packages:
* [Debian](https://packages.debian.org/search?keywords=abduco)
* [Fedora](https://admin.fedoraproject.org/pkgdb/package/abduco/)
* [Gentoo](http://packages.gentoo.org/package/app-misc/abduco/)
* [Ubuntu](http://packages.ubuntu.com/search?keywords=abduco)
* [Mac OS X](http://www.braumeister.org/formula/abduco) via homebrew
or use one of the distribution provided
[binary packages](https://repology.org/project/abduco/packages).
## Quickstart
@ -52,7 +48,7 @@ In order to create a new session `abduco` requires a session name
as well as an command which will be run. If no command is given
the environment variable `$ABDUCO_CMD` is examined and if not set
`dvtm` is executed. Therefore assuming `dvtm` is located somewhere
in `$PATH` a new session named *demo* is created with:
in `$PATH` a new session named *demo* is created with:
$ abduco -c demo
@ -61,7 +57,7 @@ An arbitrary application can be started as follows:
$ abduco -c session-name your-application
`CTRL-\` detaches from the active session. This detach key can be
changed by means of the `-e` command line option, `-e ^q` would
changed by means of the `-e` command line option, `-e ^q` would
for example set it to `CTRL-q`.
To get an overview of existing session run `abduco` without any
@ -81,7 +77,13 @@ A session can be reattached by using the `-a` command line option
in combination with the session name which was used during session
creation.
abduco -a demo
$ abduco -a demo
If you encounter problems with incomplete redraws or other
incompatibilities it is recommended to run your applications
within [dvtm](https://github.com/martanne/dvtm) under abduco:
$ abduco -c demo dvtm your-application
Check out the manual page for further information and all available
command line options.
@ -103,11 +105,36 @@ command line options.
attaching to a session, then all keyboard input is ignored and the
client is a passive observer only.
Note that this is not a security feature, but only a convenient way to
avoid accidental keyboard input.
If you want to make your abduco session available to another user
in a read only fashion, use [socat](http://www.dest-unreach.org/socat/)
to proxy the abduco socket in a unidirectional (from the abduco server
to the client, but not vice versa) way.
Start your to be shared session, make sure only you have access to
the `private` directory:
$ abduco -c /tmp/abduco/private/session
Then proxy the socket in unidirectional mode `-u` to a directory
where the desired observers have sufficient access rights:
$ socat -u unix-connect:/tmp/abduco/private/session unix-listen:/tmp/abduco/public/read-only &
Now the observers can connect to the read-only side of the socket:
$ abduco -a /tmp/abduco/public/read-only
communication in the other direction will not be possible and keyboard
input will hence be discarded.
* **better resize handling** on shared sessions, resize request are only
processed if they are initiated by the most recently connected, non
processed if they are initiated by the most recently connected, non
read only client.
* **socket recreation** by sending the `SIGUSR1` signal to the server
* **socket recreation** by sending the `SIGUSR1` signal to the server
process. In case the unix domain socket was removed by accident it
can be recreated. The simplest way to find out the server process
id is to look for abduco processes which are reparented to the init
@ -119,7 +146,7 @@ command line options.
$ kill -USR1 $PID
If the abduco binary itself has also been deleted, but a session is
If the abduco binary itself has also been deleted, but a session is
still running, use the following command to bring back the session:
$ /proc/$PID/exe
@ -130,21 +157,28 @@ command line options.
## Development
You can always fetch the current code base from the git repository.
git clone https://github.com/martanne/abduco.git
or
git clone git://repo.or.cz/abduco.git
You can always fetch the current code base from the git repository
located at [Github](https://github.com/martanne/abduco/) or
[Sourcehut](https://git.sr.ht/~martanne/abduco).
If you have comments, suggestions, ideas, a bug report, a patch or something
else related to abduco then write to the
[suckless developer mailing list](http://suckless.org/community)
or contact me directly mat[at]brain-dump.org.
else related to abduco then write to the
[suckless developer mailing list](https://suckless.org/community)
or contact me directly.
[![Build Status](https://travis-ci.org/martanne/abduco.svg?branch=master)](https://travis-ci.org/martanne/abduco)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/4285/badge.svg)](https://scan.coverity.com/projects/4285)
### Debugging
The protocol content exchanged between client and server can be dumped
to temporary files as follows:
$ make debug
$ ./abduco -n debug [command-to-debug] 2> server-log
$ ./abduco -a debug 2> client-log
If you want to run client and server with one command (e.g. using the `-c`
option) then within `gdb` the option `set follow-fork-mode {child,parent}`
might be useful. Similarly to get a syscall trace `strace -o abduco -ff
[abduco-cmd]` proved to be handy.
## License

340
abduco.1
View File

@ -1,128 +1,226 @@
.TH ABDUCO 1 abduco\-VERSION
.nh
.SH NAME
abduco - terminal session manager
.SH SYNOPSIS
.B abduco
.RB [ \-e
.IR detachkey ]
.RB [ \-r ]
.RB [ \-f ]
.RB \-c
.RB name
.RB command
.RI [ args \ ... "" ]
.br
.B abduco
.RB [ \-e
.IR detachkey ]
.RB [ \-r ]
.RB [ \-f ]
.RB \-n
.RB name
.RB command
.RI [ args \ ... "" ]
.br
.B abduco
.RB [ \-e
.IR detachkey ]
.RB [ \-r ]
.RB [ \-f ]
.RB \-A
.RB name
.RB command
.RI [ args \ ... "" ]
.br
.B abduco
.RB [ \-e
.IR detachkey ]
.RB [ \-r ]
.RB \-a
.RB name
.br
.SH DESCRIPTION
.B abduco
provides a way to disconnect a given application from its controlling
terminal, thus it provides roughly the same session attach/detach support as
.BR screen(1) , " tmux(1)" " or" " dtach(1)".
If the
.BR command
to execute is not specified, the environment variable
.BR $ABDUCO_CMD
is examined, if it is not set
.BR dvtm(1)
is executed.
By default all session related information is stored in
.B $HOME/.abduco
with
.BR $TMPDIR/abduco/$USER
as a fallback and
.BR /tmp/abduco/$USER
as a last resort.
However if a given session name represents either a relative or absolute path
it is used unmodified.
If for some reason the
.BR unix(7)
domain socket representing a session is deleted, sending
.BR SIGUSR1
to the server process will recreate it.
.SH OPTIONS
If no command line arguments are given all currently active sessions are
printed sorted by their respective creation date. Lines starting with an
asterik
.BR *
indicate that at least one client is connected.
.Dd March 18, 2018
.Dt ABDUCO 1
.Os abduco VERSION
.
.Sh NAME
.Nm abduco
.Nd terminal session manager
.
.Sh SYNOPSIS
.Nm
.Fl a
.Op options ...
.Cm name
.
.Nm
.Fl A
.Op options ...
.Cm name
.Cm command Op args ...
.
.Nm
.Fl c
.Op options ...
.Cm name
.Cm command Op args ...
.
.Nm
.Fl n
.Op options ...
.Cm name
.Cm command Op args ...
.
.Sh DESCRIPTION
.
.Nm
disassociates a given application from its controlling
terminal, thereby providing roughly the same session attach/detach support as
.Xr screen 1 ,
.Xr tmux 1 ,
or
.Xr dtach 1 .
.Pp
A session comprises of an
.Nm
server process which spawns a user
command in its own pseudo terminal
.Pq see Xr pty 7 .
Each session is given a name represented by a unix domain socket
.Pq see Xr unix 7
stored in the local file system.
.Nm
clients can connect to it and their standard input output streams
are relayed to the command supervised by the server.
.Pp
.Nm
operates on the raw I/O byte stream without interpreting any terminal
escape sequences.
As a consequence the terminal state is not preserved across sessions.
If this functionality is desired, it should be provided by another
utility such as
.Xr dvtm 1 .
.
.Ss ACTIONS
.
If no command line arguments are given, all currently active sessions are
listed sorted by their respective creation date.
Lines starting with an asterisk
.Pq *
indicate that at least one client is currently connected.
A plus sign
.BR +
indicates that the command terminated while no client was connected, attach
to get its exit status.
.TP
.B \-v
Print version information to standard output and exit.
.TP
.B \-r
Readonly session, i.e. user input is ignored.
.TP
.BI \-e \ detachkey
Set the key to detach which by default is set to CTRL+\\ i.e. ^\\ to detachkey.
.TP
.BI \-f
.Pq +
signals that the command terminated while no client was connected.
Attaching to the session will print its exit status.
The next column shows the PID of the server process, followed by the session
.Ic name .
.Pp
.Nm
provides different actions of which one must be provided.
.
.Bl -tag -width indent
.It Fl a
Attach to an existing session.
.It Fl A
Try to connect to an existing session, upon failure create said session and attach immediately to it.
.It Fl c
Create a new session and attach immediately to it.
.It Fl n
Create a new session but do not attach to it.
.El
.
.Ss OPTIONS
.
Additionally the following options can be provided to further tweak
the behavior.
.Bl -tag -width indent
.It Fl e Ar detachkey
Set the key to detach.
Defaults to
.Aq Ctrl+\e
which is specified as ^\\ i.e. Ctrl is represented as a caret
.Pq ^ .
.It Fl f
Force creation of session when there is an already terminated session of the same name,
after showing its exit status.
.TP
.BI \-c
Create a new session and attach immediately to it.
.TP
.BI \-n
Create a new session but do not attach to it.
.TP
.BI \-A
Try to connect to an existing session, upon failure create said session and attach immediately to it.
.TP
.BI \-a
Attach to an existing session.
.SH EXAMPLE
.It Fl l
Attach with the lowest priority, meaning this client will be the last to control the size.
.It Fl p
Pass through content of standard input to the session.
Implies the
.Fl q
and
.Fl l
options.
.It Fl q
Be quiet, do not print informative messages.
.It Fl r
Read-only session, user input is ignored.
.It Fl v
Print version information and exit.
.El
.
.Sh SIGNALS
.
.Bl -tag -width indent
.It Dv SIGWINCH
Whenever the primary client resizes its terminal the server process will deliver a
.Ev SIGWINCH
signal to the supervised process.
.It Dv SIGUSR1
If for some reason the unix domain socket representing a session is deleted, sending
.Ev SIGUSR1
to the server process will recreate it.
.It Dv SIGTERM
Detaches a client.
.El
.
.Sh ENVIRONMENT
.
.Bl -tag -width indent
.It Ev ABDUCO_CMD
If
.Ic command
is not specified, the environment variable
.Ev $ABDUCO_CMD
is examined, if it is not set
.Xr dvtm 1
is executed.
.It Ev ABDUCO_SESSION
The current session name available to the supervised command.
.It Ev ABDUCO_SOCKET
The absolute path of the session socket available to the supervised command.
.El
.Pp
See the
.Sx FILES
section for environment variables used in determining the location
of unix domain sockets representing sessions.
.Sh FILES
.
All session related information is stored in the following directories (first
to succeed is used):
.Bl -bullet
.It
.Ev $ABDUCO_SOCKET_DIR/abduco
.It
.Ev $HOME/.abduco
.It
.Ev $TMPDIR/abduco/$USER
.It
.Ev /tmp/abduco/$USER
.El
.
.Pp
However, if a given session
.Ic name
represents either a relative or absolute path it is used unmodified.
.
.
.Sh EXAMPLES
.
Start a new session (assuming
.BR dvtm(1)
.Xr dvtm 1
is in
.BR $PATH )
.Ev $PATH )
with
.nf
.B abduco -c my-session
.fi
.Pp
.Dl $ abduco -c my-session
.Pp
do some work, then detach by pressing
.B CTRL+\e
and later reattach with
.nf
.B abduco -a my-session
.fi
.SH AUTHOR
abduco is written by Marc André Tanner <mat at brain-dump.org>
.Aq Ctrl+\e ,
list existing session by running
.Nm
without any arguments and later reattach with
.Pp
.Dl $ abduco -a my-session
.Pp
Alternatively, we can also explicitly specify the command to run.
.Pp
.Dl $ abduco -c my-session /bin/sh
.Pp
Attach with a
.Aq Ctrl+z
as detach key.
.Pp
.Dl $ abduco -e ^z -a my-session
.Pp
Send a command to an existing session.
.Pp
.Dl $ echo make | abduco -a my-session
.Pp
Or in a slightly more interactive fashion.
.Pp
.Dl $ abduco -p my-session
.Dl make
.Dl ^D
.
.Sh SEE ALSO
.Xr dvtm 1 ,
.Xr dtach 1 ,
.Xr tmux 1 ,
.Xr screen 1
.
.Sh AUTHORS
.Nm
is written by
.An Marc André Tanner Aq mat at brain-dump.org

255
abduco.c
View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2015 Marc André Tanner <mat at brain-dump.org>
* Copyright (c) 2013-2018 Marc André Tanner <mat at brain-dump.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -15,6 +15,7 @@
*/
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
@ -66,18 +67,21 @@ enum PacketType {
MSG_ATTACH = 1,
MSG_DETACH = 2,
MSG_RESIZE = 3,
MSG_REDRAW = 4,
MSG_EXIT = 5,
MSG_EXIT = 4,
MSG_PID = 5,
};
typedef struct {
unsigned int type;
size_t len;
uint32_t type;
uint32_t len;
union {
char msg[BUFSIZ];
struct winsize ws;
int i;
bool b;
char msg[4096 - 2*sizeof(uint32_t)];
struct {
uint16_t rows;
uint16_t cols;
} ws;
uint32_t i;
uint64_t l;
} u;
} Packet;
@ -91,7 +95,10 @@ struct Client {
STATE_DISCONNECTED,
} state;
bool need_resize;
bool readonly;
enum {
CLIENT_READONLY = 1 << 0,
CLIENT_LOWPRIORITY = 1 << 1,
} flags;
Client *next;
};
@ -114,7 +121,7 @@ typedef struct {
static Server server = { .running = true, .exit_status = -1, .host = "@localhost" };
static Client client;
static struct termios orig_term, cur_term;
static bool has_term, alternate_buffer;
static bool has_term, alternate_buffer, quiet, passthrough;
static struct sockaddr_un sockaddr = {
.sun_family = AF_UNIX,
@ -201,12 +208,12 @@ static bool recv_packet(int socket, Packet *pkt) {
static void info(const char *str, ...) {
va_list ap;
va_start(ap, str);
if (str) {
if (str && !quiet) {
fprintf(stderr, "%s: %s: ", server.name, server.session_name);
vfprintf(stderr, str, ap);
fprintf(stderr, "\r\n");
fflush(stderr);
}
fflush(stderr);
va_end(ap);
}
@ -216,7 +223,7 @@ static void die(const char *s) {
}
static void usage(void) {
fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-r] [-f] [-e detachkey] name command\n");
fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-p] [-r] [-q] [-l] [-f] [-e detachkey] name command\n");
exit(EXIT_FAILURE);
}
@ -236,28 +243,65 @@ static bool xsnprintf(char *buf, size_t size, const char *fmt, ...) {
return true;
}
static int session_connect(const char *name) {
int fd;
struct stat sb;
if (!set_socket_name(&sockaddr, name) || (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
return -1;
socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
if (connect(fd, (struct sockaddr*)&sockaddr, socklen) == -1) {
if (errno == ECONNREFUSED && stat(sockaddr.sun_path, &sb) == 0 && S_ISSOCK(sb.st_mode))
unlink(sockaddr.sun_path);
close(fd);
return -1;
}
return fd;
}
static pid_t session_exists(const char *name) {
Packet pkt;
pid_t pid = 0;
if ((server.socket = session_connect(name)) == -1)
return pid;
if (client_recv_packet(&pkt) && pkt.type == MSG_PID)
pid = pkt.u.l;
close(server.socket);
return pid;
}
static bool session_alive(const char *name) {
struct stat sb;
return session_exists(name) &&
stat(sockaddr.sun_path, &sb) == 0 &&
S_ISSOCK(sb.st_mode) && (sb.st_mode & S_IXGRP) == 0;
}
static bool create_socket_dir(struct sockaddr_un *sockaddr) {
sockaddr->sun_path[0] = '\0';
uid_t uid = getuid();
size_t maxlen = sizeof(sockaddr->sun_path);
char *dirs[] = { getenv("HOME"), getenv("TMPDIR"), "/tmp" };
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketfd == -1)
return false;
struct passwd *pw = getpwuid(uid);
if ((!dirs[0] || !dirs[0][0]) && pw)
dirs[0] = pw->pw_dir;
for (unsigned int i = 0; i < countof(dirs); i++) {
char *dir = dirs[i];
const size_t maxlen = sizeof(sockaddr->sun_path);
uid_t uid = getuid();
struct passwd *pw = getpwuid(uid);
for (unsigned int i = 0; i < countof(socket_dirs); i++) {
struct stat sb;
bool ishome = (i == 0);
if (!dir)
struct Dir *dir = &socket_dirs[i];
bool ishome = false;
if (dir->env) {
dir->path = getenv(dir->env);
ishome = !strcmp(dir->env, "HOME");
if (ishome && (!dir->path || !dir->path[0]) && pw)
dir->path = pw->pw_dir;
}
if (!dir->path || !dir->path[0])
continue;
if (!xsnprintf(sockaddr->sun_path, maxlen, "%s/%s%s/", dir, ishome ? "." : "", server.name))
if (!xsnprintf(sockaddr->sun_path, maxlen, "%s/%s%s/", dir->path, ishome ? "." : "", server.name))
continue;
mode_t mask = umask(0);
int r = mkdir(sockaddr->sun_path, ishome ? S_IRWXU : S_IRWXU|S_IRWXG|S_IRWXO|S_ISVTX);
int r = mkdir(sockaddr->sun_path, dir->personal ? S_IRWXU : S_IRWXU|S_IRWXG|S_IRWXO|S_ISVTX);
umask(mask);
if (r != 0 && errno != EEXIST)
continue;
@ -269,7 +313,8 @@ static bool create_socket_dir(struct sockaddr_un *sockaddr) {
}
size_t dirlen = strlen(sockaddr->sun_path);
if (!ishome) {
if (!dir->personal) {
/* create subdirectory only accessible to user */
if (pw && !xsnprintf(sockaddr->sun_path+dirlen, maxlen-dirlen, "%s/", pw->pw_name))
continue;
if (!pw && !xsnprintf(sockaddr->sun_path+dirlen, maxlen-dirlen, "%d/", uid))
@ -307,7 +352,10 @@ static bool create_socket_dir(struct sockaddr_un *sockaddr) {
}
static bool set_socket_name(struct sockaddr_un *sockaddr, const char *name) {
size_t maxlen = sizeof(sockaddr->sun_path);
const size_t maxlen = sizeof(sockaddr->sun_path);
const char *session_name = NULL;
char buf[maxlen];
if (name[0] == '/') {
if (strlen(name) >= maxlen) {
errno = ENAMETOOLONG;
@ -315,7 +363,7 @@ static bool set_socket_name(struct sockaddr_un *sockaddr, const char *name) {
}
strncpy(sockaddr->sun_path, name, maxlen);
} else if (name[0] == '.' && (name[1] == '.' || name[1] == '/')) {
char buf[maxlen], *cwd = getcwd(buf, sizeof buf);
char *cwd = getcwd(buf, sizeof buf);
if (!cwd)
return false;
if (!xsnprintf(sockaddr->sun_path, maxlen, "%s/%s", cwd, name))
@ -327,9 +375,18 @@ static bool set_socket_name(struct sockaddr_un *sockaddr, const char *name) {
errno = ENAMETOOLONG;
return false;
}
session_name = name;
strncat(sockaddr->sun_path, name, maxlen - strlen(sockaddr->sun_path) - 1);
strncat(sockaddr->sun_path, server.host, maxlen - strlen(sockaddr->sun_path) - 1);
}
if (!session_name) {
strncpy(buf, sockaddr->sun_path, sizeof buf);
session_name = basename(buf);
}
setenv("ABDUCO_SESSION", session_name, 1);
setenv("ABDUCO_SOCKET", sockaddr->sun_path, 1);
return true;
}
@ -340,7 +397,7 @@ static bool create_session(const char *name, char * const argv[]) {
*
* pipes are used for synchronization and error reporting i.e. the child sets
* the close on exec flag before calling execvp(3) the parent blocks on a read(2)
* in case of failure the error message is written to the pipe, success is
* in case of failure the error message is written to the pipe, success is
* indicated by EOF on the pipe.
*/
int client_pipe[2], server_pipe[2];
@ -348,6 +405,11 @@ static bool create_session(const char *name, char * const argv[]) {
char errormsg[255];
struct sigaction sa;
if (session_exists(name)) {
errno = EADDRINUSE;
return false;
}
if (pipe(client_pipe) == -1)
return false;
if ((server.socket = server_create_socket(name)) == -1)
@ -401,13 +463,14 @@ static bool create_session(const char *name, char * const argv[]) {
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
chdir("/");
if (chdir("/") == -1)
_exit(EXIT_FAILURE);
#ifdef NDEBUG
int fd = open("/dev/null", O_RDWR);
if (fd != -1) {
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
}
#endif /* NDEBUG */
@ -438,8 +501,7 @@ static bool create_session(const char *name, char * const argv[]) {
return false;
default: /* parent = client process */
close(client_pipe[1]);
int status;
wait(&status); /* wait for first fork */
while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
ssize_t len = read_all(client_pipe[0], errormsg, sizeof(errormsg));
if (len > 0) {
write_all(STDERR_FILENO, errormsg, len);
@ -454,10 +516,7 @@ static bool create_session(const char *name, char * const argv[]) {
static bool attach_session(const char *name, const bool terminate) {
if (server.socket > 0)
close(server.socket);
if (!set_socket_name(&sockaddr, name) || (server.socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
return false;
socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
if (connect(server.socket, (struct sockaddr*)&sockaddr, socklen) == -1)
if ((server.socket = session_connect(name)) == -1)
return false;
if (server_set_socket_non_blocking(server.socket) == -1)
return false;
@ -486,21 +545,6 @@ static bool attach_session(const char *name, const bool terminate) {
return terminate;
}
static bool session_exists(const char *name) {
struct stat sb;
if (!set_socket_name(&sockaddr, name))
return false;
return stat(sockaddr.sun_path, &sb) == 0 && S_ISSOCK(sb.st_mode);
}
static bool session_alive(const char *name) {
struct stat sb;
if (!set_socket_name(&sockaddr, name))
return false;
return stat(sockaddr.sun_path, &sb) == 0 && S_ISSOCK(sb.st_mode) &&
(sb.st_mode & S_IXGRP) == 0;
}
static int session_filter(const struct dirent *d) {
return strstr(d->d_name, server.host) != NULL;
}
@ -517,7 +561,8 @@ static int session_comparator(const struct dirent **a, const struct dirent **b)
static int list_session(void) {
if (!create_socket_dir(&sockaddr))
return 1;
chdir(sockaddr.sun_path);
if (chdir(sockaddr.sun_path) == -1)
die("list-session");
struct dirent **namelist;
int n = scandir(sockaddr.sun_path, &namelist, session_filter, session_comparator);
if (n < 0)
@ -526,16 +571,20 @@ static int list_session(void) {
while (n--) {
struct stat sb; char buf[255];
if (stat(namelist[n]->d_name, &sb) == 0 && S_ISSOCK(sb.st_mode)) {
strftime(buf, sizeof(buf), "%a%t %F %T", localtime(&sb.st_atime));
pid_t pid = 0;
strftime(buf, sizeof(buf), "%a%t %F %T", localtime(&sb.st_mtime));
char status = ' ';
char *local = strstr(namelist[n]->d_name, server.host);
if (local) {
*local = '\0'; /* truncate hostname if we are local */
if (!(pid = session_exists(namelist[n]->d_name)))
continue;
}
if (sb.st_mode & S_IXUSR)
status = '*';
else if (sb.st_mode & S_IXGRP)
status = '+';
char *name = strstr(namelist[n]->d_name, server.host);
if (name)
*name = '\0';
printf("%c %s\t%s\n", status, buf, namelist[n]->d_name);
printf("%c %s\t%jd\t%s\n", status, buf, (intmax_t)pid, namelist[n]->d_name);
}
free(namelist[n]);
}
@ -544,63 +593,83 @@ static int list_session(void) {
}
int main(int argc, char *argv[]) {
int opt;
bool force = false;
char **cmd = NULL, action = '\0';
char *default_cmd[4] = { "/bin/sh", "-c", getenv("ABDUCO_CMD"), NULL };
if (!default_cmd[2]) {
default_cmd[0] = ABDUCO_CMD;
default_cmd[1] = NULL;
}
server.name = basename(argv[0]);
gethostname(server.host+1, sizeof(server.host) - 1);
if (argc == 1)
exit(list_session());
for (int arg = 1; arg < argc; arg++) {
if (argv[arg][0] != '-') {
if (!server.session_name) {
server.session_name = argv[arg];
continue;
} else if (!cmd) {
cmd = &argv[arg];
break;
}
}
if (server.session_name)
usage();
switch (argv[arg][1]) {
while ((opt = getopt(argc, argv, "aAclne:fpqrv")) != -1) {
switch (opt) {
case 'a':
case 'A':
case 'c':
case 'n':
action = argv[arg][1];
action = opt;
break;
case 'e':
if (arg + 1 >= argc)
if (!optarg)
usage();
char *esc = argv[++arg];
if (esc[0] == '^' && esc[1])
*esc = CTRL(esc[1]);
KEY_DETACH = *esc;
if (optarg[0] == '^' && optarg[1])
optarg[0] = CTRL(optarg[1]);
KEY_DETACH = optarg[0];
break;
case 'f':
force = true;
break;
case 'p':
passthrough = true;
break;
case 'q':
quiet = true;
break;
case 'r':
client.readonly = true;
client.flags |= CLIENT_READONLY;
break;
case 'l':
client.flags |= CLIENT_LOWPRIORITY;
break;
case 'v':
puts("abduco-"VERSION" © 2013-2015 Marc André Tanner");
puts("abduco-"VERSION" © 2013-2018 Marc André Tanner");
exit(EXIT_SUCCESS);
default:
usage();
}
}
if (!cmd) {
cmd = (char*[]){ getenv("ABDUCO_CMD"), NULL };
if (!cmd[0])
cmd[0] = "dvtm";
/* collect the session name if trailing args */
if (optind < argc)
server.session_name = argv[optind];
/* if yet more trailing arguments, they must be the command */
if (optind + 1 < argc)
cmd = &argv[optind + 1];
else
cmd = default_cmd;
if (server.session_name && !isatty(STDIN_FILENO))
passthrough = true;
if (passthrough) {
if (!action)
action = 'a';
quiet = true;
client.flags |= CLIENT_LOWPRIORITY;
}
if (!action && !server.session_name)
exit(list_session());
if (!action || !server.session_name)
usage();
if (tcgetattr(STDIN_FILENO, &orig_term) != -1) {
if (!passthrough && tcgetattr(STDIN_FILENO, &orig_term) != -1) {
server.term = orig_term;
has_term = true;
}
@ -628,14 +697,16 @@ int main(int argc, char *argv[]) {
die("create-session");
if (action == 'n')
break;
/* fall through */
case 'a':
if (!attach_session(server.session_name, true))
die("attach-session");
break;
case 'A':
if (session_alive(server.session_name) && !attach_session(server.session_name, true))
die("attach-session");
if (!attach_session(server.session_name, !force)) {
if (session_alive(server.session_name)) {
if (!attach_session(server.session_name, true))
die("attach-session");
} else if (!attach_session(server.session_name, !force)) {
force = false;
action = 'c';
goto redo;

View File

@ -22,16 +22,19 @@ static bool client_recv_packet(Packet *pkt) {
}
static void client_restore_terminal(void) {
if (has_term)
tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_term);
if (!has_term)
return;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_term);
if (alternate_buffer) {
printf("\033[?1049l");
printf("\033[?25h\033[?1049l");
fflush(stdout);
alternate_buffer = false;
}
}
static void client_setup_terminal(void) {
if (!has_term)
return;
atexit(client_restore_terminal);
cur_term = orig_term;
@ -43,7 +46,7 @@ static void client_setup_terminal(void) {
cur_term.c_cc[VLNEXT] = _POSIX_VDISABLE;
cur_term.c_cc[VMIN] = 1;
cur_term.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSADRAIN, &cur_term);
tcsetattr(STDIN_FILENO, TCSANOW, &cur_term);
if (!alternate_buffer) {
printf("\033[?1049h\033[H");
@ -62,8 +65,8 @@ static int client_mainloop(void) {
client.need_resize = true;
Packet pkt = {
.type = MSG_ATTACH,
.u = { .b = client.readonly },
.len = sizeof(pkt.u.b),
.u.i = client.flags,
.len = sizeof(pkt.u.i),
};
client_send_packet(&pkt);
@ -78,8 +81,8 @@ static int client_mainloop(void) {
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != -1) {
Packet pkt = {
.type = MSG_RESIZE,
.u = { .ws = ws },
.len = sizeof(ws),
.u = { .ws = { .rows = ws.ws_row, .cols = ws.ws_col } },
.len = sizeof(pkt.u.ws),
};
if (client_send_packet(&pkt))
client.need_resize = false;
@ -97,7 +100,8 @@ static int client_mainloop(void) {
if (client_recv_packet(&pkt)) {
switch (pkt.type) {
case MSG_CONTENT:
write_all(STDOUT_FILENO, pkt.u.msg, pkt.len);
if (!passthrough)
write_all(STDOUT_FILENO, pkt.u.msg, pkt.len);
break;
case MSG_RESIZE:
client.need_resize = true;
@ -118,7 +122,7 @@ static int client_mainloop(void) {
if (len > 0) {
debug("client-stdin: %c\n", pkt.u.msg[0]);
pkt.len = len;
if (pkt.u.msg[0] == KEY_REDRAW) {
if (KEY_REDRAW && pkt.u.msg[0] == KEY_REDRAW) {
client.need_resize = true;
} else if (pkt.u.msg[0] == KEY_DETACH) {
pkt.type = MSG_DETACH;
@ -126,9 +130,12 @@ static int client_mainloop(void) {
client_send_packet(&pkt);
close(server.socket);
return -1;
} else if (!client.readonly) {
} else if (!(client.flags & CLIENT_READONLY)) {
client_send_packet(&pkt);
}
} else if (len == 0) {
debug("client-stdin: EOF\n");
return -1;
}
}
}

View File

@ -1,2 +1,19 @@
/* default command to execute if non is given and $ABDUCO_CMD is unset */
#define ABDUCO_CMD "dvtm"
/* default detach key, can be overriden at run time using -e option */
static char KEY_DETACH = CTRL('\\');
/* redraw key to send a SIGWINCH signal to underlying process
* (set to 0 to disable the redraw key) */
static char KEY_REDRAW = 0;
/* Where to place the "abduco" directory storing all session socket files.
* The first directory to succeed is used. */
static struct Dir {
char *path; /* fixed (absolute) path to a directory */
char *env; /* environment variable to use if (set) */
bool personal; /* if false a user owned sub directory will be created */
} socket_dirs[] = {
{ .env = "ABDUCO_SOCKET_DIR", false },
{ .env = "HOME", true },
{ .env = "TMPDIR", false },
{ .path = "/tmp", false },
};

View File

@ -1,19 +0,0 @@
# abduco version
VERSION = 0.4
# Customize below to fit your system
PREFIX ?= /usr/local
MANPREFIX = ${PREFIX}/share/man
INCS = -I.
LIBS = -lc -lutil
CPPFLAGS = -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700
CFLAGS += -std=c99 -pedantic -Wall ${INCS} -DVERSION=\"${VERSION}\" -DNDEBUG ${CPPFLAGS}
LDFLAGS += ${LIBS}
DEBUG_CFLAGS = ${CFLAGS} -UNDEBUG -O0 -g -ggdb
CC ?= cc
STRIP ?= strip

244
configure vendored Executable file
View File

@ -0,0 +1,244 @@
#!/bin/sh
# Based on the configure script from musl libc, MIT licensed
usage () {
cat <<EOF
Usage: $0 [OPTION]... [VAR=VALUE]...
To assign environment variables (e.g., CC, CFLAGS...), specify them as
VAR=VALUE. See below for descriptions of some of the useful variables.
Defaults for the options are specified in brackets.
Configuration:
--srcdir=DIR source directory [detected]
Installation directories:
--prefix=PREFIX main installation prefix [/usr/local]
--exec-prefix=EPREFIX installation prefix for executable files [PREFIX]
Fine tuning of the installation directories:
--bindir=DIR user executables [EPREFIX/bin]
--sharedir=DIR share directories [PREFIX/share]
--docdir=DIR misc. documentation [PREFIX/share/doc]
--mandir=DIR man pages [PREFIX/share/man]
Some influential environment variables:
CC C compiler command [detected]
CFLAGS C compiler flags [-Os -pipe ...]
LDFLAGS Linker flags
Use these variables to override the choices made by configure.
EOF
exit 0
}
# Helper functions
quote () {
tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/dev/null 2>&1 && { echo "$1" ; return 0 ; }
$1
EOF
printf %s\\n "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#"
}
echo () { printf "%s\n" "$*" ; }
fail () { echo "$*" ; exit 1 ; }
fnmatch () { eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac" ; }
cmdexists () { type "$1" >/dev/null 2>&1 ; }
trycc () { test -z "$CC" && cmdexists "$1" && CC=$1 ; }
stripdir () {
while eval "fnmatch '*/' \"\${$1}\"" ; do eval "$1=\${$1%/}" ; done
}
trycppif () {
printf "checking preprocessor condition %s... " "$1"
echo "typedef int x;" > "$tmpc"
echo "#if $1" >> "$tmpc"
echo "#error yes" >> "$tmpc"
echo "#endif" >> "$tmpc"
if $CC $2 -c -o "$tmpo" "$tmpc" >/dev/null 2>&1 ; then
printf "false\n"
return 1
else
printf "true\n"
return 0
fi
}
tryflag () {
printf "checking whether compiler accepts %s... " "$2"
echo "typedef int x;" > "$tmpc"
if $CC $CFLAGS_TRY $2 -c -o "$tmpo" "$tmpc" >/dev/null 2>&1 ; then
printf "yes\n"
eval "$1=\"\${$1} \$2\""
eval "$1=\${$1# }"
return 0
else
printf "no\n"
return 1
fi
}
tryldflag () {
printf "checking whether linker accepts %s... " "$2"
echo "typedef int x;" > "$tmpc"
if $CC $LDFLAGS_TRY -nostdlib -shared "$2" -o "$tmpo" "$tmpc" >/dev/null 2>&1 ; then
printf "yes\n"
eval "$1=\"\${$1} \$2\""
eval "$1=\${$1# }"
return 0
else
printf "no\n"
return 1
fi
}
# Beginning of actual script
CFLAGS_AUTO=
CFLAGS_TRY=
LDFLAGS_AUTO=
LDFLAGS_TRY=
SRCDIR=
PREFIX=/usr/local
EXEC_PREFIX='$(PREFIX)'
BINDIR='$(EXEC_PREFIX)/bin'
MANDIR='$(PREFIX)/share/man'
for arg ; do
case "$arg" in
--help|-h) usage ;;
--srcdir=*) SRCDIR=${arg#*=} ;;
--prefix=*) PREFIX=${arg#*=} ;;
--exec-prefix=*) EXEC_PREFIX=${arg#*=} ;;
--bindir=*) BINDIR=${arg#*=} ;;
--sharedir=*) SHAREDIR=${arg#*=} ;;
--docdir=*) DOCDIR=${arg#*=} ;;
--mandir=*) MANDIR=${arg#*=} ;;
--enable-*|--disable-*|--with-*|--without-*|--*dir=*|--build=*) ;;
-* ) echo "$0: unknown option $arg" ;;
CC=*) CC=${arg#*=} ;;
CFLAGS=*) CFLAGS=${arg#*=} ;;
CPPFLAGS=*) CPPFLAGS=${arg#*=} ;;
LDFLAGS=*) LDFLAGS=${arg#*=} ;;
*=*) ;;
*) ;;
esac
done
for i in SRCDIR PREFIX EXEC_PREFIX BINDIR MANDIR ; do
stripdir $i
done
#
# Get the source dir for out-of-tree builds
#
if test -z "$SRCDIR" ; then
SRCDIR="${0%/configure}"
stripdir SRCDIR
fi
abs_builddir="$(pwd)" || fail "$0: cannot determine working directory"
abs_srcdir="$(cd $SRCDIR && pwd)" || fail "$0: invalid source directory $SRCDIR"
test "$abs_srcdir" = "$abs_builddir" && SRCDIR=.
test "$SRCDIR" != "." -a -f Makefile -a ! -h Makefile && fail "$0: Makefile already exists in the working directory"
#
# Get a temp filename we can use
#
i=0
set -C
while : ; do i=$(($i+1))
tmpc="./conf$$-$PPID-$i.c"
tmpo="./conf$$-$PPID-$i.o"
2>|/dev/null > "$tmpc" && break
test "$i" -gt 50 && fail "$0: cannot create temporary file $tmpc"
done
set +C
trap 'rm -f "$tmpc" "$tmpo"' EXIT INT QUIT TERM HUP
#
# Find a C compiler to use
#
printf "checking for C compiler... "
trycc cc
trycc gcc
trycc clang
printf "%s\n" "$CC"
test -n "$CC" || { echo "$0: cannot find a C compiler" ; exit 1 ; }
printf "checking whether C compiler works... "
echo "typedef int x;" > "$tmpc"
if output=$($CC $CPPFLAGS $CFLAGS -c -o "$tmpo" "$tmpc" 2>&1) ; then
printf "yes\n"
else
printf "no; compiler output follows:\n%s\n" "$output"
exit 1
fi
#
# Figure out options to force errors on unknown flags.
#
tryflag CFLAGS_TRY -Werror=unknown-warning-option
tryflag CFLAGS_TRY -Werror=unused-command-line-argument
tryldflag LDFLAGS_TRY -Werror=unknown-warning-option
tryldflag LDFLAGS_TRY -Werror=unused-command-line-argument
CFLAGS_STD="-std=c99 -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -DNDEBUG -D_FORTIFY_SOURCE=2"
LDFLAGS_STD="-lc -lutil"
OS=$(uname)
case "$OS" in
FreeBSD) CFLAGS_STD="$CFLAGS_STD -D_BSD_SOURCE -D__BSD_VISIBLE=1" ;;
*BSD) CFLAGS_STD="$CFLAGS_STD -D_BSD_SOURCE" ;;
Darwin) CFLAGS_STD="$CFLAGS_STD -D_DARWIN_C_SOURCE" ;;
AIX) CFLAGS_STD="$CFLAGS_STD -D_ALL_SOURCE" ;;
esac
tryflag CFLAGS -pipe
# Try flags to optimize binary size
tryflag CFLAGS -Os
tryflag CFLAGS -ffunction-sections
tryflag CFLAGS -fdata-sections
tryldflag LDFLAGS_AUTO -Wl,--gc-sections
# Try hardening flags
tryflag CFLAGS -fPIE
tryflag CFLAGS_AUTO -fstack-protector-all
tryldflag LDFLAGS -Wl,-z,now
tryldflag LDFLAGS -Wl,-z,relro
tryldflag LDFLAGS_AUTO -pie
printf "creating config.mk... "
cmdline=$(quote "$0")
for i ; do cmdline="$cmdline $(quote "$i")" ; done
exec 3>&1 1>config.mk
cat << EOF
# This version of config.mk was generated by:
# $cmdline
# Any changes made here will be lost if configure is re-run
SRCDIR = $SRCDIR
PREFIX = $PREFIX
EXEC_PREFIX = $EXEC_PREFIX
BINDIR = $BINDIR
MANPREFIX = $MANDIR
CC = $CC
CFLAGS = $CFLAGS
LDFLAGS = $LDFLAGS
CFLAGS_STD = $CFLAGS_STD
LDFLAGS_STD = $LDFLAGS_STD
CFLAGS_AUTO = $CFLAGS_AUTO
LDFLAGS_AUTO = $LDFLAGS_AUTO
CFLAGS_DEBUG = -U_FORTIFY_SOURCE -UNDEBUG -O0 -g -ggdb -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-sign-compare
EOF
exec 1>&3 3>&-
printf "done\n"
test "$SRCDIR" = "." || ln -sf $SRCDIR/Makefile .

35
contrib/abduco.zsh Normal file
View File

@ -0,0 +1,35 @@
#compdef abduco
typeset -A opt_args
_abduco_sessions() {
declare -a sessions
sessions=( $(abduco | sed '1d;s/.*\t[0-9][0-9]*\t//') )
_describe -t session 'session' sessions
}
_abduco_firstarg() {
if (( $+opt_args[-a] || $+opt_args[-A] )); then
_abduco_sessions
elif (( $+opt_args[-c] || $+opt_args[-n] )); then
_guard "^-*" 'session name'
elif [[ -z $words[CURRENT] ]]; then
compadd "$@" -S '' -- -
fi
}
_arguments -s \
'(-a -A -c -n -f)-a[attach to an existing session]' \
'(-a -A -c -n)-A[attach to a session, create if does not exist]' \
'(-a -A -c -n -l)-c[create a new session and attach to it]' \
'(-a -A -c -n -l)-n[create a new session but do not attach to it]' \
'-e[set the detachkey (default: ^\\)]:detachkey' \
'(-a)-f[force create the session]' \
'(-q)-p[pass-through mode]' \
'-q[be quiet]' \
'-r[read-only session, ignore user input]' \
'(-c -n)-l[attach with the lowest priority]' \
'(-)-v[show version information and exit]' \
'1: :_abduco_firstarg' \
'2:command:_path_commands' \
'*:: :{ shift $((CURRENT-3)) words; _precommand; }'

20
debug.c
View File

@ -16,8 +16,8 @@ static void print_packet(const char *prefix, Packet *pkt) {
[MSG_ATTACH] = "ATTACH",
[MSG_DETACH] = "DETACH",
[MSG_RESIZE] = "RESIZE",
[MSG_REDRAW] = "REDRAW",
[MSG_EXIT] = "EXIT",
[MSG_PID] = "PID",
};
const char *type = "UNKNOWN";
if (pkt->type < countof(msgtype) && msgtype[pkt->type])
@ -26,14 +26,24 @@ static void print_packet(const char *prefix, Packet *pkt) {
fprintf(stderr, "%s: %s ", prefix, type);
switch (pkt->type) {
case MSG_CONTENT:
for (size_t i = 0; i < pkt->len && i < sizeof(pkt->u.msg); i++)
fprintf(stderr, "%c", pkt->u.msg[i]);
fwrite(pkt->u.msg, pkt->len, 1, stderr);
break;
case MSG_RESIZE:
fprintf(stderr, "%dx%d", pkt->u.ws.ws_col, pkt->u.ws.ws_row);
fprintf(stderr, "%"PRIu16"x%"PRIu16, pkt->u.ws.cols, pkt->u.ws.rows);
break;
case MSG_ATTACH:
fprintf(stderr, "readonly: %d low-priority: %d",
pkt->u.i & CLIENT_READONLY,
pkt->u.i & CLIENT_LOWPRIORITY);
break;
case MSG_EXIT:
fprintf(stderr, "status: %"PRIu32, pkt->u.i);
break;
case MSG_PID:
fprintf(stderr, "pid: %"PRIu32, pkt->u.i);
break;
default:
fprintf(stderr, "len: %zu", pkt->len);
fprintf(stderr, "len: %"PRIu32, pkt->len);
break;
}
fprintf(stderr, "\n");

View File

@ -18,6 +18,18 @@ static void client_free(Client *c) {
free(c);
}
static void server_sink_client() {
if (!server.clients || !server.clients->next)
return;
Client *target = server.clients;
server.clients = target->next;
Client *dst = server.clients;
while (dst->next)
dst = dst->next;
target->next = NULL;
dst->next = target;
}
static void server_mark_socket_exec(bool exec, bool usr) {
struct stat sb;
if (stat(sockaddr.sun_path, &sb) == -1)
@ -63,33 +75,14 @@ static int server_set_socket_non_blocking(int sock) {
return fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}
static Client *server_accept_client(void) {
int newfd = accept(server.socket, NULL, NULL);
if (newfd == -1 || server_set_socket_non_blocking(newfd) == -1)
goto error;
Client *c = client_malloc(newfd);
if (!c)
goto error;
if (!server.clients)
server_mark_socket_exec(true, true);
c->socket = newfd;
c->state = STATE_CONNECTED;
c->next = server.clients;
server.clients = c;
server.read_pty = true;
return c;
error:
if (newfd != -1)
close(newfd);
return NULL;
}
static bool server_read_pty(Packet *pkt) {
pkt->type = MSG_CONTENT;
ssize_t len = read(server.pty, pkt->u.msg, sizeof(pkt->u.msg));
if (len != -1)
if (len > 0)
pkt->len = len;
else if (errno != EAGAIN && errno != EINTR)
else if (len == 0)
server.running = false;
else if (len == -1 && errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK)
server.running = false;
print_packet("server-read-pty:", pkt);
return len > 0;
@ -143,6 +136,35 @@ static void server_sigterm_handler(int sig) {
exit(EXIT_FAILURE); /* invoke atexit handler */
}
static Client *server_accept_client(void) {
int newfd = accept(server.socket, NULL, NULL);
if (newfd == -1 || server_set_socket_non_blocking(newfd) == -1)
goto error;
Client *c = client_malloc(newfd);
if (!c)
goto error;
if (!server.clients)
server_mark_socket_exec(true, true);
c->socket = newfd;
c->state = STATE_CONNECTED;
c->next = server.clients;
server.clients = c;
server.read_pty = true;
Packet pkt = {
.type = MSG_PID,
.len = sizeof pkt.u.l,
.u.l = getpid(),
};
server_send_packet(c, &pkt);
return c;
error:
if (newfd != -1)
close(newfd);
return NULL;
}
static void server_sigusr1_handler(int sig) {
int socket = server_create_socket(server.session_name);
if (socket != -1) {
@ -201,19 +223,25 @@ static void server_mainloop(void) {
server_write_pty(&client_packet);
break;
case MSG_ATTACH:
c->readonly = client_packet.u.b;
c->flags = client_packet.u.i;
if (c->flags & CLIENT_LOWPRIORITY)
server_sink_client();
break;
case MSG_RESIZE:
c->state = STATE_ATTACHED;
case MSG_REDRAW:
if (!c->readonly && (client_packet.type == MSG_REDRAW || c == server.clients)) {
if (!(c->flags & CLIENT_READONLY) && c == server.clients) {
debug("server-ioct: TIOCSWINSZ\n");
ioctl(server.pty, TIOCSWINSZ, &client_packet.u.ws);
struct winsize ws = { 0 };
ws.ws_row = client_packet.u.ws.rows;
ws.ws_col = client_packet.u.ws.cols;
ioctl(server.pty, TIOCSWINSZ, &ws);
}
kill(-server.pid, SIGWINCH);
break;
case MSG_DETACH:
case MSG_EXIT:
exit_packet_delivered = true;
/* fall through */
case MSG_DETACH:
c->state = STATE_DISCONNECTED;
break;
default: /* ignore package */
@ -249,9 +277,7 @@ static void server_mainloop(void) {
.u.i = server.exit_status,
.len = sizeof(pkt.u.i),
};
if (server_send_packet(c, &pkt))
exit_packet_delivered = true;
else
if (!server_send_packet(c, &pkt))
FD_SET_MAX(c->socket, &new_writefds, new_fdmax);
} else {
FD_SET_MAX(c->socket, &new_writefds, new_fdmax);

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh
ABDUCO="./abduco"
# set detach key explicitly in case it was changed in config.h
@ -7,6 +7,9 @@ ABDUCO_OPTS="-e ^\\"
[ ! -z "$1" ] && ABDUCO="$1"
[ ! -x "$ABDUCO" ] && echo "usage: $0 /path/to/abduco" && exit 1
TESTS_OK=0
TESTS_RUN=0
detach() {
sleep 1
printf ""
@ -27,7 +30,7 @@ dvtm_session() {
dvtm_cmd ' '
dvtm_cmd ' '
sleep 1
dvtm_cmd 'q'
dvtm_cmd 'qq'
}
expected_abduco_prolog() {
@ -36,7 +39,7 @@ expected_abduco_prolog() {
# $1 => session-name, $2 => exit status
expected_abduco_epilog() {
echo "[?1049labduco: $1: session terminated with exit status $2"
echo "[?25h[?1049labduco: $1: session terminated with exit status $2"
}
# $1 => session-name, $2 => cmd to run
@ -49,7 +52,7 @@ expected_abduco_attached_output() {
# $1 => session-name, $2 => cmd to run
expected_abduco_detached_output() {
expected_abduco_prolog
$2 &> /dev/null
$2 >/dev/null 2>&1
expected_abduco_epilog "$1" $?
}
@ -61,7 +64,7 @@ check_environment() {
test_non_existing_command() {
check_environment || return 1;
$ABDUCO -c test ./non-existing-command &> /dev/null
$ABDUCO -c test ./non-existing-command >/dev/null 2>&1
check_environment || return 1;
}
@ -74,12 +77,14 @@ run_test_attached() {
local output="$name.out"
local output_expected="$name.expected"
TESTS_RUN=$((TESTS_RUN + 1))
echo -n "Running test attached: $name "
expected_abduco_attached_output "$name" "$cmd" &> "$output_expected"
$ABDUCO -c "$name" $cmd 2>&1 | sed 's/.$//' > "$output"
expected_abduco_attached_output "$name" "$cmd" > "$output_expected" 2>&1
if diff -u "$output_expected" "$output" && check_environment; then
if $ABDUCO -c "$name" $cmd 2>&1 | sed 's/.$//' > "$output" && sleep 1 &&
diff -u "$output_expected" "$output" && check_environment; then
rm "$output" "$output_expected"
TESTS_OK=$((TESTS_OK + 1))
echo "OK"
return 0
else
@ -97,13 +102,15 @@ run_test_detached() {
local output="$name.out"
local output_expected="$name.expected"
TESTS_RUN=$((TESTS_RUN + 1))
echo -n "Running test detached: $name "
expected_abduco_detached_output "$name" "$cmd" &> "$output_expected"
expected_abduco_detached_output "$name" "$cmd" > "$output_expected" 2>&1
if $ABDUCO -n "$name" $cmd &> /dev/null && sleep 1 &&
if $ABDUCO -n "$name" $cmd >/dev/null 2>&1 && sleep 1 &&
$ABDUCO -a "$name" 2>&1 | sed 's/.$//' > "$output" &&
diff -u "$output_expected" "$output" && check_environment; then
rm "$output" "$output_expected"
TESTS_OK=$((TESTS_OK + 1))
echo "OK"
return 0
else
@ -121,14 +128,16 @@ run_test_attached_detached() {
local output="$name.out"
local output_expected="$name.expected"
TESTS_RUN=$((TESTS_RUN + 1))
echo -n "Running test: $name "
$cmd &> /dev/null
expected_abduco_epilog "$name" $? &> "$output_expected"
$cmd >/dev/null 2>&1
expected_abduco_epilog "$name" $? > "$output_expected" 2>&1
if detach | $ABDUCO $ABDUCO_OPTS -c "$name" $cmd &> /dev/null && sleep 3 &&
if detach | $ABDUCO $ABDUCO_OPTS -c "$name" $cmd >/dev/null 2>&1 && sleep 3 &&
$ABDUCO -a "$name" 2>&1 | tail -1 | sed 's/.$//' > "$output" &&
diff -u "$output_expected" "$output" && check_environment; then
rm "$output" "$output_expected"
TESTS_OK=$((TESTS_OK + 1))
echo "OK"
return 0
else
@ -139,22 +148,21 @@ run_test_attached_detached() {
run_test_dvtm() {
echo -n "Running dvtm test: "
if ! which dvtm &> /dev/null; then
if ! which dvtm >/dev/null 2>&1; then
echo "SKIPPED"
return 0;
fi
TESTS_RUN=$((TESTS_RUN + 1))
local name="dvtm"
local output="$name.out"
local output_expected="$name.expected"
echo exit | dvtm &> /dev/null
expected_abduco_epilog "$name" $? > "$output_expected"
local len=`wc -c "$output_expected" | awk '{ print $1 }'`
len=$((len+1))
if dvtm_session | $ABDUCO -c "$name" 2>&1 | tail -c $len | sed 's/.$//' > "$output" &&
: > "$output_expected"
if dvtm_session | $ABDUCO -c "$name" > "$output" 2>&1 &&
diff -u "$output_expected" "$output" && check_environment; then
rm "$output" "$output_expected"
TESTS_OK=$((TESTS_OK + 1))
echo "OK"
return 0
else
@ -165,8 +173,8 @@ run_test_dvtm() {
test_non_existing_command || echo "Execution of non existing command FAILED"
run_test_attached "seq" "seq 1 1000"
run_test_detached "seq" "seq 1 1000"
run_test_attached "awk" "awk 'BEGIN {for(i=1;i<=1000;i++) print i}'"
run_test_detached "awk" "awk 'BEGIN {for(i=1;i<=1000;i++) print i}'"
run_test_attached "false" "false"
run_test_detached "false" "false"
@ -203,3 +211,5 @@ run_test_attached_detached "attach-detach" "./long-running.sh"
rm ./long-running.sh
run_test_dvtm
[ $TESTS_OK -eq $TESTS_RUN ]