Unlink socket when server is killed
[abduco.git] / abduco.c
blob288071493287c31494c81049ecf0eb4d1f822ad9
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,
69 /* packet sent from client to server */
70 typedef struct {
71 unsigned char type;
72 unsigned char len;
73 union {
74 char msg[sizeof(struct winsize)];
75 struct winsize ws;
76 int i;
77 } u;
78 } ClientPacket;
80 /* packet sent from server to all clients */
81 typedef struct {
82 char buf[BUFSIZ];
83 size_t len;
84 } ServerPacket;
86 typedef struct {
87 ClientPacket pkt;
88 size_t off;
89 } ClientPacketState;
91 typedef struct {
92 ServerPacket *pkt;
93 size_t off;
94 } ServerPacketState;
96 typedef struct Client Client;
97 struct Client {
98 ServerPacketState output; /* display output as received from server */
99 ClientPacketState input; /* input as sent to the server */
100 int socket;
101 enum {
102 STATE_CONNECTED,
103 STATE_ATTACHED,
104 STATE_DETACHED,
105 STATE_DISCONNECTED,
106 } state;
107 time_t last_activity;
108 bool need_resize;
109 Client *next;
112 typedef struct {
113 Client *clients;
114 int client_count;
115 int socket;
116 ServerPacket pty_output;
117 ClientPacketState pty_input;
118 ClientPacket queue[10];
119 unsigned int queue_count;
120 unsigned int queue_insert;
121 unsigned int queue_remove;
122 int pty;
123 int exit_status;
124 struct termios term;
125 pid_t pid;
126 volatile sig_atomic_t running;
127 const char *name;
128 } Server;
130 static Server server = { .running = true, .exit_status = -1 };
131 static struct termios orig_term, cur_term;
132 bool has_term;
134 static struct sockaddr_un sockaddr = {
135 .sun_family = AF_UNIX,
138 static int create_socket(const char *name);
139 static void die(const char *s);
140 static void info(const char *str, ...);
142 static bool is_client_packet_complete(ClientPacketState *pkt) {
143 return pkt->off == sizeof pkt->pkt;
146 static bool is_server_packet_complete(ServerPacketState *pkt) {
147 return pkt->pkt && pkt->off == pkt->pkt->len;
150 static bool is_server_packet_nonempty(ServerPacketState *pkt) {
151 return pkt->pkt && pkt->pkt->len > 0;
154 #include "debug.c"
155 #include "client.c"
156 #include "server.c"
158 static void info(const char *str, ...) {
159 va_list ap;
160 va_start(ap, str);
161 fprintf(stdout, "\e[999H\r\n");
162 if (str) {
163 fprintf(stdout, "%s: ", server.name);
164 vfprintf(stdout, str, ap);
165 fprintf(stdout, "\r\n");
167 fflush(stdout);
168 va_end(ap);
171 static void die(const char *s) {
172 perror(s);
173 exit(EXIT_FAILURE);
176 static void usage() {
177 fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-e detachkey] name command\n");
178 exit(EXIT_FAILURE);
181 static int create_socket_dir() {
182 size_t maxlen = sizeof(sockaddr.sun_path);
183 char *dir = getenv("HOME");
184 if (!dir)
185 dir = "/tmp";
186 int len = snprintf(sockaddr.sun_path, maxlen, "%s/.%s/", dir, server.name);
187 if (len < 0 || (size_t)len >= maxlen)
188 return -1;
189 if (mkdir(sockaddr.sun_path, 0750) == -1 && errno != EEXIST)
190 return -1;
191 return len;
194 static int create_socket(const char *name) {
195 size_t maxlen = sizeof(sockaddr.sun_path);
196 if (name[0] == '.' || name[0] == '/') {
197 strncpy(sockaddr.sun_path, name, maxlen);
198 if (sockaddr.sun_path[maxlen-1])
199 return -1;
200 } else {
201 int len = create_socket_dir(), rem = strlen(name);
202 if (len == -1 || maxlen - len - rem <= 0)
203 return -1;
204 strncat(sockaddr.sun_path, name, maxlen - len - 1);
206 return socket(AF_LOCAL, SOCK_STREAM, 0);
209 static bool create_session(const char *name, char * const argv[]) {
210 int pipefds[2];
211 if (pipe(pipefds) == -1)
212 return false;
213 if ((server.socket = create_socket(name)) == -1)
214 return false;
215 socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
216 mode_t mode = S_IRUSR|S_IWUSR;
217 fchmod(server.socket, mode);
218 if (bind(server.socket, (struct sockaddr*)&sockaddr, socklen) == -1)
219 return false;
220 if (listen(server.socket, 5) == -1)
221 goto error;
222 if (fchmod(server.socket, mode) == -1 && chmod(sockaddr.sun_path, mode) == -1)
223 goto error;
225 pid_t pid;
226 char errormsg[255];
227 struct sigaction sa;
229 switch ((pid = fork())) {
230 case 0: /* child process */
231 setsid();
232 close(pipefds[0]);
233 switch ((pid = fork())) {
234 case 0: /* child process */
235 sa.sa_flags = 0;
236 sigemptyset(&sa.sa_mask);
237 sa.sa_handler = server_pty_died_handler;
238 sigaction(SIGCHLD, &sa, NULL);
239 switch (server.pid = forkpty(&server.pty, NULL, has_term ? &server.term : NULL, NULL)) {
240 case 0: /* child process */
241 fcntl(pipefds[1], F_SETFD, FD_CLOEXEC);
242 close(server.socket);
243 execvp(argv[0], argv);
244 snprintf(errormsg, sizeof(errormsg), "server-execvp: %s\n", strerror(errno));
245 write_all(pipefds[1], errormsg, strlen(errormsg));
246 close(pipefds[1]);
247 exit(EXIT_FAILURE);
248 break;
249 case -1:
250 die("server-forkpty");
251 break;
252 default:
253 /* SIGTTIN, SIGTTU */
254 sa.sa_handler = server_sigterm_handler;
255 sigaction(SIGTERM, &sa, NULL);
256 sigaction(SIGINT, &sa, NULL);
257 sa.sa_handler = SIG_IGN;
258 sigaction(SIGPIPE, &sa, NULL);
259 sigaction(SIGHUP, &sa, NULL);
260 chdir("/");
261 #ifdef NDEBUG
262 int fd = open("/dev/null", O_RDWR);
263 dup2(fd, 0);
264 dup2(fd, 1);
265 dup2(fd, 2);
266 #endif /* NDEBUG */
267 close(pipefds[1]);
268 server_mainloop();
269 break;
271 break;
272 default:
273 close(pipefds[1]);
274 exit(EXIT_SUCCESS);
275 break;
277 break;
278 case -1: /* fork failed */
279 return false;
280 default: /* parent */
281 close(pipefds[1]);
282 int status;
283 wait(&status); /* wait for first fork */
284 if ((status = read_all(pipefds[0], errormsg, sizeof(errormsg))) > 0) {
285 write_all(STDERR_FILENO, errormsg, status);
286 unlink(sockaddr.sun_path);
287 exit(EXIT_FAILURE);
289 close(pipefds[0]);
291 return true;
292 error:
293 unlink(sockaddr.sun_path);
294 return false;
297 static bool attach_session(const char *name) {
298 if (server.socket > 0)
299 close(server.socket);
300 if ((server.socket = create_socket(name)) == -1)
301 return false;
302 socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
303 if (connect(server.socket, (struct sockaddr*)&sockaddr, socklen) == -1)
304 return false;
305 if (server_set_socket_non_blocking(server.socket) == -1)
306 return false;
308 struct sigaction sa;
309 sa.sa_flags = 0;
310 sigemptyset(&sa.sa_mask);
311 sa.sa_handler = client_sigwinch_handler;
312 sigaction(SIGWINCH, &sa, NULL);
313 sa.sa_handler = SIG_IGN;
314 sigaction(SIGPIPE, &sa, NULL);
315 atexit(client_restore_terminal);
317 cur_term = orig_term;
318 cur_term.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF);
319 cur_term.c_oflag &= ~(OPOST);
320 cur_term.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
321 cur_term.c_cflag &= ~(CSIZE|PARENB);
322 cur_term.c_cflag |= CS8;
323 cur_term.c_cc[VLNEXT] = _POSIX_VDISABLE;
324 cur_term.c_cc[VMIN] = 1;
325 cur_term.c_cc[VTIME] = 0;
326 tcsetattr(0, TCSADRAIN, &cur_term);
328 client_clear_screen();
329 switch (client_mainloop()) {
330 case -1:
331 info("detached");
332 break;
333 case EIO:
334 info("exited due to I/O errors: %s", strerror(errno));
335 break;
338 return true;
341 static int session_filter(const struct dirent *d) {
342 return d->d_name[0] != '.';
345 static int session_comparator(const struct dirent **a, const struct dirent **b) {
346 struct stat sa, sb;
347 if (stat((*a)->d_name, &sa) != 0)
348 return -1;
349 if (stat((*b)->d_name, &sb) != 0)
350 return 1;
351 return sa.st_atime < sb.st_atime ? -1 : 1;
354 static int list_session() {
355 if (create_socket_dir() == -1)
356 return 1;
357 chdir(sockaddr.sun_path);
358 struct dirent **namelist;
359 int n = scandir(sockaddr.sun_path, &namelist, session_filter, session_comparator);
360 if (n < 0)
361 return 1;
362 puts("Active sessions");
363 while (n--) {
364 struct stat sb; char buf[255];
365 if (stat(namelist[n]->d_name, &sb) == 0) {
366 strftime(buf, sizeof(buf), "%a%t %d.%m.%Y %T", localtime(&sb.st_atime));
367 printf(" %s\t%s\n", buf, namelist[n]->d_name);
369 free(namelist[n]);
371 free(namelist);
372 return 0;
375 int main(int argc, char *argv[]) {
376 char *session = NULL, **cmd = NULL, action = '\0';
377 server.name = basename(argv[0]);
378 if (argc == 1)
379 exit(list_session());
380 for (int arg = 1; arg < argc; arg++) {
381 if (argv[arg][0] != '-') {
382 if (!session) {
383 session = argv[arg];
384 continue;
385 } else if (!cmd) {
386 cmd = &argv[arg];
387 break;
390 if (session)
391 usage();
392 switch (argv[arg][1]) {
393 case 'a':
394 case 'A':
395 case 'c':
396 case 'n':
397 action = argv[arg][1];
398 break;
399 case 'e':
400 if (arg + 1 >= argc)
401 usage();
402 char *esc = argv[++arg];
403 if (esc[0] == '^' && esc[1])
404 *esc = CTRL(esc[1]);
405 KEY_DETACH = *esc;
406 break;
407 case 'v':
408 puts("abduco-"VERSION" © 2013-2014 Marc André Tanner");
409 exit(EXIT_SUCCESS);
410 default:
411 usage();
415 if (!action || !session || (action != 'a' && !cmd))
416 usage();
418 if (tcgetattr(STDIN_FILENO, &orig_term) != -1) {
419 server.term = orig_term;
420 has_term = true;
423 switch (action) {
424 redo:
425 case 'n':
426 case 'c':
427 if (!create_session(session, cmd))
428 die("create-session");
429 if (action == 'n')
430 break;
431 case 'a':
432 case 'A':
433 if (!attach_session(session)) {
434 if (action == 'A') {
435 action = 'c';
436 goto redo;
438 die("attach-session");
442 return 0;