pax_global_header00006660000000000000000000000064126747700470014530gustar00rootroot0000000000000052 comment=e76729a2df45ecabe72a423e925ed876f66235d6 abduco-0.6/000077500000000000000000000000001267477004700126325ustar00rootroot00000000000000abduco-0.6/.gitignore000066400000000000000000000000661267477004700146240ustar00rootroot00000000000000# normal ignores .* *.[ao] *.lo *.so tags !.gitignore abduco-0.6/.travis.yml000066400000000000000000000001341267477004700147410ustar00rootroot00000000000000language: c compiler: - gcc - clang env: - DEBUG= - DEBUG=debug script: make $DEBUG abduco-0.6/LICENSE000066400000000000000000000013751267477004700136450ustar00rootroot00000000000000Copyright (c) 2013-2016 Marc André Tanner Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. abduco-0.6/Makefile000066400000000000000000000030631267477004700142740ustar00rootroot00000000000000include config.mk 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}" config.h: cp config.def.h config.h .c.o: @echo CC $< @${CC} -c ${CFLAGS} $< ${OBJ}: config.h config.mk abduco: ${OBJ} @echo CC -o $@ @${CC} -o $@ ${OBJ} ${LDFLAGS} debug: clean @make CFLAGS='${DEBUG_CFLAGS}' clean: @echo cleaning @rm -f abduco ${OBJ} abduco-${VERSION}.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} install: abduco @echo stripping executable @${STRIP} abduco @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 @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 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 .PHONY: all options clean dist install uninstall debug abduco-0.6/README.md000066400000000000000000000173001267477004700141120ustar00rootroot00000000000000# abduco a tool for session {at,de}tach support [abduco](http://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 simpler and cleaner alternative to tmux or screen. ![abduco+dvtm demo](https://raw.githubusercontent.com/martanne/abduco/gh-pages/screencast.gif) 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 [ISC license](https://raw.githubusercontent.com/martanne/abduco/master/LICENSE) ## News * [abduco-0.5](http://www.brain-dump.org/projects/abduco/abduco-0.5.tar.gz) [released](http://lists.suckless.org/dev/1601/28094.html) (09.01.2016) * [abduco-0.4](http://www.brain-dump.org/projects/abduco/abduco-0.4.tar.gz) [released](http://lists.suckless.org/dev/1503/26027.html) (18.03.2015) * [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) on the suckless development mailing list (08.03.2014) ## Download Either download the latest source tarball [abduco-0.5.tar.gz](http://www.brain-dump.org/projects/abduco/abduco-0.5.tar.gz) with sha1sum 37c51a0d5c3dd216251d84d5c1b550f119ad53c9 abduco-0.5.tar.gz compile and install it $EDITOR config.mk && 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 ## Quickstart 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: $ abduco -c demo 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 for example set it to `CTRL-q`. To get an overview of existing session run `abduco` without any arguments. $ abduco Active sessions (on host debbook) * Thu 2015-03-12 12:05:20 demo-active + Thu 2015-03-12 12:04:50 demo-finished Thu 2015-03-12 12:03:30 demo A leading asterisk `*` indicates that at least one client is connected. A leading plus `+` denotes that the session terminated, attaching to it will print its exit status. 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 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. ## Improvements over dtach * **session list**, available by executing `abduco` without any arguments, indicating whether clients are connected or the command has already terminated. * the **session exit status** of the command being run is always kept and reported either upon command termination or on reconnection e.g. the following works: $ abduco -n demo true && abduco -a demo abduco: demo: session terminated with exit status 0 * **read only sessions** if the `-r` command line argument is used when 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 read only client. * **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 process. $ pgrep -P 1 abduco After finding the correct PID the socket can be recreated with $ kill -USR1 $PID 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 * **improved socket permissions** the session sockets are by default either stored in `$HOME/.abduco` or `/tmp/abduco/$USER` in both cases it is made sure that only the owner has access to the respective directory. ## 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 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. [![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 abduco is licensed under the [ISC license](https://raw.githubusercontent.com/martanne/abduco/master/LICENSE) abduco-0.6/abduco.1000066400000000000000000000052421267477004700141540ustar00rootroot00000000000000.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 [ \-l ] .RB \-A .RB name .RB command .RI [ args \ ... "" ] .br .B abduco .RB [ \-e .IR detachkey ] .RB [ \-r ] .RB [ \-l ] .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. All session related information is stored in the following directories (first to succeed is used): .RS .nf .PP .B $ABDUCO_SOCKET_DIR/abduco .B $HOME/.abduco .B $TMPDIR/abduco/$USER .B /tmp/abduco/$USER .fi .RE 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. 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 .B \-e \fIdetachkey\fR Set the key to detach which by default is set to CTRL+\\ i.e. ^\\ to detachkey. .TP .BI \-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. .TP .BI \-l Attach with the lowest priority, meaning this client will be the last to control the size. .SH EXAMPLE Start a new session (assuming .BR dvtm(1) is in .BR $PATH ) with .nf .B abduco -c my-session .fi 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 abduco-0.6/abduco.c000066400000000000000000000416571267477004700142500ustar00rootroot00000000000000/* * Copyright (c) 2013-2016 Marc André Tanner * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux__) || defined(__CYGWIN__) # include #elif defined(__FreeBSD__) || defined(__DragonFly__) # include #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include #endif #if defined CTRL && defined _AIX #undef CTRL #endif #ifndef CTRL #define CTRL(k) ((k) & 0x1F) #endif #include "config.h" #if defined(_AIX) # include "forkpty-aix.c" #elif defined(__sun) # include "forkpty-sunos.c" #endif #define countof(arr) (sizeof(arr) / sizeof((arr)[0])) enum PacketType { MSG_CONTENT = 0, MSG_ATTACH = 1, MSG_DETACH = 2, MSG_RESIZE = 3, MSG_REDRAW = 4, MSG_EXIT = 5, }; typedef struct { unsigned int type; size_t len; union { char msg[BUFSIZ]; struct winsize ws; int i; } u; } Packet; typedef struct Client Client; struct Client { int socket; enum { STATE_CONNECTED, STATE_ATTACHED, STATE_DETACHED, STATE_DISCONNECTED, } state; bool need_resize; enum { CLIENT_READONLY = 1 << 0, CLIENT_LOWPRIORITY = 1 << 1, } flags; Client *next; }; typedef struct { Client *clients; int socket; Packet pty_output; int pty; int exit_status; struct termios term; struct winsize winsize; pid_t pid; volatile sig_atomic_t running; const char *name; const char *session_name; char host[255]; bool read_pty; } Server; 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 struct sockaddr_un sockaddr = { .sun_family = AF_UNIX, }; static bool set_socket_name(struct sockaddr_un *sockaddr, const char *name); static void die(const char *s); static void info(const char *str, ...); #include "debug.c" static inline size_t packet_header_size() { return offsetof(Packet, u); } static size_t packet_size(Packet *pkt) { return packet_header_size() + pkt->len; } static ssize_t write_all(int fd, const char *buf, size_t len) { debug("write_all(%d)\n", len); ssize_t ret = len; while (len > 0) { ssize_t res = write(fd, buf, len); if (res < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; return -1; } if (res == 0) return ret - len; buf += res; len -= res; } return ret; } static ssize_t read_all(int fd, char *buf, size_t len) { debug("read_all(%d)\n", len); ssize_t ret = len; while (len > 0) { ssize_t res = read(fd, buf, len); if (res < 0) { if (errno == EWOULDBLOCK) return ret - len; if (errno == EAGAIN || errno == EINTR) continue; return -1; } if (res == 0) return ret - len; buf += res; len -= res; } return ret; } static bool send_packet(int socket, Packet *pkt) { size_t size = packet_size(pkt); if (size > sizeof(*pkt)) return false; return write_all(socket, (char *)pkt, size) == size; } static bool recv_packet(int socket, Packet *pkt) { ssize_t len = read_all(socket, (char*)pkt, packet_header_size()); if (len <= 0 || len != packet_header_size()) return false; if (pkt->len > sizeof(pkt->u.msg)) { pkt->len = 0; return false; } if (pkt->len > 0) { len = read_all(socket, pkt->u.msg, pkt->len); if (len <= 0 || len != pkt->len) return false; } return true; } #include "client.c" #include "server.c" static void info(const char *str, ...) { va_list ap; va_start(ap, str); if (str) { fprintf(stderr, "%s: %s: ", server.name, server.session_name); vfprintf(stderr, str, ap); fprintf(stderr, "\r\n"); } fflush(stderr); va_end(ap); } static void die(const char *s) { perror(s); exit(EXIT_FAILURE); } static void usage(void) { fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-r] [-l] [-f] [-e detachkey] name command\n"); exit(EXIT_FAILURE); } static bool xsnprintf(char *buf, size_t size, const char *fmt, ...) { va_list ap; if (size > INT_MAX) return false; va_start(ap, fmt); int n = vsnprintf(buf, size, fmt, ap); va_end(ap); if (n == -1) return false; if (n >= size) { errno = ENAMETOOLONG; return false; } 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 bool session_exists(const char *name) { int fd = session_connect(name); if (fd != -1) close(fd); return fd != -1; } 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'; int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); if (socketfd == -1) return false; 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; 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->path, ishome ? "." : "", server.name)) continue; mode_t mask = umask(0); 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; if (lstat(sockaddr->sun_path, &sb) != 0) continue; if (!S_ISDIR(sb.st_mode)) { errno = ENOTDIR; continue; } size_t dirlen = strlen(sockaddr->sun_path); 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)) continue; if (mkdir(sockaddr->sun_path, S_IRWXU) != 0 && errno != EEXIST) continue; if (lstat(sockaddr->sun_path, &sb) != 0) continue; if (!S_ISDIR(sb.st_mode)) { errno = ENOTDIR; continue; } dirlen = strlen(sockaddr->sun_path); } if (sb.st_uid != uid || sb.st_mode & (S_IRWXG|S_IRWXO)) { errno = EACCES; continue; } if (!xsnprintf(sockaddr->sun_path+dirlen, maxlen-dirlen, ".abduco-%d", getpid())) continue; socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr->sun_path) + 1; if (bind(socketfd, (struct sockaddr*)sockaddr, socklen) == -1) continue; unlink(sockaddr->sun_path); close(socketfd); sockaddr->sun_path[dirlen] = '\0'; return true; } close(socketfd); return false; } static bool set_socket_name(struct sockaddr_un *sockaddr, const char *name) { size_t maxlen = sizeof(sockaddr->sun_path); if (name[0] == '/') { if (strlen(name) >= maxlen) { errno = ENAMETOOLONG; return false; } strncpy(sockaddr->sun_path, name, maxlen); } else if (name[0] == '.' && (name[1] == '.' || name[1] == '/')) { char buf[maxlen], *cwd = getcwd(buf, sizeof buf); if (!cwd) return false; if (!xsnprintf(sockaddr->sun_path, maxlen, "%s/%s", cwd, name)) return false; } else { if (!create_socket_dir(sockaddr)) return false; if (strlen(sockaddr->sun_path) + strlen(name) + strlen(server.host) >= maxlen) { errno = ENAMETOOLONG; return false; } strncat(sockaddr->sun_path, name, maxlen - strlen(sockaddr->sun_path) - 1); strncat(sockaddr->sun_path, server.host, maxlen - strlen(sockaddr->sun_path) - 1); } return true; } static bool create_session(const char *name, char * const argv[]) { /* this uses the well known double fork strategy as described in section 1.7 of * * http://www.faqs.org/faqs/unix-faq/programmer/faq/ * * 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 * indicated by EOF on the pipe. */ int client_pipe[2], server_pipe[2]; pid_t pid; 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) return false; switch ((pid = fork())) { case 0: /* child process */ setsid(); close(client_pipe[0]); switch ((pid = fork())) { case 0: /* child process */ if (pipe(server_pipe) == -1) { snprintf(errormsg, sizeof(errormsg), "server-pipe: %s\n", strerror(errno)); write_all(client_pipe[1], errormsg, strlen(errormsg)); close(client_pipe[1]); _exit(EXIT_FAILURE); } sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = server_pty_died_handler; sigaction(SIGCHLD, &sa, NULL); switch (server.pid = forkpty(&server.pty, NULL, has_term ? &server.term : NULL, &server.winsize)) { case 0: /* child = user application process */ close(server.socket); close(server_pipe[0]); if (fcntl(client_pipe[1], F_SETFD, FD_CLOEXEC) == 0 && fcntl(server_pipe[1], F_SETFD, FD_CLOEXEC) == 0) execvp(argv[0], argv); snprintf(errormsg, sizeof(errormsg), "server-execvp: %s: %s\n", argv[0], strerror(errno)); write_all(client_pipe[1], errormsg, strlen(errormsg)); write_all(server_pipe[1], errormsg, strlen(errormsg)); close(client_pipe[1]); close(server_pipe[1]); _exit(EXIT_FAILURE); break; case -1: /* forkpty failed */ snprintf(errormsg, sizeof(errormsg), "server-forkpty: %s\n", strerror(errno)); write_all(client_pipe[1], errormsg, strlen(errormsg)); close(client_pipe[1]); close(server_pipe[0]); close(server_pipe[1]); _exit(EXIT_FAILURE); break; default: /* parent = server process */ sa.sa_handler = server_sigterm_handler; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sa.sa_handler = server_sigusr1_handler; sigaction(SIGUSR1, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); sigaction(SIGHUP, &sa, NULL); chdir("/"); #ifdef NDEBUG int fd = open("/dev/null", O_RDWR); if (fd != -1) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } #endif /* NDEBUG */ close(client_pipe[1]); close(server_pipe[1]); if (read_all(server_pipe[0], errormsg, sizeof(errormsg)) > 0) _exit(EXIT_FAILURE); close(server_pipe[0]); server_mainloop(); break; } break; case -1: /* fork failed */ snprintf(errormsg, sizeof(errormsg), "server-fork: %s\n", strerror(errno)); write_all(client_pipe[1], errormsg, strlen(errormsg)); close(client_pipe[1]); _exit(EXIT_FAILURE); break; default: /* parent = intermediate process */ close(client_pipe[1]); _exit(EXIT_SUCCESS); break; } break; case -1: /* fork failed */ close(client_pipe[0]); close(client_pipe[1]); return false; default: /* parent = client process */ close(client_pipe[1]); int status; wait(&status); /* wait for first fork */ ssize_t len = read_all(client_pipe[0], errormsg, sizeof(errormsg)); if (len > 0) { write_all(STDERR_FILENO, errormsg, len); unlink(sockaddr.sun_path); exit(EXIT_FAILURE); } close(client_pipe[0]); } return true; } static bool attach_session(const char *name, const bool terminate) { if (server.socket > 0) close(server.socket); if ((server.socket = session_connect(name)) == -1) return false; if (server_set_socket_non_blocking(server.socket) == -1) return false; struct sigaction sa; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = client_sigwinch_handler; sigaction(SIGWINCH, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); client_setup_terminal(); int status = client_mainloop(); client_restore_terminal(); if (status == -1) { info("detached"); } else if (status == -EIO) { info("exited due to I/O errors"); } else { info("session terminated with exit status %d", status); if (terminate) exit(status); } return terminate; } static int session_filter(const struct dirent *d) { return strstr(d->d_name, server.host) != NULL; } static int session_comparator(const struct dirent **a, const struct dirent **b) { struct stat sa, sb; if (stat((*a)->d_name, &sa) != 0) return -1; if (stat((*b)->d_name, &sb) != 0) return 1; return sa.st_atime < sb.st_atime ? -1 : 1; } static int list_session(void) { if (!create_socket_dir(&sockaddr)) return 1; chdir(sockaddr.sun_path); struct dirent **namelist; int n = scandir(sockaddr.sun_path, &namelist, session_filter, session_comparator); if (n < 0) return 1; printf("Active sessions (on host %s)\n", server.host+1); 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_mtime)); char status = ' '; char *local = strstr(namelist[n]->d_name, server.host); if (local) { *local = '\0'; /* truncate hostname if we are local */ if (!session_exists(namelist[n]->d_name)) continue; } if (sb.st_mode & S_IXUSR) status = '*'; else if (sb.st_mode & S_IXGRP) status = '+'; printf("%c %s\t%s\n", status, buf, namelist[n]->d_name); } free(namelist[n]); } free(namelist); return 0; } 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); while ((opt = getopt(argc, argv, "aAclne:frv")) != -1) { switch (opt) { case 'a': case 'A': case 'c': case 'n': action = opt; break; case 'e': if (!optarg) usage(); if (optarg[0] == '^' && optarg[1]) optarg[0] = CTRL(optarg[1]); KEY_DETACH = optarg[0]; break; case 'f': force = true; break; case 'r': client.flags |= CLIENT_READONLY; break; case 'l': client.flags |= CLIENT_LOWPRIORITY; break; case 'v': puts("abduco-"VERSION" © 2013-2016 Marc André Tanner"); exit(EXIT_SUCCESS); default: usage(); } } /* 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 (!action && !server.session_name) exit(list_session()); if (!action || !server.session_name) usage(); if (tcgetattr(STDIN_FILENO, &orig_term) != -1) { server.term = orig_term; has_term = true; } if (ioctl(STDIN_FILENO, TIOCGWINSZ, &server.winsize) == -1) { server.winsize.ws_col = 80; server.winsize.ws_row = 25; } server.read_pty = (action == 'n'); redo: switch (action) { case 'n': case 'c': if (force) { if (session_alive(server.session_name)) { info("session exists and has not yet terminated"); return 1; } if (session_exists(server.session_name)) attach_session(server.session_name, false); } if (!create_session(server.session_name, cmd)) die("create-session"); if (action == 'n') break; case 'a': if (!attach_session(server.session_name, true)) die("attach-session"); break; case 'A': 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; } break; } return 0; } abduco-0.6/client.c000066400000000000000000000062361267477004700142630ustar00rootroot00000000000000static void client_sigwinch_handler(int sig) { client.need_resize = true; } static bool client_send_packet(Packet *pkt) { print_packet("client-send:", pkt); if (send_packet(server.socket, pkt)) return true; debug("FAILED\n"); server.running = false; return false; } static bool client_recv_packet(Packet *pkt) { if (recv_packet(server.socket, pkt)) { print_packet("client-recv:", pkt); return true; } debug("client-recv: FAILED\n"); server.running = false; return false; } static void client_restore_terminal(void) { if (has_term) tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_term); if (alternate_buffer) { printf("\033[?25h\033[?1049l"); fflush(stdout); alternate_buffer = false; } } static void client_setup_terminal(void) { atexit(client_restore_terminal); cur_term = orig_term; cur_term.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF); cur_term.c_oflag &= ~(OPOST); cur_term.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); cur_term.c_cflag &= ~(CSIZE|PARENB); cur_term.c_cflag |= CS8; cur_term.c_cc[VLNEXT] = _POSIX_VDISABLE; cur_term.c_cc[VMIN] = 1; cur_term.c_cc[VTIME] = 0; tcsetattr(STDIN_FILENO, TCSANOW, &cur_term); if (!alternate_buffer) { printf("\033[?1049h\033[H"); fflush(stdout); alternate_buffer = true; } } static int client_mainloop(void) { sigset_t emptyset, blockset; sigemptyset(&emptyset); sigemptyset(&blockset); sigaddset(&blockset, SIGWINCH); sigprocmask(SIG_BLOCK, &blockset, NULL); client.need_resize = true; Packet pkt = { .type = MSG_ATTACH, .u.i = client.flags, .len = sizeof(pkt.u.i), }; client_send_packet(&pkt); while (server.running) { fd_set fds; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); FD_SET(server.socket, &fds); if (client.need_resize) { struct winsize ws; if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != -1) { Packet pkt = { .type = MSG_RESIZE, .u = { .ws = ws }, .len = sizeof(ws), }; if (client_send_packet(&pkt)) client.need_resize = false; } } if (pselect(server.socket+1, &fds, NULL, NULL, NULL, &emptyset) == -1) { if (errno == EINTR) continue; die("client-mainloop"); } if (FD_ISSET(server.socket, &fds)) { Packet pkt; if (client_recv_packet(&pkt)) { switch (pkt.type) { case MSG_CONTENT: write_all(STDOUT_FILENO, pkt.u.msg, pkt.len); break; case MSG_RESIZE: client.need_resize = true; break; case MSG_EXIT: client_send_packet(&pkt); close(server.socket); return pkt.u.i; } } } if (FD_ISSET(STDIN_FILENO, &fds)) { Packet pkt = { .type = MSG_CONTENT }; ssize_t len = read(STDIN_FILENO, pkt.u.msg, sizeof(pkt.u.msg)); if (len == -1 && errno != EAGAIN && errno != EINTR) die("client-stdin"); if (len > 0) { debug("client-stdin: %c\n", pkt.u.msg[0]); pkt.len = len; 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; pkt.len = 0; client_send_packet(&pkt); close(server.socket); return -1; } else if (!(client.flags & CLIENT_READONLY)) { client_send_packet(&pkt); } } } } return -EIO; } abduco-0.6/config.def.h000066400000000000000000000015351267477004700150110ustar00rootroot00000000000000/* 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 }, }; abduco-0.6/config.mk000066400000000000000000000006101267477004700144250ustar00rootroot00000000000000# abduco version VERSION = 0.6 # 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 abduco-0.6/debug.c000066400000000000000000000021421267477004700140630ustar00rootroot00000000000000#ifdef NDEBUG static void debug(const char *errstr, ...) { } static void print_packet(const char *prefix, Packet *pkt) { } #else static void debug(const char *errstr, ...) { va_list ap; va_start(ap, errstr); vfprintf(stderr, errstr, ap); va_end(ap); } static void print_packet(const char *prefix, Packet *pkt) { static const char *msgtype[] = { [MSG_CONTENT] = "CONTENT", [MSG_ATTACH] = "ATTACH", [MSG_DETACH] = "DETACH", [MSG_RESIZE] = "RESIZE", [MSG_REDRAW] = "REDRAW", [MSG_EXIT] = "EXIT", }; const char *type = "UNKNOWN"; if (pkt->type < countof(msgtype) && msgtype[pkt->type]) type = msgtype[pkt->type]; fprintf(stderr, "%s: %s ", prefix, type); switch (pkt->type) { case MSG_CONTENT: 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); break; case MSG_ATTACH: fprintf(stderr, "readonly: %d low-priority: %d", pkt->u.i & CLIENT_READONLY, pkt->u.i & CLIENT_LOWPRIORITY); break; default: fprintf(stderr, "len: %zu", pkt->len); break; } fprintf(stderr, "\n"); } #endif /* NDEBUG */ abduco-0.6/forkpty-aix.c000066400000000000000000000043261267477004700152600ustar00rootroot00000000000000/* * Copyright (c) 2009 Nicholas Marriott * Copyright (c) 2012 Ross Palmer Mohn * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include pid_t forkpty(int *master, char *name, struct termios *tio, struct winsize *ws) { int slave, fd; char *path; pid_t pid; struct termios tio2; if ((*master = open("/dev/ptc", O_RDWR|O_NOCTTY)) == -1) return -1; if ((path = ttyname(*master)) == NULL) goto out; if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1) goto out; switch (pid = fork()) { case -1: goto out; case 0: close(*master); fd = open(_PATH_TTY, O_RDWR|O_NOCTTY); if (fd >= 0) { ioctl(fd, TIOCNOTTY, NULL); close(fd); } setsid(); fd = open(_PATH_TTY, O_RDWR|O_NOCTTY); if (fd >= 0) return -1; fd = open(path, O_RDWR); if (fd < 0) return -1; close(fd); fd = open("/dev/tty", O_WRONLY); if (fd < 0) return -1; close(fd); if (tcgetattr(slave, &tio2) != 0) return -1; if (tio != NULL) memcpy(tio2.c_cc, tio->c_cc, sizeof tio2.c_cc); tio2.c_cc[VERASE] = '\177'; if (tcsetattr(slave, TCSAFLUSH, &tio2) == -1) return -1; if (ioctl(slave, TIOCSWINSZ, ws) == -1) return -1; dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); if (slave > 2) close(slave); return 0; } close(slave); return pid; out: if (*master != -1) close(*master); if (slave != -1) close(slave); return -1; } abduco-0.6/forkpty-sunos.c000066400000000000000000000040751267477004700156470ustar00rootroot00000000000000/* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #ifndef TTY_NAME_MAX #define TTY_NAME_MAX TTYNAME_MAX #endif pid_t forkpty(int *master, char *name, struct termios *tio, struct winsize *ws) { int slave; char *path; pid_t pid; if ((*master = open("/dev/ptmx", O_RDWR|O_NOCTTY)) == -1) return -1; if (grantpt(*master) != 0) goto out; if (unlockpt(*master) != 0) goto out; if ((path = ptsname(*master)) == NULL) goto out; if (name != NULL) strlcpy(name, path, TTY_NAME_MAX); if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1) goto out; switch (pid = fork()) { case -1: goto out; case 0: close(*master); setsid(); #ifdef TIOCSCTTY if (ioctl(slave, TIOCSCTTY, NULL) == -1) return -1; #endif if (ioctl(slave, I_PUSH, "ptem") == -1) return -1; if (ioctl(slave, I_PUSH, "ldterm") == -1) return -1; if (tio != NULL && tcsetattr(slave, TCSAFLUSH, tio) == -1) return -1; if (ioctl(slave, TIOCSWINSZ, ws) == -1) return -1; dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); if (slave > 2) close(slave); return 0; } close(slave); return pid; out: if (*master != -1) close(*master); if (slave != -1) close(slave); return -1; } abduco-0.6/server.c000066400000000000000000000152531267477004700143120ustar00rootroot00000000000000#define FD_SET_MAX(fd, set, maxfd) do { \ FD_SET(fd, set); \ if (fd > maxfd) \ maxfd = fd; \ } while (0) static Client *client_malloc(int socket) { Client *c = calloc(1, sizeof(Client)); if (!c) return NULL; c->socket = socket; return c; } static void client_free(Client *c) { if (c && c->socket > 0) close(c->socket); 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) return; mode_t mode = sb.st_mode; mode_t flag = usr ? S_IXUSR : S_IXGRP; if (exec) mode |= flag; else mode &= ~flag; chmod(sockaddr.sun_path, mode); } static int server_create_socket(const char *name) { if (!set_socket_name(&sockaddr, name)) return -1; int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) return -1; socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1; mode_t mask = umask(S_IXUSR|S_IRWXG|S_IRWXO); int r = bind(fd, (struct sockaddr*)&sockaddr, socklen); umask(mask); if (r == -1) { close(fd); return -1; } if (listen(fd, 5) == -1) { unlink(sockaddr.sun_path); close(fd); return -1; } return fd; } static int server_set_socket_non_blocking(int sock) { int flags; if ((flags = fcntl(sock, F_GETFL, 0)) == -1) flags = 0; 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 > 0) pkt->len = len; 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; } static bool server_write_pty(Packet *pkt) { print_packet("server-write-pty:", pkt); size_t size = pkt->len; if (write_all(server.pty, pkt->u.msg, size) == size) return true; debug("FAILED\n"); server.running = false; return false; } static bool server_recv_packet(Client *c, Packet *pkt) { if (recv_packet(c->socket, pkt)) { print_packet("server-recv:", pkt); return true; } debug("server-recv: FAILED\n"); c->state = STATE_DISCONNECTED; return false; } static bool server_send_packet(Client *c, Packet *pkt) { print_packet("server-send:", pkt); if (send_packet(c->socket, pkt)) return true; debug("FAILED\n"); c->state = STATE_DISCONNECTED; return false; } static void server_pty_died_handler(int sig) { int errsv = errno; pid_t pid; while ((pid = waitpid(-1, &server.exit_status, WNOHANG)) != 0) { if (pid == -1) break; server.exit_status = WEXITSTATUS(server.exit_status); server_mark_socket_exec(true, false); } debug("server pty died: %d\n", server.exit_status); errno = errsv; } static void server_sigterm_handler(int sig) { exit(EXIT_FAILURE); /* invoke atexit handler */ } static void server_sigusr1_handler(int sig) { int socket = server_create_socket(server.session_name); if (socket != -1) { if (server.socket) close(server.socket); server.socket = socket; } } static void server_atexit_handler(void) { unlink(sockaddr.sun_path); } static void server_mainloop(void) { atexit(server_atexit_handler); fd_set new_readfds, new_writefds; FD_ZERO(&new_readfds); FD_ZERO(&new_writefds); FD_SET(server.socket, &new_readfds); int new_fdmax = server.socket; bool exit_packet_delivered = false; if (server.read_pty) FD_SET_MAX(server.pty, &new_readfds, new_fdmax); while (server.clients || !exit_packet_delivered) { int fdmax = new_fdmax; fd_set readfds = new_readfds; fd_set writefds = new_writefds; FD_SET_MAX(server.socket, &readfds, fdmax); if (select(fdmax+1, &readfds, &writefds, NULL, NULL) == -1) { if (errno == EINTR) continue; die("server-mainloop"); } FD_ZERO(&new_readfds); FD_ZERO(&new_writefds); new_fdmax = server.socket; bool pty_data = false; Packet server_packet, client_packet; if (FD_ISSET(server.socket, &readfds)) server_accept_client(); if (FD_ISSET(server.pty, &readfds)) pty_data = server_read_pty(&server_packet); for (Client **prev_next = &server.clients, *c = server.clients; c;) { if (FD_ISSET(c->socket, &readfds) && server_recv_packet(c, &client_packet)) { switch (client_packet.type) { case MSG_CONTENT: server_write_pty(&client_packet); break; case MSG_ATTACH: 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->flags & CLIENT_READONLY) && (client_packet.type == MSG_REDRAW || c == server.clients)) { debug("server-ioct: TIOCSWINSZ\n"); ioctl(server.pty, TIOCSWINSZ, &client_packet.u.ws); } kill(-server.pid, SIGWINCH); break; case MSG_EXIT: exit_packet_delivered = true; /* fall through */ case MSG_DETACH: c->state = STATE_DISCONNECTED; break; default: /* ignore package */ break; } } if (c->state == STATE_DISCONNECTED) { bool first = (c == server.clients); Client *t = c->next; client_free(c); *prev_next = c = t; if (first && server.clients) { Packet pkt = { .type = MSG_RESIZE, .len = 0, }; server_send_packet(server.clients, &pkt); } else if (!server.clients) { server_mark_socket_exec(false, true); } continue; } FD_SET_MAX(c->socket, &new_readfds, new_fdmax); if (pty_data) server_send_packet(c, &server_packet); if (!server.running) { if (server.exit_status != -1) { Packet pkt = { .type = MSG_EXIT, .u.i = server.exit_status, .len = sizeof(pkt.u.i), }; 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); } } prev_next = &c->next; c = c->next; } if (server.running && server.read_pty) FD_SET_MAX(server.pty, &new_readfds, new_fdmax); } exit(EXIT_SUCCESS); } abduco-0.6/testsuite.sh000077500000000000000000000104201267477004700152170ustar00rootroot00000000000000#!/bin/sh ABDUCO="./abduco" # set detach key explicitly in case it was changed in config.h ABDUCO_OPTS="-e ^\\" [ ! -z "$1" ] && ABDUCO="$1" [ ! -x "$ABDUCO" ] && echo "usage: $0 /path/to/abduco" && exit 1 detach() { sleep 1 printf "" } dvtm_cmd() { printf "$1\n" sleep 1 } dvtm_session() { sleep 1 dvtm_cmd 'c' dvtm_cmd 'c' dvtm_cmd 'c' sleep 1 dvtm_cmd ' ' dvtm_cmd ' ' dvtm_cmd ' ' sleep 1 dvtm_cmd 'qq' } expected_abduco_prolog() { printf "[?1049h" } # $1 => session-name, $2 => exit status expected_abduco_epilog() { echo "[?25h[?1049labduco: $1: session terminated with exit status $2" } # $1 => session-name, $2 => cmd to run expected_abduco_attached_output() { expected_abduco_prolog $2 expected_abduco_epilog "$1" $? } # $1 => session-name, $2 => cmd to run expected_abduco_detached_output() { expected_abduco_prolog $2 >/dev/null 2>&1 expected_abduco_epilog "$1" $? } check_environment() { [ "`$ABDUCO | wc -l`" -gt 1 ] && echo Abduco session exists && exit 1; pgrep abduco && echo Abduco process exists && exit 1; return 0; } test_non_existing_command() { check_environment || return 1; $ABDUCO -c test ./non-existing-command >/dev/null 2>&1 check_environment || return 1; } # $1 => session-name, $2 => command to execute run_test_attached() { check_environment || return 1; local name="$1" local cmd="$2" local output="$name.out" local output_expected="$name.expected" echo -n "Running test attached: $name " expected_abduco_attached_output "$name" "$cmd" > "$output_expected" 2>&1 $ABDUCO -c "$name" $cmd 2>&1 | sed 's/.$//' > "$output" if diff -u "$output_expected" "$output" && check_environment; then rm "$output" "$output_expected" echo "OK" return 0 else echo "FAIL" return 1 fi } # $1 => session-name, $2 => command to execute run_test_detached() { check_environment || return 1; local name="$1" local cmd="$2" local output="$name.out" local output_expected="$name.expected" echo -n "Running test detached: $name " expected_abduco_detached_output "$name" "$cmd" > "$output_expected" 2>&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" echo "OK" return 0 else echo "FAIL" return 1 fi } # $1 => session-name, $2 => command to execute run_test_attached_detached() { check_environment || return 1; local name="$1" local cmd="$2" local output="$name.out" local output_expected="$name.expected" echo -n "Running test: $name " $cmd >/dev/null 2>&1 expected_abduco_epilog "$name" $? > "$output_expected" 2>&1 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" echo "OK" return 0 else echo "FAIL" return 1 fi } run_test_dvtm() { echo -n "Running dvtm test: " if ! which dvtm >/dev/null 2>&1; then echo "SKIPPED" return 0; fi local name="dvtm" local output="$name.out" local output_expected="$name.expected" echo exit | dvtm >/dev/null 2>&1 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" && diff -u "$output_expected" "$output" && check_environment; then rm "$output" "$output_expected" echo "OK" return 0 else echo "FAIL" return 1 fi } 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 "false" "false" run_test_detached "false" "false" run_test_attached "true" "true" run_test_detached "true" "true" cat > exit-status.sh <<-EOT #!/bin/sh exit 42 EOT chmod +x exit-status.sh run_test_attached "exit-status" "./exit-status.sh" run_test_detached "exit-status" "./exit-status.sh" rm ./exit-status.sh cat > long-running.sh <<-EOT #!/bin/sh echo Start date sleep 3 echo Hello World sleep 3 echo End date exit 1 EOT chmod +x long-running.sh run_test_attached_detached "attach-detach" "./long-running.sh" rm ./long-running.sh run_test_dvtm