Do not display hostname suffix in session listing
[abduco.git] / abduco.c
blobef74e03823929ff292f35f2304788a077a5434bb
1 /*
2 * Copyright (c) 2013-2014 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 #define _GNU_SOURCE
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <stdio.h>
20 #include <stdarg.h>
21 #include <stdlib.h>
22 #include <stdbool.h>
23 #include <stddef.h>
24 #include <signal.h>
25 #include <libgen.h>
26 #include <string.h>
27 #include <limits.h>
28 #include <dirent.h>
29 #include <termios.h>
30 #include <time.h>
31 #include <unistd.h>
32 #include <sys/stat.h>
33 #include <sys/ioctl.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/socket.h>
37 #include <sys/un.h>
38 #if defined(__linux__) || defined(__CYGWIN__)
39 # include <pty.h>
40 #elif defined(__FreeBSD__)
41 # include <libutil.h>
42 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
43 # include <util.h>
44 #endif
46 #if defined CTRL && defined _AIX
47 #undef CTRL
48 #endif
49 #ifndef CTRL
50 #define CTRL(k) ((k) & 0x1F)
51 #endif
53 #include "config.h"
55 #if defined(_AIX)
56 # include "forkpty-aix.c"
57 #elif defined(__sun)
58 # include "forkpty-sunos.c"
59 #endif
61 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
63 enum PacketType {
64 MSG_CONTENT = 0,
65 MSG_ATTACH = 1,
66 MSG_DETACH = 2,
67 MSG_RESIZE = 3,
68 MSG_REDRAW = 4,
69 MSG_EXIT = 5,
72 typedef struct {
73 unsigned int type;
74 size_t len;
75 union {
76 char msg[BUFSIZ];
77 struct winsize ws;
78 int i;
79 bool b;
80 } u;
81 } Packet;
83 typedef struct Client Client;
84 struct Client {
85 int socket;
86 enum {
87 STATE_CONNECTED,
88 STATE_ATTACHED,
89 STATE_DETACHED,
90 STATE_DISCONNECTED,
91 } state;
92 bool need_resize;
93 bool readonly;
94 Client *next;
97 typedef struct {
98 Client *clients;
99 int socket;
100 Packet pty_output;
101 int pty;
102 int exit_status;
103 struct termios term;
104 struct winsize winsize;
105 pid_t pid;
106 volatile sig_atomic_t running;
107 const char *name;
108 const char *session_name;
109 char host[255];
110 bool read_pty;
111 } Server;
113 static Server server = { .running = true, .exit_status = -1, .host = "@localhost" };
114 static Client client;
115 static struct termios orig_term, cur_term;
116 bool has_term;
118 static struct sockaddr_un sockaddr = {
119 .sun_family = AF_UNIX,
122 static int create_socket(const char *name);
123 static void die(const char *s);
124 static void info(const char *str, ...);
126 #include "debug.c"
128 static inline size_t packet_header_size() {
129 return offsetof(Packet, u);
132 static size_t packet_size(Packet *pkt) {
133 return packet_header_size() + pkt->len;
136 static ssize_t write_all(int fd, const char *buf, size_t len) {
137 debug("write_all(%d)\n", len);
138 ssize_t ret = len;
139 while (len > 0) {
140 ssize_t res = write(fd, buf, len);
141 if (res < 0) {
142 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
143 continue;
144 return -1;
146 if (res == 0)
147 return ret - len;
148 buf += res;
149 len -= res;
151 return ret;
154 static ssize_t read_all(int fd, char *buf, size_t len) {
155 debug("read_all(%d)\n", len);
156 ssize_t ret = len;
157 while (len > 0) {
158 ssize_t res = read(fd, buf, len);
159 if (res < 0) {
160 if (errno == EWOULDBLOCK)
161 return ret - len;
162 if (errno == EAGAIN || errno == EINTR)
163 continue;
164 return -1;
166 if (res == 0)
167 return ret - len;
168 buf += res;
169 len -= res;
171 return ret;
174 static bool send_packet(int socket, Packet *pkt) {
175 size_t size = packet_size(pkt);
176 return write_all(socket, (char *)pkt, size) == size;
179 static bool recv_packet(int socket, Packet *pkt) {
180 ssize_t len = read_all(socket, (char*)pkt, packet_header_size());
181 if (len <= 0 || len != packet_header_size())
182 return false;
183 if (pkt->len > 0) {
184 len = read_all(socket, pkt->u.msg, pkt->len);
185 if (len <= 0 || len != pkt->len)
186 return false;
188 return true;
191 #include "client.c"
192 #include "server.c"
194 static void info(const char *str, ...) {
195 va_list ap;
196 va_start(ap, str);
197 fprintf(stderr, "\033[999H");
198 if (str) {
199 fprintf(stderr, "%s: %s: ", server.name, server.session_name);
200 vfprintf(stderr, str, ap);
201 fprintf(stderr, "\r\n");
203 fflush(stderr);
204 va_end(ap);
207 static void die(const char *s) {
208 perror(s);
209 exit(EXIT_FAILURE);
212 static void usage() {
213 fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-r] [-e detachkey] name command\n");
214 exit(EXIT_FAILURE);
217 static int create_socket_dir() {
218 size_t maxlen = sizeof(sockaddr.sun_path);
219 char *dirs[] = { getenv("HOME"), getenv("TMPDIR"), "/tmp" };
220 int socketfd = socket(AF_LOCAL, SOCK_STREAM, 0);
221 if (socketfd == -1)
222 return -1;
223 for (unsigned int i = 0; i < countof(dirs); i++) {
224 char *dir = dirs[i];
225 if (!dir)
226 continue;
227 int len = snprintf(sockaddr.sun_path, maxlen, "%s/.%s/", dir, server.name);
228 if (len < 0 || (size_t)len >= maxlen)
229 continue;
230 if (mkdir(sockaddr.sun_path, 0750) == -1 && errno != EEXIST)
231 continue;
232 int len2 = snprintf(sockaddr.sun_path, maxlen, "%s/.%s/.abduco-%d", dir, server.name, getpid());
233 if (len2 < 0 || (size_t)len2 >= maxlen)
234 continue;
235 socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
236 if (bind(socketfd, (struct sockaddr*)&sockaddr, socklen) == -1)
237 continue;
238 unlink(sockaddr.sun_path);
239 close(socketfd);
240 sockaddr.sun_path[len] = '\0';
241 return len;
244 close(socketfd);
245 return -1;
248 static int create_socket(const char *name) {
249 size_t maxlen = sizeof(sockaddr.sun_path);
250 if (name[0] == '/') {
251 strncpy(sockaddr.sun_path, name, maxlen);
252 if (sockaddr.sun_path[maxlen-1])
253 return -1;
254 } else if (name[0] == '.' && (name[1] == '.' || name[1] == '/')) {
255 char buf[maxlen], *cwd = getcwd(buf, sizeof buf);
256 if (!cwd)
257 return -1;
258 int len = snprintf(sockaddr.sun_path, maxlen, "%s/%s", cwd, name);
259 if (len < 0 || (size_t)len >= maxlen)
260 return -1;
261 } else {
262 int dir_len = create_socket_dir();
263 if (dir_len == -1 || dir_len + strlen(name) + strlen(server.host) >= maxlen)
264 return -1;
265 strncat(sockaddr.sun_path, name, maxlen - strlen(sockaddr.sun_path) - 1);
266 strncat(sockaddr.sun_path, server.host, maxlen - strlen(sockaddr.sun_path) - 1);
268 return socket(AF_LOCAL, SOCK_STREAM, 0);
271 static bool create_session(const char *name, char * const argv[]) {
272 /* this uses the well known double fork strategy as described in section 1.7 of
274 * http://www.faqs.org/faqs/unix-faq/programmer/faq/
276 * pipes are used for synchronization and error reporting i.e. the child sets
277 * the close on exec flag before calling execvp(3) the parent blocks on a read(2)
278 * in case of failure the error message is written to the pipe, success is
279 * indicated by EOF on the pipe.
281 int client_pipe[2], server_pipe[2];
282 pid_t pid;
283 char errormsg[255];
284 struct sigaction sa;
286 if (pipe(client_pipe) == -1)
287 return false;
288 if ((server.socket = server_create_socket(name)) == -1)
289 return false;
291 switch ((pid = fork())) {
292 case 0: /* child process */
293 setsid();
294 close(client_pipe[0]);
295 switch ((pid = fork())) {
296 case 0: /* child process */
297 if (pipe(server_pipe) == -1) {
298 snprintf(errormsg, sizeof(errormsg), "server-pipe: %s\n", strerror(errno));
299 write_all(client_pipe[1], errormsg, strlen(errormsg));
300 close(client_pipe[1]);
301 _exit(EXIT_FAILURE);
303 sa.sa_flags = 0;
304 sigemptyset(&sa.sa_mask);
305 sa.sa_handler = server_pty_died_handler;
306 sigaction(SIGCHLD, &sa, NULL);
307 switch (server.pid = forkpty(&server.pty, NULL, has_term ? &server.term : NULL, &server.winsize)) {
308 case 0: /* child = user application process */
309 close(server.socket);
310 close(server_pipe[0]);
311 fcntl(client_pipe[1], F_SETFD, FD_CLOEXEC);
312 fcntl(server_pipe[1], F_SETFD, FD_CLOEXEC);
313 execvp(argv[0], argv);
314 snprintf(errormsg, sizeof(errormsg), "server-execvp: %s\n", strerror(errno));
315 write_all(client_pipe[1], errormsg, strlen(errormsg));
316 write_all(server_pipe[1], errormsg, strlen(errormsg));
317 close(client_pipe[1]);
318 close(server_pipe[1]);
319 _exit(EXIT_FAILURE);
320 break;
321 case -1: /* forkpty failed */
322 snprintf(errormsg, sizeof(errormsg), "server-forkpty: %s\n", strerror(errno));
323 write_all(client_pipe[1], errormsg, strlen(errormsg));
324 close(client_pipe[1]);
325 close(server_pipe[0]);
326 close(server_pipe[1]);
327 _exit(EXIT_FAILURE);
328 break;
329 default: /* parent = server process */
330 sa.sa_handler = server_sigterm_handler;
331 sigaction(SIGTERM, &sa, NULL);
332 sigaction(SIGINT, &sa, NULL);
333 sa.sa_handler = server_sigusr1_handler;
334 sigaction(SIGUSR1, &sa, NULL);
335 sa.sa_handler = SIG_IGN;
336 sigaction(SIGPIPE, &sa, NULL);
337 sigaction(SIGHUP, &sa, NULL);
338 chdir("/");
339 #ifdef NDEBUG
340 int fd = open("/dev/null", O_RDWR);
341 dup2(fd, 0);
342 dup2(fd, 1);
343 dup2(fd, 2);
344 #endif /* NDEBUG */
345 close(client_pipe[1]);
346 close(server_pipe[1]);
347 if (read_all(server_pipe[0], errormsg, sizeof(errormsg)) > 0)
348 _exit(EXIT_FAILURE);
349 close(server_pipe[0]);
350 server_mainloop();
351 break;
353 break;
354 case -1: /* fork failed */
355 snprintf(errormsg, sizeof(errormsg), "server-fork: %s\n", strerror(errno));
356 write_all(client_pipe[1], errormsg, strlen(errormsg));
357 close(client_pipe[1]);
358 _exit(EXIT_FAILURE);
359 break;
360 default: /* parent = intermediate process */
361 close(client_pipe[1]);
362 _exit(EXIT_SUCCESS);
363 break;
365 break;
366 case -1: /* fork failed */
367 close(client_pipe[0]);
368 close(client_pipe[1]);
369 return false;
370 default: /* parent = client process */
371 close(client_pipe[1]);
372 int status;
373 wait(&status); /* wait for first fork */
374 ssize_t len = read_all(client_pipe[0], errormsg, sizeof(errormsg));
375 if (len > 0) {
376 write_all(STDERR_FILENO, errormsg, len);
377 unlink(sockaddr.sun_path);
378 exit(EXIT_FAILURE);
380 close(client_pipe[0]);
382 return true;
385 static bool attach_session(const char *name) {
386 if (server.socket > 0)
387 close(server.socket);
388 if ((server.socket = create_socket(name)) == -1)
389 return false;
390 socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
391 if (connect(server.socket, (struct sockaddr*)&sockaddr, socklen) == -1)
392 return false;
393 if (server_set_socket_non_blocking(server.socket) == -1)
394 return false;
396 struct sigaction sa;
397 sa.sa_flags = 0;
398 sigemptyset(&sa.sa_mask);
399 sa.sa_handler = client_sigwinch_handler;
400 sigaction(SIGWINCH, &sa, NULL);
401 sa.sa_handler = SIG_IGN;
402 sigaction(SIGPIPE, &sa, NULL);
403 atexit(client_restore_terminal);
405 cur_term = orig_term;
406 cur_term.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF);
407 cur_term.c_oflag &= ~(OPOST);
408 cur_term.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
409 cur_term.c_cflag &= ~(CSIZE|PARENB);
410 cur_term.c_cflag |= CS8;
411 cur_term.c_cc[VLNEXT] = _POSIX_VDISABLE;
412 cur_term.c_cc[VMIN] = 1;
413 cur_term.c_cc[VTIME] = 0;
414 tcsetattr(STDIN_FILENO, TCSADRAIN, &cur_term);
416 int status = client_mainloop();
417 client_restore_terminal();
418 if (status == -1) {
419 info("detached");
420 } else if (status == -EIO) {
421 info("exited due to I/O errors");
422 } else {
423 info("session terminated with exit status %d", status);
424 exit(status);
427 return true;
430 static int session_filter(const struct dirent *d) {
431 return strstr(d->d_name, server.host) != NULL;
434 static int session_comparator(const struct dirent **a, const struct dirent **b) {
435 struct stat sa, sb;
436 if (stat((*a)->d_name, &sa) != 0)
437 return -1;
438 if (stat((*b)->d_name, &sb) != 0)
439 return 1;
440 return sa.st_atime < sb.st_atime ? -1 : 1;
443 static int list_session() {
444 if (create_socket_dir() == -1)
445 return 1;
446 chdir(sockaddr.sun_path);
447 struct dirent **namelist;
448 int n = scandir(sockaddr.sun_path, &namelist, session_filter, session_comparator);
449 if (n < 0)
450 return 1;
451 printf("Active sessions (on host %s)\n", server.host+1);
452 while (n--) {
453 struct stat sb; char buf[255];
454 if (stat(namelist[n]->d_name, &sb) == 0 && S_ISSOCK(sb.st_mode)) {
455 strftime(buf, sizeof(buf), "%a%t %F %T", localtime(&sb.st_atime));
456 char status = ' ';
457 if (sb.st_mode & S_IXUSR)
458 status = '*';
459 else if (sb.st_mode & S_IXGRP)
460 status = '+';
461 char *name = strstr(namelist[n]->d_name, server.host);
462 if (name)
463 *name = '\0';
464 printf("%c %s\t%s\n", status, buf, namelist[n]->d_name);
466 free(namelist[n]);
468 free(namelist);
469 return 0;
472 int main(int argc, char *argv[]) {
473 char **cmd = NULL, action = '\0';
474 server.name = basename(argv[0]);
475 gethostname(server.host+1, sizeof(server.host) - 1);
476 if (argc == 1)
477 exit(list_session());
478 for (int arg = 1; arg < argc; arg++) {
479 if (argv[arg][0] != '-') {
480 if (!server.session_name) {
481 server.session_name = argv[arg];
482 continue;
483 } else if (!cmd) {
484 cmd = &argv[arg];
485 break;
488 if (server.session_name)
489 usage();
490 switch (argv[arg][1]) {
491 case 'a':
492 case 'A':
493 case 'c':
494 case 'n':
495 action = argv[arg][1];
496 break;
497 case 'e':
498 if (arg + 1 >= argc)
499 usage();
500 char *esc = argv[++arg];
501 if (esc[0] == '^' && esc[1])
502 *esc = CTRL(esc[1]);
503 KEY_DETACH = *esc;
504 break;
505 case 'r':
506 client.readonly = true;
507 break;
508 case 'v':
509 puts("abduco-"VERSION" © 2013-2014 Marc André Tanner");
510 exit(EXIT_SUCCESS);
511 default:
512 usage();
516 if (!cmd) {
517 cmd = (char*[]){ getenv("ABDUCO_CMD"), NULL };
518 if (!cmd[0])
519 cmd[0] = "dvtm";
522 if (!action || !server.session_name || ((action == 'c' || action == 'A') && client.readonly))
523 usage();
525 if (tcgetattr(STDIN_FILENO, &orig_term) != -1) {
526 server.term = orig_term;
527 has_term = true;
530 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &server.winsize) == -1) {
531 server.winsize.ws_col = 80;
532 server.winsize.ws_row = 25;
535 server.read_pty = (action == 'n');
537 switch (action) {
538 redo:
539 case 'n':
540 case 'c':
541 if (!create_session(server.session_name, cmd))
542 die("create-session");
543 if (action == 'n')
544 break;
545 case 'a':
546 case 'A':
547 if (!attach_session(server.session_name)) {
548 if (action == 'A') {
549 action = 'c';
550 goto redo;
552 die("attach-session");
556 return 0;