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.
33 #include <sys/ioctl.h>
34 #include <sys/types.h>
36 #include <sys/socket.h>
38 #if defined(__linux__) || defined(__CYGWIN__)
40 #elif defined(__FreeBSD__)
42 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
46 #if defined CTRL && defined _AIX
50 #define CTRL(k) ((k) & 0x1F)
56 # include "forkpty-aix.c"
58 # include "forkpty-sunos.c"
61 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
83 typedef struct Client Client
;
104 struct winsize winsize
;
106 volatile sig_atomic_t running
;
108 const char *session_name
;
112 static Server server
= { .running
= true, .exit_status
= -1 };
113 static Client client
;
114 static struct termios orig_term
, cur_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
, ...);
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
);
139 ssize_t res
= write(fd
, buf
, len
);
141 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
153 static ssize_t
read_all(int fd
, char *buf
, size_t len
) {
154 debug("read_all(%d)\n", len
);
157 ssize_t res
= read(fd
, buf
, len
);
159 if (errno
== EWOULDBLOCK
)
161 if (errno
== EAGAIN
|| errno
== EINTR
)
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())
183 len
= read_all(socket
, pkt
->u
.msg
, pkt
->len
);
184 if (len
<= 0 || len
!= pkt
->len
)
193 static void info(const char *str
, ...) {
196 fprintf(stderr
, "\033[999H");
198 fprintf(stderr
, "%s: %s: ", server
.name
, server
.session_name
);
199 vfprintf(stderr
, str
, ap
);
200 fprintf(stderr
, "\r\n");
206 static void die(const char *s
) {
211 static void usage() {
212 fprintf(stderr
, "usage: abduco [-a|-A|-c|-n] [-r] [-e detachkey] name command\n");
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);
222 for (unsigned int i
= 0; i
< countof(dirs
); i
++) {
226 int len
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/.%s/", dir
, server
.name
);
227 if (len
< 0 || (size_t)len
>= maxlen
)
229 if (mkdir(sockaddr
.sun_path
, 0750) == -1 && errno
!= EEXIST
)
231 int len2
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/.%s/.abduco-%d", dir
, server
.name
, getpid());
232 if (len2
< 0 || (size_t)len2
>= maxlen
)
234 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
235 if (bind(socketfd
, (struct sockaddr
*)&sockaddr
, socklen
) == -1)
237 unlink(sockaddr
.sun_path
);
239 sockaddr
.sun_path
[len
] = '\0';
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])
253 } else if (name
[0] == '.' && (name
[1] == '.' || name
[1] == '/')) {
254 char buf
[maxlen
], *cwd
= getcwd(buf
, sizeof buf
);
257 int len
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/%s", cwd
, name
);
258 if (len
< 0 || (size_t)len
>= maxlen
)
261 int len
= create_socket_dir(), rem
= strlen(name
);
262 if (len
== -1 || maxlen
- len
- rem
<= 0)
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];
284 if (pipe(client_pipe
) == -1)
286 if ((server
.socket
= server_create_socket(name
)) == -1)
289 switch ((pid
= fork())) {
290 case 0: /* child process */
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]);
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]);
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]);
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
);
338 int fd
= open("/dev/null", O_RDWR
);
343 close(client_pipe
[1]);
344 close(server_pipe
[1]);
345 if (read_all(server_pipe
[0], errormsg
, sizeof(errormsg
)) > 0)
347 close(server_pipe
[0]);
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]);
358 default: /* parent = intermediate process */
359 close(client_pipe
[1]);
364 case -1: /* fork failed */
365 close(client_pipe
[0]);
366 close(client_pipe
[1]);
368 default: /* parent = client process */
369 close(client_pipe
[1]);
371 wait(&status
); /* wait for first fork */
372 ssize_t len
= read_all(client_pipe
[0], errormsg
, sizeof(errormsg
));
374 write_all(STDERR_FILENO
, errormsg
, len
);
375 unlink(sockaddr
.sun_path
);
378 close(client_pipe
[0]);
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)
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)
391 if (server_set_socket_non_blocking(server
.socket
) == -1)
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();
418 } else if (status
== -EIO
) {
419 info("exited due to I/O errors");
421 info("session terminated with exit status %d", status
);
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
) {
434 if (stat((*a
)->d_name
, &sa
) != 0)
436 if (stat((*b
)->d_name
, &sb
) != 0)
438 return sa
.st_atime
< sb
.st_atime
? -1 : 1;
441 static int list_session() {
442 if (create_socket_dir() == -1)
444 chdir(sockaddr
.sun_path
);
445 struct dirent
**namelist
;
446 int n
= scandir(sockaddr
.sun_path
, &namelist
, session_filter
, session_comparator
);
449 puts("Active sessions");
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
));
455 if (sb
.st_mode
& S_IXUSR
)
457 else if (sb
.st_mode
& S_IXGRP
)
459 printf("%c %s\t%s\n", status
, buf
, namelist
[n
]->d_name
);
467 int main(int argc
, char *argv
[]) {
468 char **cmd
= NULL
, action
= '\0';
469 server
.name
= basename(argv
[0]);
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
];
482 if (server
.session_name
)
484 switch (argv
[arg
][1]) {
489 action
= argv
[arg
][1];
494 char *esc
= argv
[++arg
];
495 if (esc
[0] == '^' && esc
[1])
500 client
.readonly
= true;
503 puts("abduco-"VERSION
" © 2013-2014 Marc André Tanner");
511 cmd
= (char*[]){ getenv("ABDUCO_CMD"), NULL
};
516 if (!action
|| !server
.session_name
|| ((action
== 'c' || action
== 'A') && client
.readonly
))
519 if (tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
520 server
.term
= orig_term
;
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');
535 if (!create_session(server
.session_name
, cmd
))
536 die("create-session");
541 if (!attach_session(server
.session_name
)) {
546 die("attach-session");