Recreate server socket upon receiving SIGUSR1
[abduco.git] / abduco.c
blob012c1c31b432bf47f24dbf7c37bd264d7493f6aa
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 #ifdef _AIX
56 # include "forkpty-aix.c"
57 #endif
58 #define CLIENT_TIMEOUT 100
59 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
61 enum PacketType {
62 MSG_CONTENT = 0,
63 MSG_ATTACH = 1,
64 MSG_DETACH = 2,
65 MSG_RESIZE = 3,
66 MSG_REDRAW = 4,
67 MSG_EXIT = 5,
70 typedef struct {
71 unsigned int type;
72 size_t len;
73 union {
74 char msg[BUFSIZ];
75 struct winsize ws;
76 int i;
77 } u;
78 } Packet;
80 typedef struct {
81 Packet pkt;
82 size_t off;
83 } ClientPacketState;
85 typedef struct {
86 Packet *pkt;
87 size_t off;
88 } ServerPacketState;
90 typedef struct Client Client;
91 struct Client {
92 ServerPacketState output; /* display output as received from server */
93 ClientPacketState input; /* input as sent to the server */
94 int socket;
95 enum {
96 STATE_CONNECTED,
97 STATE_ATTACHED,
98 STATE_DETACHED,
99 STATE_DISCONNECTED,
100 } state;
101 time_t last_activity;
102 bool need_resize;
103 Client *next;
106 typedef struct {
107 Client *clients;
108 int client_count;
109 int socket;
110 Packet pty_output;
111 ClientPacketState pty_input;
112 Packet queue[10];
113 unsigned int queue_count;
114 unsigned int queue_insert;
115 unsigned int queue_remove;
116 int pty;
117 int exit_status;
118 struct termios term;
119 pid_t pid;
120 volatile sig_atomic_t running;
121 const char *name;
122 const char *session_name;
123 } Server;
125 static Server server = { .running = true, .exit_status = -1 };
126 static struct termios orig_term, cur_term;
127 bool has_term;
129 static struct sockaddr_un sockaddr = {
130 .sun_family = AF_UNIX,
133 static int create_socket(const char *name);
134 static void die(const char *s);
135 static void info(const char *str, ...);
137 static inline size_t packet_header_size() {
138 return offsetof(Packet, u);
141 static size_t packet_size(Packet *pkt) {
142 return packet_header_size() + pkt->len;
145 static bool is_client_packet_complete(ClientPacketState *pkt) {
146 return pkt->off >= packet_header_size() && pkt->off == packet_size(&pkt->pkt);
149 static bool is_server_packet_complete(ServerPacketState *pkt) {
150 return pkt->pkt && pkt->off == packet_size(pkt->pkt);
153 static bool is_server_packet_nonempty(ServerPacketState *pkt) {
154 return pkt->pkt && pkt->pkt->len > 0;
157 #include "debug.c"
158 #include "client.c"
159 #include "server.c"
161 static void info(const char *str, ...) {
162 va_list ap;
163 va_start(ap, str);
164 fprintf(stdout, "\e[999H\r\n");
165 if (str) {
166 fprintf(stdout, "%s: ", server.name);
167 vfprintf(stdout, str, ap);
168 fprintf(stdout, "\r\n");
170 fflush(stdout);
171 va_end(ap);
174 static void die(const char *s) {
175 perror(s);
176 exit(EXIT_FAILURE);
179 static void usage() {
180 fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-e detachkey] name command\n");
181 exit(EXIT_FAILURE);
184 static int create_socket_dir() {
185 size_t maxlen = sizeof(sockaddr.sun_path);
186 char *dir = getenv("HOME");
187 if (!dir)
188 dir = "/tmp";
189 int len = snprintf(sockaddr.sun_path, maxlen, "%s/.%s/", dir, server.name);
190 if (len < 0 || (size_t)len >= maxlen)
191 return -1;
192 if (mkdir(sockaddr.sun_path, 0750) == -1 && errno != EEXIST)
193 return -1;
194 return len;
197 static int create_socket(const char *name) {
198 size_t maxlen = sizeof(sockaddr.sun_path);
199 if (name[0] == '.' || name[0] == '/') {
200 strncpy(sockaddr.sun_path, name, maxlen);
201 if (sockaddr.sun_path[maxlen-1])
202 return -1;
203 } else {
204 int len = create_socket_dir(), rem = strlen(name);
205 if (len == -1 || maxlen - len - rem <= 0)
206 return -1;
207 strncat(sockaddr.sun_path, name, maxlen - len - 1);
209 return socket(AF_LOCAL, SOCK_STREAM, 0);
212 static bool create_session(const char *name, char * const argv[]) {
213 int pipefds[2];
214 pid_t pid;
215 char errormsg[255];
216 struct sigaction sa;
218 if (pipe(pipefds) == -1)
219 return false;
220 if ((server.socket = server_create_socket(name)) == -1)
221 return false;
223 switch ((pid = fork())) {
224 case 0: /* child process */
225 setsid();
226 close(pipefds[0]);
227 switch ((pid = fork())) {
228 case 0: /* child process */
229 sa.sa_flags = 0;
230 sigemptyset(&sa.sa_mask);
231 sa.sa_handler = server_pty_died_handler;
232 sigaction(SIGCHLD, &sa, NULL);
233 switch (server.pid = forkpty(&server.pty, NULL, has_term ? &server.term : NULL, NULL)) {
234 case 0: /* child process */
235 fcntl(pipefds[1], F_SETFD, FD_CLOEXEC);
236 close(server.socket);
237 execvp(argv[0], argv);
238 snprintf(errormsg, sizeof(errormsg), "server-execvp: %s\n", strerror(errno));
239 write_all(pipefds[1], errormsg, strlen(errormsg));
240 close(pipefds[1]);
241 exit(EXIT_FAILURE);
242 break;
243 case -1:
244 die("server-forkpty");
245 break;
246 default:
247 /* SIGTTIN, SIGTTU */
248 sa.sa_handler = server_sigterm_handler;
249 sigaction(SIGTERM, &sa, NULL);
250 sigaction(SIGINT, &sa, NULL);
251 sa.sa_handler = server_sigusr1_handler;
252 sigaction(SIGUSR1, &sa, NULL);
253 sa.sa_handler = SIG_IGN;
254 sigaction(SIGPIPE, &sa, NULL);
255 sigaction(SIGHUP, &sa, NULL);
256 chdir("/");
257 #ifdef NDEBUG
258 int fd = open("/dev/null", O_RDWR);
259 dup2(fd, 0);
260 dup2(fd, 1);
261 dup2(fd, 2);
262 #endif /* NDEBUG */
263 close(pipefds[1]);
264 server_mainloop();
265 break;
267 break;
268 default:
269 close(pipefds[1]);
270 exit(EXIT_SUCCESS);
271 break;
273 break;
274 case -1: /* fork failed */
275 return false;
276 default: /* parent */
277 close(pipefds[1]);
278 int status;
279 wait(&status); /* wait for first fork */
280 if ((status = read_all(pipefds[0], errormsg, sizeof(errormsg))) > 0) {
281 write_all(STDERR_FILENO, errormsg, status);
282 unlink(sockaddr.sun_path);
283 exit(EXIT_FAILURE);
285 close(pipefds[0]);
287 return true;
290 static bool attach_session(const char *name) {
291 if (server.socket > 0)
292 close(server.socket);
293 if ((server.socket = create_socket(name)) == -1)
294 return false;
295 socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
296 if (connect(server.socket, (struct sockaddr*)&sockaddr, socklen) == -1)
297 return false;
298 if (server_set_socket_non_blocking(server.socket) == -1)
299 return false;
301 struct sigaction sa;
302 sa.sa_flags = 0;
303 sigemptyset(&sa.sa_mask);
304 sa.sa_handler = client_sigwinch_handler;
305 sigaction(SIGWINCH, &sa, NULL);
306 sa.sa_handler = SIG_IGN;
307 sigaction(SIGPIPE, &sa, NULL);
308 atexit(client_restore_terminal);
310 cur_term = orig_term;
311 cur_term.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF);
312 cur_term.c_oflag &= ~(OPOST);
313 cur_term.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
314 cur_term.c_cflag &= ~(CSIZE|PARENB);
315 cur_term.c_cflag |= CS8;
316 cur_term.c_cc[VLNEXT] = _POSIX_VDISABLE;
317 cur_term.c_cc[VMIN] = 1;
318 cur_term.c_cc[VTIME] = 0;
319 tcsetattr(0, TCSADRAIN, &cur_term);
321 client_clear_screen();
322 int status = client_mainloop();
323 if (status == -1) {
324 info("detached");
325 } else if (status == -EIO) {
326 info("exited due to I/O errors");
327 } else {
328 info("session terminated with exit status %d", status);
329 exit(status);
332 return true;
335 static int session_filter(const struct dirent *d) {
336 return d->d_name[0] != '.';
339 static int session_comparator(const struct dirent **a, const struct dirent **b) {
340 struct stat sa, sb;
341 if (stat((*a)->d_name, &sa) != 0)
342 return -1;
343 if (stat((*b)->d_name, &sb) != 0)
344 return 1;
345 return sa.st_atime < sb.st_atime ? -1 : 1;
348 static int list_session() {
349 if (create_socket_dir() == -1)
350 return 1;
351 chdir(sockaddr.sun_path);
352 struct dirent **namelist;
353 int n = scandir(sockaddr.sun_path, &namelist, session_filter, session_comparator);
354 if (n < 0)
355 return 1;
356 puts("Active sessions");
357 while (n--) {
358 struct stat sb; char buf[255];
359 if (stat(namelist[n]->d_name, &sb) == 0) {
360 strftime(buf, sizeof(buf), "%a%t %d.%m.%Y %T", localtime(&sb.st_atime));
361 printf(" %s\t%s\n", buf, namelist[n]->d_name);
363 free(namelist[n]);
365 free(namelist);
366 return 0;
369 int main(int argc, char *argv[]) {
370 char **cmd = NULL, action = '\0';
371 server.name = basename(argv[0]);
372 if (argc == 1)
373 exit(list_session());
374 for (int arg = 1; arg < argc; arg++) {
375 if (argv[arg][0] != '-') {
376 if (!server.session_name) {
377 server.session_name = argv[arg];
378 continue;
379 } else if (!cmd) {
380 cmd = &argv[arg];
381 break;
384 if (server.session_name)
385 usage();
386 switch (argv[arg][1]) {
387 case 'a':
388 case 'A':
389 case 'c':
390 case 'n':
391 action = argv[arg][1];
392 break;
393 case 'e':
394 if (arg + 1 >= argc)
395 usage();
396 char *esc = argv[++arg];
397 if (esc[0] == '^' && esc[1])
398 *esc = CTRL(esc[1]);
399 KEY_DETACH = *esc;
400 break;
401 case 'v':
402 puts("abduco-"VERSION" © 2013-2014 Marc André Tanner");
403 exit(EXIT_SUCCESS);
404 default:
405 usage();
409 if (!action || !server.session_name || (action != 'a' && !cmd))
410 usage();
412 if (tcgetattr(STDIN_FILENO, &orig_term) != -1) {
413 server.term = orig_term;
414 has_term = true;
417 switch (action) {
418 redo:
419 case 'n':
420 case 'c':
421 if (!create_session(server.session_name, cmd))
422 die("create-session");
423 if (action == 'n')
424 break;
425 case 'a':
426 case 'A':
427 if (!attach_session(server.session_name)) {
428 if (action == 'A') {
429 action = 'c';
430 goto redo;
432 die("attach-session");
436 return 0;