Simplify Solaris detection
[abduco.git] / abduco.c
blobb2c6dc383355ced4d9000d2d8b9242fab0203daa
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 bool read_pty;
110 } Server;
112 static Server server = { .running = true, .exit_status = -1 };
113 static Client client;
114 static struct termios orig_term, cur_term;
115 bool has_term;
117 static struct sockaddr_un sockaddr = {
118 .sun_family = AF_UNIX,
121 static int create_socket(const char *name);
122 static void die(const char *s);
123 static void info(const char *str, ...);
125 #include "debug.c"
127 static inline size_t packet_header_size() {
128 return offsetof(Packet, u);
131 static size_t packet_size(Packet *pkt) {
132 return packet_header_size() + pkt->len;
135 static ssize_t write_all(int fd, const char *buf, size_t len) {
136 debug("write_all(%d)\n", len);
137 ssize_t ret = len;
138 while (len > 0) {
139 ssize_t res = write(fd, buf, len);
140 if (res < 0) {
141 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
142 continue;
143 return -1;
145 if (res == 0)
146 return ret - len;
147 buf += res;
148 len -= res;
150 return ret;
153 static ssize_t read_all(int fd, char *buf, size_t len) {
154 debug("read_all(%d)\n", len);
155 ssize_t ret = len;
156 while (len > 0) {
157 ssize_t res = read(fd, buf, len);
158 if (res < 0) {
159 if (errno == EWOULDBLOCK)
160 return ret - len;
161 if (errno == EAGAIN || errno == EINTR)
162 continue;
163 return -1;
165 if (res == 0)
166 return ret - len;
167 buf += res;
168 len -= res;
170 return ret;
173 static bool send_packet(int socket, Packet *pkt) {
174 size_t size = packet_size(pkt);
175 return write_all(socket, (char *)pkt, size) == size;
178 static bool recv_packet(int socket, Packet *pkt) {
179 ssize_t len = read_all(socket, (char*)pkt, packet_header_size());
180 if (len <= 0 || len != packet_header_size())
181 return false;
182 if (pkt->len > 0) {
183 len = read_all(socket, pkt->u.msg, pkt->len);
184 if (len <= 0 || len != pkt->len)
185 return false;
187 return true;
190 #include "client.c"
191 #include "server.c"
193 static void info(const char *str, ...) {
194 va_list ap;
195 va_start(ap, str);
196 fprintf(stderr, "\033[999H");
197 if (str) {
198 fprintf(stderr, "%s: %s: ", server.name, server.session_name);
199 vfprintf(stderr, str, ap);
200 fprintf(stderr, "\r\n");
202 fflush(stderr);
203 va_end(ap);
206 static void die(const char *s) {
207 perror(s);
208 exit(EXIT_FAILURE);
211 static void usage() {
212 fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-r] [-e detachkey] name command\n");
213 exit(EXIT_FAILURE);
216 static int create_socket_dir() {
217 size_t maxlen = sizeof(sockaddr.sun_path);
218 char *dirs[] = { getenv("HOME"), getenv("TMPDIR"), "/tmp" };
219 int socketfd = socket(AF_LOCAL, SOCK_STREAM, 0);
220 if (socketfd == -1)
221 return -1;
222 for (unsigned int i = 0; i < countof(dirs); i++) {
223 char *dir = dirs[i];
224 if (!dir)
225 continue;
226 int len = snprintf(sockaddr.sun_path, maxlen, "%s/.%s/", dir, server.name);
227 if (len < 0 || (size_t)len >= maxlen)
228 continue;
229 if (mkdir(sockaddr.sun_path, 0750) == -1 && errno != EEXIST)
230 continue;
231 int len2 = snprintf(sockaddr.sun_path, maxlen, "%s/.%s/.abduco-%d", dir, server.name, getpid());
232 if (len2 < 0 || (size_t)len2 >= maxlen)
233 continue;
234 socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
235 if (bind(socketfd, (struct sockaddr*)&sockaddr, socklen) == -1)
236 continue;
237 unlink(sockaddr.sun_path);
238 close(socketfd);
239 sockaddr.sun_path[len] = '\0';
240 return len;
243 close(socketfd);
244 return -1;
247 static int create_socket(const char *name) {
248 size_t maxlen = sizeof(sockaddr.sun_path);
249 if (name[0] == '/') {
250 strncpy(sockaddr.sun_path, name, maxlen);
251 if (sockaddr.sun_path[maxlen-1])
252 return -1;
253 } else if (name[0] == '.' && (name[1] == '.' || name[1] == '/')) {
254 char buf[maxlen], *cwd = getcwd(buf, sizeof buf);
255 if (!cwd)
256 return -1;
257 int len = snprintf(sockaddr.sun_path, maxlen, "%s/%s", cwd, name);
258 if (len < 0 || (size_t)len >= maxlen)
259 return -1;
260 } else {
261 int len = create_socket_dir(), rem = strlen(name);
262 if (len == -1 || maxlen - len - rem <= 0)
263 return -1;
264 strncat(sockaddr.sun_path, name, maxlen - len - 1);
266 return socket(AF_LOCAL, SOCK_STREAM, 0);
269 static bool create_session(const char *name, char * const argv[]) {
270 /* this uses the well known double fork strategy as described in section 1.7 of
272 * http://www.faqs.org/faqs/unix-faq/programmer/faq/
274 * pipes are used for synchronization and error reporting i.e. the child sets
275 * the close on exec flag before calling execvp(3) the parent blocks on a read(2)
276 * in case of failure the error message is written to the pipe, success is
277 * indicated by EOF on the pipe.
279 int client_pipe[2], server_pipe[2];
280 pid_t pid;
281 char errormsg[255];
282 struct sigaction sa;
284 if (pipe(client_pipe) == -1)
285 return false;
286 if ((server.socket = server_create_socket(name)) == -1)
287 return false;
289 switch ((pid = fork())) {
290 case 0: /* child process */
291 setsid();
292 close(client_pipe[0]);
293 switch ((pid = fork())) {
294 case 0: /* child process */
295 if (pipe(server_pipe) == -1) {
296 snprintf(errormsg, sizeof(errormsg), "server-pipe: %s\n", strerror(errno));
297 write_all(client_pipe[1], errormsg, strlen(errormsg));
298 close(client_pipe[1]);
299 _exit(EXIT_FAILURE);
301 sa.sa_flags = 0;
302 sigemptyset(&sa.sa_mask);
303 sa.sa_handler = server_pty_died_handler;
304 sigaction(SIGCHLD, &sa, NULL);
305 switch (server.pid = forkpty(&server.pty, NULL, has_term ? &server.term : NULL, &server.winsize)) {
306 case 0: /* child = user application process */
307 close(server.socket);
308 close(server_pipe[0]);
309 fcntl(client_pipe[1], F_SETFD, FD_CLOEXEC);
310 fcntl(server_pipe[1], F_SETFD, FD_CLOEXEC);
311 execvp(argv[0], argv);
312 snprintf(errormsg, sizeof(errormsg), "server-execvp: %s\n", strerror(errno));
313 write_all(client_pipe[1], errormsg, strlen(errormsg));
314 write_all(server_pipe[1], errormsg, strlen(errormsg));
315 close(client_pipe[1]);
316 close(server_pipe[1]);
317 _exit(EXIT_FAILURE);
318 break;
319 case -1: /* forkpty failed */
320 snprintf(errormsg, sizeof(errormsg), "server-forkpty: %s\n", strerror(errno));
321 write_all(client_pipe[1], errormsg, strlen(errormsg));
322 close(client_pipe[1]);
323 close(server_pipe[0]);
324 close(server_pipe[1]);
325 _exit(EXIT_FAILURE);
326 break;
327 default: /* parent = server process */
328 sa.sa_handler = server_sigterm_handler;
329 sigaction(SIGTERM, &sa, NULL);
330 sigaction(SIGINT, &sa, NULL);
331 sa.sa_handler = server_sigusr1_handler;
332 sigaction(SIGUSR1, &sa, NULL);
333 sa.sa_handler = SIG_IGN;
334 sigaction(SIGPIPE, &sa, NULL);
335 sigaction(SIGHUP, &sa, NULL);
336 chdir("/");
337 #ifdef NDEBUG
338 int fd = open("/dev/null", O_RDWR);
339 dup2(fd, 0);
340 dup2(fd, 1);
341 dup2(fd, 2);
342 #endif /* NDEBUG */
343 close(client_pipe[1]);
344 close(server_pipe[1]);
345 if (read_all(server_pipe[0], errormsg, sizeof(errormsg)) > 0)
346 _exit(EXIT_FAILURE);
347 close(server_pipe[0]);
348 server_mainloop();
349 break;
351 break;
352 case -1: /* fork failed */
353 snprintf(errormsg, sizeof(errormsg), "server-fork: %s\n", strerror(errno));
354 write_all(client_pipe[1], errormsg, strlen(errormsg));
355 close(client_pipe[1]);
356 _exit(EXIT_FAILURE);
357 break;
358 default: /* parent = intermediate process */
359 close(client_pipe[1]);
360 _exit(EXIT_SUCCESS);
361 break;
363 break;
364 case -1: /* fork failed */
365 close(client_pipe[0]);
366 close(client_pipe[1]);
367 return false;
368 default: /* parent = client process */
369 close(client_pipe[1]);
370 int status;
371 wait(&status); /* wait for first fork */
372 ssize_t len = read_all(client_pipe[0], errormsg, sizeof(errormsg));
373 if (len > 0) {
374 write_all(STDERR_FILENO, errormsg, len);
375 unlink(sockaddr.sun_path);
376 exit(EXIT_FAILURE);
378 close(client_pipe[0]);
380 return true;
383 static bool attach_session(const char *name) {
384 if (server.socket > 0)
385 close(server.socket);
386 if ((server.socket = create_socket(name)) == -1)
387 return false;
388 socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
389 if (connect(server.socket, (struct sockaddr*)&sockaddr, socklen) == -1)
390 return false;
391 if (server_set_socket_non_blocking(server.socket) == -1)
392 return false;
394 struct sigaction sa;
395 sa.sa_flags = 0;
396 sigemptyset(&sa.sa_mask);
397 sa.sa_handler = client_sigwinch_handler;
398 sigaction(SIGWINCH, &sa, NULL);
399 sa.sa_handler = SIG_IGN;
400 sigaction(SIGPIPE, &sa, NULL);
401 atexit(client_restore_terminal);
403 cur_term = orig_term;
404 cur_term.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF);
405 cur_term.c_oflag &= ~(OPOST);
406 cur_term.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
407 cur_term.c_cflag &= ~(CSIZE|PARENB);
408 cur_term.c_cflag |= CS8;
409 cur_term.c_cc[VLNEXT] = _POSIX_VDISABLE;
410 cur_term.c_cc[VMIN] = 1;
411 cur_term.c_cc[VTIME] = 0;
412 tcsetattr(STDIN_FILENO, TCSADRAIN, &cur_term);
414 int status = client_mainloop();
415 client_restore_terminal();
416 if (status == -1) {
417 info("detached");
418 } else if (status == -EIO) {
419 info("exited due to I/O errors");
420 } else {
421 info("session terminated with exit status %d", status);
422 exit(status);
425 return true;
428 static int session_filter(const struct dirent *d) {
429 return d->d_name[0] != '.';
432 static int session_comparator(const struct dirent **a, const struct dirent **b) {
433 struct stat sa, sb;
434 if (stat((*a)->d_name, &sa) != 0)
435 return -1;
436 if (stat((*b)->d_name, &sb) != 0)
437 return 1;
438 return sa.st_atime < sb.st_atime ? -1 : 1;
441 static int list_session() {
442 if (create_socket_dir() == -1)
443 return 1;
444 chdir(sockaddr.sun_path);
445 struct dirent **namelist;
446 int n = scandir(sockaddr.sun_path, &namelist, session_filter, session_comparator);
447 if (n < 0)
448 return 1;
449 puts("Active sessions");
450 while (n--) {
451 struct stat sb; char buf[255];
452 if (stat(namelist[n]->d_name, &sb) == 0 && S_ISSOCK(sb.st_mode)) {
453 strftime(buf, sizeof(buf), "%a%t %F %T", localtime(&sb.st_atime));
454 char status = ' ';
455 if (sb.st_mode & S_IXUSR)
456 status = '*';
457 else if (sb.st_mode & S_IXGRP)
458 status = '+';
459 printf("%c %s\t%s\n", status, buf, namelist[n]->d_name);
461 free(namelist[n]);
463 free(namelist);
464 return 0;
467 int main(int argc, char *argv[]) {
468 char **cmd = NULL, action = '\0';
469 server.name = basename(argv[0]);
470 if (argc == 1)
471 exit(list_session());
472 for (int arg = 1; arg < argc; arg++) {
473 if (argv[arg][0] != '-') {
474 if (!server.session_name) {
475 server.session_name = argv[arg];
476 continue;
477 } else if (!cmd) {
478 cmd = &argv[arg];
479 break;
482 if (server.session_name)
483 usage();
484 switch (argv[arg][1]) {
485 case 'a':
486 case 'A':
487 case 'c':
488 case 'n':
489 action = argv[arg][1];
490 break;
491 case 'e':
492 if (arg + 1 >= argc)
493 usage();
494 char *esc = argv[++arg];
495 if (esc[0] == '^' && esc[1])
496 *esc = CTRL(esc[1]);
497 KEY_DETACH = *esc;
498 break;
499 case 'r':
500 client.readonly = true;
501 break;
502 case 'v':
503 puts("abduco-"VERSION" © 2013-2014 Marc André Tanner");
504 exit(EXIT_SUCCESS);
505 default:
506 usage();
510 if (!cmd) {
511 cmd = (char*[]){ getenv("ABDUCO_CMD"), NULL };
512 if (!cmd[0])
513 cmd[0] = "dvtm";
516 if (!action || !server.session_name || ((action == 'c' || action == 'A') && client.readonly))
517 usage();
519 if (tcgetattr(STDIN_FILENO, &orig_term) != -1) {
520 server.term = orig_term;
521 has_term = true;
524 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &server.winsize) == -1) {
525 server.winsize.ws_col = 80;
526 server.winsize.ws_row = 25;
529 server.read_pty = (action == 'n');
531 switch (action) {
532 redo:
533 case 'n':
534 case 'c':
535 if (!create_session(server.session_name, cmd))
536 die("create-session");
537 if (action == 'n')
538 break;
539 case 'a':
540 case 'A':
541 if (!attach_session(server.session_name)) {
542 if (action == 'A') {
543 action = 'c';
544 goto redo;
546 die("attach-session");
550 return 0;