Change screencast url in README
[abduco.git] / abduco.c
blob5c4d17442a689dc9ffd0700b6ea42c615b2921ee
1 /*
2 * Copyright (c) 2013-2015 Marc André Tanner <mat at brain-dump.org>
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <stdio.h>
19 #include <stdarg.h>
20 #include <stdlib.h>
21 #include <stdbool.h>
22 #include <stddef.h>
23 #include <signal.h>
24 #include <libgen.h>
25 #include <string.h>
26 #include <limits.h>
27 #include <dirent.h>
28 #include <termios.h>
29 #include <time.h>
30 #include <unistd.h>
31 #include <pwd.h>
32 #include <sys/select.h>
33 #include <sys/stat.h>
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
36 #include <sys/wait.h>
37 #include <sys/socket.h>
38 #include <sys/un.h>
39 #if defined(__linux__) || defined(__CYGWIN__)
40 # include <pty.h>
41 #elif defined(__FreeBSD__) || defined(__DragonFly__)
42 # include <libutil.h>
43 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
44 # include <util.h>
45 #endif
47 #if defined CTRL && defined _AIX
48 #undef CTRL
49 #endif
50 #ifndef CTRL
51 #define CTRL(k) ((k) & 0x1F)
52 #endif
54 #include "config.h"
56 #if defined(_AIX)
57 # include "forkpty-aix.c"
58 #elif defined(__sun)
59 # include "forkpty-sunos.c"
60 #endif
62 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
64 enum PacketType {
65 MSG_CONTENT = 0,
66 MSG_ATTACH = 1,
67 MSG_DETACH = 2,
68 MSG_RESIZE = 3,
69 MSG_REDRAW = 4,
70 MSG_EXIT = 5,
73 typedef struct {
74 unsigned int type;
75 size_t len;
76 union {
77 char msg[BUFSIZ];
78 struct winsize ws;
79 int i;
80 bool b;
81 } u;
82 } Packet;
84 typedef struct Client Client;
85 struct Client {
86 int socket;
87 enum {
88 STATE_CONNECTED,
89 STATE_ATTACHED,
90 STATE_DETACHED,
91 STATE_DISCONNECTED,
92 } state;
93 bool need_resize;
94 bool readonly;
95 Client *next;
98 typedef struct {
99 Client *clients;
100 int socket;
101 Packet pty_output;
102 int pty;
103 int exit_status;
104 struct termios term;
105 struct winsize winsize;
106 pid_t pid;
107 volatile sig_atomic_t running;
108 const char *name;
109 const char *session_name;
110 char host[255];
111 bool read_pty;
112 } Server;
114 static Server server = { .running = true, .exit_status = -1, .host = "@localhost" };
115 static Client client;
116 static struct termios orig_term, cur_term;
117 static bool has_term, alternate_buffer;
119 static struct sockaddr_un sockaddr = {
120 .sun_family = AF_UNIX,
123 static bool set_socket_name(struct sockaddr_un *sockaddr, const char *name);
124 static void die(const char *s);
125 static void info(const char *str, ...);
127 #include "debug.c"
129 static inline size_t packet_header_size() {
130 return offsetof(Packet, u);
133 static size_t packet_size(Packet *pkt) {
134 return packet_header_size() + pkt->len;
137 static ssize_t write_all(int fd, const char *buf, size_t len) {
138 debug("write_all(%d)\n", len);
139 ssize_t ret = len;
140 while (len > 0) {
141 ssize_t res = write(fd, buf, len);
142 if (res < 0) {
143 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
144 continue;
145 return -1;
147 if (res == 0)
148 return ret - len;
149 buf += res;
150 len -= res;
152 return ret;
155 static ssize_t read_all(int fd, char *buf, size_t len) {
156 debug("read_all(%d)\n", len);
157 ssize_t ret = len;
158 while (len > 0) {
159 ssize_t res = read(fd, buf, len);
160 if (res < 0) {
161 if (errno == EWOULDBLOCK)
162 return ret - len;
163 if (errno == EAGAIN || errno == EINTR)
164 continue;
165 return -1;
167 if (res == 0)
168 return ret - len;
169 buf += res;
170 len -= res;
172 return ret;
175 static bool send_packet(int socket, Packet *pkt) {
176 size_t size = packet_size(pkt);
177 if (size > sizeof(*pkt))
178 return false;
179 return write_all(socket, (char *)pkt, size) == size;
182 static bool recv_packet(int socket, Packet *pkt) {
183 ssize_t len = read_all(socket, (char*)pkt, packet_header_size());
184 if (len <= 0 || len != packet_header_size())
185 return false;
186 if (pkt->len > sizeof(pkt->u.msg)) {
187 pkt->len = 0;
188 return false;
190 if (pkt->len > 0) {
191 len = read_all(socket, pkt->u.msg, pkt->len);
192 if (len <= 0 || len != pkt->len)
193 return false;
195 return true;
198 #include "client.c"
199 #include "server.c"
201 static void info(const char *str, ...) {
202 va_list ap;
203 va_start(ap, str);
204 if (str) {
205 fprintf(stderr, "%s: %s: ", server.name, server.session_name);
206 vfprintf(stderr, str, ap);
207 fprintf(stderr, "\r\n");
209 fflush(stderr);
210 va_end(ap);
213 static void die(const char *s) {
214 perror(s);
215 exit(EXIT_FAILURE);
218 static void usage(void) {
219 fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-r] [-f] [-e detachkey] name command\n");
220 exit(EXIT_FAILURE);
223 static bool xsnprintf(char *buf, size_t size, const char *fmt, ...) {
224 va_list ap;
225 if (size > INT_MAX)
226 return false;
227 va_start(ap, fmt);
228 int n = vsnprintf(buf, size, fmt, ap);
229 va_end(ap);
230 if (n == -1)
231 return false;
232 if (n >= size) {
233 errno = ENAMETOOLONG;
234 return false;
236 return true;
239 static bool create_socket_dir(struct sockaddr_un *sockaddr) {
240 sockaddr->sun_path[0] = '\0';
241 uid_t uid = getuid();
242 size_t maxlen = sizeof(sockaddr->sun_path);
243 char *dirs[] = { getenv("HOME"), getenv("TMPDIR"), "/tmp" };
244 int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
245 if (socketfd == -1)
246 return false;
247 struct passwd *pw = getpwuid(uid);
248 if ((!dirs[0] || !dirs[0][0]) && pw)
249 dirs[0] = pw->pw_dir;
251 for (unsigned int i = 0; i < countof(dirs); i++) {
252 char *dir = dirs[i];
253 struct stat sb;
254 bool ishome = (i == 0);
255 if (!dir)
256 continue;
257 if (!xsnprintf(sockaddr->sun_path, maxlen, "%s/%s%s/", dir, ishome ? "." : "", server.name))
258 continue;
259 mode_t mask = umask(0);
260 int r = mkdir(sockaddr->sun_path, ishome ? S_IRWXU : S_IRWXU|S_IRWXG|S_IRWXO|S_ISVTX);
261 umask(mask);
262 if (r != 0 && errno != EEXIST)
263 continue;
264 if (lstat(sockaddr->sun_path, &sb) != 0)
265 continue;
266 if (!S_ISDIR(sb.st_mode)) {
267 errno = ENOTDIR;
268 continue;
271 size_t dirlen = strlen(sockaddr->sun_path);
272 if (!ishome) {
273 if (pw && !xsnprintf(sockaddr->sun_path+dirlen, maxlen-dirlen, "%s/", pw->pw_name))
274 continue;
275 if (!pw && !xsnprintf(sockaddr->sun_path+dirlen, maxlen-dirlen, "%d/", uid))
276 continue;
277 if (mkdir(sockaddr->sun_path, S_IRWXU) != 0 && errno != EEXIST)
278 continue;
279 if (lstat(sockaddr->sun_path, &sb) != 0)
280 continue;
281 if (!S_ISDIR(sb.st_mode)) {
282 errno = ENOTDIR;
283 continue;
285 dirlen = strlen(sockaddr->sun_path);
288 if (sb.st_uid != uid || sb.st_mode & (S_IRWXG|S_IRWXO)) {
289 errno = EACCES;
290 continue;
293 if (!xsnprintf(sockaddr->sun_path+dirlen, maxlen-dirlen, ".abduco-%d", getpid()))
294 continue;
296 socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr->sun_path) + 1;
297 if (bind(socketfd, (struct sockaddr*)sockaddr, socklen) == -1)
298 continue;
299 unlink(sockaddr->sun_path);
300 close(socketfd);
301 sockaddr->sun_path[dirlen] = '\0';
302 return true;
305 close(socketfd);
306 return false;
309 static bool set_socket_name(struct sockaddr_un *sockaddr, const char *name) {
310 size_t maxlen = sizeof(sockaddr->sun_path);
311 if (name[0] == '/') {
312 if (strlen(name) >= maxlen) {
313 errno = ENAMETOOLONG;
314 return false;
316 strncpy(sockaddr->sun_path, name, maxlen);
317 } else if (name[0] == '.' && (name[1] == '.' || name[1] == '/')) {
318 char buf[maxlen], *cwd = getcwd(buf, sizeof buf);
319 if (!cwd)
320 return false;
321 if (!xsnprintf(sockaddr->sun_path, maxlen, "%s/%s", cwd, name))
322 return false;
323 } else {
324 if (!create_socket_dir(sockaddr))
325 return false;
326 if (strlen(sockaddr->sun_path) + strlen(name) + strlen(server.host) >= maxlen) {
327 errno = ENAMETOOLONG;
328 return false;
330 strncat(sockaddr->sun_path, name, maxlen - strlen(sockaddr->sun_path) - 1);
331 strncat(sockaddr->sun_path, server.host, maxlen - strlen(sockaddr->sun_path) - 1);
333 return true;
336 static bool create_session(const char *name, char * const argv[]) {
337 /* this uses the well known double fork strategy as described in section 1.7 of
339 * http://www.faqs.org/faqs/unix-faq/programmer/faq/
341 * pipes are used for synchronization and error reporting i.e. the child sets
342 * the close on exec flag before calling execvp(3) the parent blocks on a read(2)
343 * in case of failure the error message is written to the pipe, success is
344 * indicated by EOF on the pipe.
346 int client_pipe[2], server_pipe[2];
347 pid_t pid;
348 char errormsg[255];
349 struct sigaction sa;
351 if (pipe(client_pipe) == -1)
352 return false;
353 if ((server.socket = server_create_socket(name)) == -1)
354 return false;
356 switch ((pid = fork())) {
357 case 0: /* child process */
358 setsid();
359 close(client_pipe[0]);
360 switch ((pid = fork())) {
361 case 0: /* child process */
362 if (pipe(server_pipe) == -1) {
363 snprintf(errormsg, sizeof(errormsg), "server-pipe: %s\n", strerror(errno));
364 write_all(client_pipe[1], errormsg, strlen(errormsg));
365 close(client_pipe[1]);
366 _exit(EXIT_FAILURE);
368 sa.sa_flags = 0;
369 sigemptyset(&sa.sa_mask);
370 sa.sa_handler = server_pty_died_handler;
371 sigaction(SIGCHLD, &sa, NULL);
372 switch (server.pid = forkpty(&server.pty, NULL, has_term ? &server.term : NULL, &server.winsize)) {
373 case 0: /* child = user application process */
374 close(server.socket);
375 close(server_pipe[0]);
376 if (fcntl(client_pipe[1], F_SETFD, FD_CLOEXEC) == 0 &&
377 fcntl(server_pipe[1], F_SETFD, FD_CLOEXEC) == 0)
378 execvp(argv[0], argv);
379 snprintf(errormsg, sizeof(errormsg), "server-execvp: %s: %s\n",
380 argv[0], strerror(errno));
381 write_all(client_pipe[1], errormsg, strlen(errormsg));
382 write_all(server_pipe[1], errormsg, strlen(errormsg));
383 close(client_pipe[1]);
384 close(server_pipe[1]);
385 _exit(EXIT_FAILURE);
386 break;
387 case -1: /* forkpty failed */
388 snprintf(errormsg, sizeof(errormsg), "server-forkpty: %s\n", strerror(errno));
389 write_all(client_pipe[1], errormsg, strlen(errormsg));
390 close(client_pipe[1]);
391 close(server_pipe[0]);
392 close(server_pipe[1]);
393 _exit(EXIT_FAILURE);
394 break;
395 default: /* parent = server process */
396 sa.sa_handler = server_sigterm_handler;
397 sigaction(SIGTERM, &sa, NULL);
398 sigaction(SIGINT, &sa, NULL);
399 sa.sa_handler = server_sigusr1_handler;
400 sigaction(SIGUSR1, &sa, NULL);
401 sa.sa_handler = SIG_IGN;
402 sigaction(SIGPIPE, &sa, NULL);
403 sigaction(SIGHUP, &sa, NULL);
404 chdir("/");
405 #ifdef NDEBUG
406 int fd = open("/dev/null", O_RDWR);
407 if (fd != -1) {
408 dup2(fd, 0);
409 dup2(fd, 1);
410 dup2(fd, 2);
411 close(fd);
413 #endif /* NDEBUG */
414 close(client_pipe[1]);
415 close(server_pipe[1]);
416 if (read_all(server_pipe[0], errormsg, sizeof(errormsg)) > 0)
417 _exit(EXIT_FAILURE);
418 close(server_pipe[0]);
419 server_mainloop();
420 break;
422 break;
423 case -1: /* fork failed */
424 snprintf(errormsg, sizeof(errormsg), "server-fork: %s\n", strerror(errno));
425 write_all(client_pipe[1], errormsg, strlen(errormsg));
426 close(client_pipe[1]);
427 _exit(EXIT_FAILURE);
428 break;
429 default: /* parent = intermediate process */
430 close(client_pipe[1]);
431 _exit(EXIT_SUCCESS);
432 break;
434 break;
435 case -1: /* fork failed */
436 close(client_pipe[0]);
437 close(client_pipe[1]);
438 return false;
439 default: /* parent = client process */
440 close(client_pipe[1]);
441 int status;
442 wait(&status); /* wait for first fork */
443 ssize_t len = read_all(client_pipe[0], errormsg, sizeof(errormsg));
444 if (len > 0) {
445 write_all(STDERR_FILENO, errormsg, len);
446 unlink(sockaddr.sun_path);
447 exit(EXIT_FAILURE);
449 close(client_pipe[0]);
451 return true;
454 static bool attach_session(const char *name, const bool terminate) {
455 if (server.socket > 0)
456 close(server.socket);
457 if (!set_socket_name(&sockaddr, name) || (server.socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
458 return false;
459 socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
460 if (connect(server.socket, (struct sockaddr*)&sockaddr, socklen) == -1)
461 return false;
462 if (server_set_socket_non_blocking(server.socket) == -1)
463 return false;
465 struct sigaction sa;
466 sa.sa_flags = 0;
467 sigemptyset(&sa.sa_mask);
468 sa.sa_handler = client_sigwinch_handler;
469 sigaction(SIGWINCH, &sa, NULL);
470 sa.sa_handler = SIG_IGN;
471 sigaction(SIGPIPE, &sa, NULL);
473 client_setup_terminal();
474 int status = client_mainloop();
475 client_restore_terminal();
476 if (status == -1) {
477 info("detached");
478 } else if (status == -EIO) {
479 info("exited due to I/O errors");
480 } else {
481 info("session terminated with exit status %d", status);
482 if (terminate)
483 exit(status);
486 return terminate;
489 static bool session_exists(const char *name) {
490 struct stat sb;
491 if (!set_socket_name(&sockaddr, name))
492 return false;
493 return stat(sockaddr.sun_path, &sb) == 0 && S_ISSOCK(sb.st_mode);
496 static bool session_alive(const char *name) {
497 struct stat sb;
498 if (!set_socket_name(&sockaddr, name))
499 return false;
500 return stat(sockaddr.sun_path, &sb) == 0 && S_ISSOCK(sb.st_mode) &&
501 (sb.st_mode & S_IXGRP) == 0;
504 static int session_filter(const struct dirent *d) {
505 return strstr(d->d_name, server.host) != NULL;
508 static int session_comparator(const struct dirent **a, const struct dirent **b) {
509 struct stat sa, sb;
510 if (stat((*a)->d_name, &sa) != 0)
511 return -1;
512 if (stat((*b)->d_name, &sb) != 0)
513 return 1;
514 return sa.st_atime < sb.st_atime ? -1 : 1;
517 static int list_session(void) {
518 if (!create_socket_dir(&sockaddr))
519 return 1;
520 chdir(sockaddr.sun_path);
521 struct dirent **namelist;
522 int n = scandir(sockaddr.sun_path, &namelist, session_filter, session_comparator);
523 if (n < 0)
524 return 1;
525 printf("Active sessions (on host %s)\n", server.host+1);
526 while (n--) {
527 struct stat sb; char buf[255];
528 if (stat(namelist[n]->d_name, &sb) == 0 && S_ISSOCK(sb.st_mode)) {
529 strftime(buf, sizeof(buf), "%a%t %F %T", localtime(&sb.st_atime));
530 char status = ' ';
531 if (sb.st_mode & S_IXUSR)
532 status = '*';
533 else if (sb.st_mode & S_IXGRP)
534 status = '+';
535 char *name = strstr(namelist[n]->d_name, server.host);
536 if (name)
537 *name = '\0';
538 printf("%c %s\t%s\n", status, buf, namelist[n]->d_name);
540 free(namelist[n]);
542 free(namelist);
543 return 0;
546 int main(int argc, char *argv[]) {
547 bool force = false;
548 char **cmd = NULL, action = '\0';
549 server.name = basename(argv[0]);
550 gethostname(server.host+1, sizeof(server.host) - 1);
551 if (argc == 1)
552 exit(list_session());
553 for (int arg = 1; arg < argc; arg++) {
554 if (argv[arg][0] != '-') {
555 if (!server.session_name) {
556 server.session_name = argv[arg];
557 continue;
558 } else if (!cmd) {
559 cmd = &argv[arg];
560 break;
563 if (server.session_name)
564 usage();
565 switch (argv[arg][1]) {
566 case 'a':
567 case 'A':
568 case 'c':
569 case 'n':
570 action = argv[arg][1];
571 break;
572 case 'e':
573 if (arg + 1 >= argc)
574 usage();
575 char *esc = argv[++arg];
576 if (esc[0] == '^' && esc[1])
577 *esc = CTRL(esc[1]);
578 KEY_DETACH = *esc;
579 break;
580 case 'f':
581 force = true;
582 break;
583 case 'r':
584 client.readonly = true;
585 break;
586 case 'v':
587 puts("abduco-"VERSION" © 2013-2015 Marc André Tanner");
588 exit(EXIT_SUCCESS);
589 default:
590 usage();
594 if (!cmd) {
595 cmd = (char*[]){ getenv("ABDUCO_CMD"), NULL };
596 if (!cmd[0])
597 cmd[0] = "dvtm";
600 if (!action || !server.session_name)
601 usage();
603 if (tcgetattr(STDIN_FILENO, &orig_term) != -1) {
604 server.term = orig_term;
605 has_term = true;
608 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &server.winsize) == -1) {
609 server.winsize.ws_col = 80;
610 server.winsize.ws_row = 25;
613 server.read_pty = (action == 'n');
615 redo:
616 switch (action) {
617 case 'n':
618 case 'c':
619 if (force) {
620 if (session_alive(server.session_name)) {
621 info("session exists and has not yet terminated");
622 return 1;
624 if (session_exists(server.session_name))
625 attach_session(server.session_name, false);
627 if (!create_session(server.session_name, cmd))
628 die("create-session");
629 if (action == 'n')
630 break;
631 case 'a':
632 if (!attach_session(server.session_name, true))
633 die("attach-session");
634 break;
635 case 'A':
636 if (session_alive(server.session_name) && !attach_session(server.session_name, true))
637 die("attach-session");
638 if (!attach_session(server.session_name, !force)) {
639 force = false;
640 action = 'c';
641 goto redo;
643 break;
646 return 0;