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
;
113 static Server server
= { .running
= true, .exit_status
= -1, .host
= "@localhost" };
114 static Client client
;
115 static struct termios orig_term
, cur_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
, ...);
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
);
140 ssize_t res
= write(fd
, buf
, len
);
142 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
154 static ssize_t
read_all(int fd
, char *buf
, size_t len
) {
155 debug("read_all(%d)\n", len
);
158 ssize_t res
= read(fd
, buf
, len
);
160 if (errno
== EWOULDBLOCK
)
162 if (errno
== EAGAIN
|| errno
== EINTR
)
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())
184 len
= read_all(socket
, pkt
->u
.msg
, pkt
->len
);
185 if (len
<= 0 || len
!= pkt
->len
)
194 static void info(const char *str
, ...) {
197 fprintf(stderr
, "\033[999H");
199 fprintf(stderr
, "%s: %s: ", server
.name
, server
.session_name
);
200 vfprintf(stderr
, str
, ap
);
201 fprintf(stderr
, "\r\n");
207 static void die(const char *s
) {
212 static void usage() {
213 fprintf(stderr
, "usage: abduco [-a|-A|-c|-n] [-r] [-e detachkey] name command\n");
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);
223 for (unsigned int i
= 0; i
< countof(dirs
); i
++) {
227 int len
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/.%s/", dir
, server
.name
);
228 if (len
< 0 || (size_t)len
>= maxlen
)
230 if (mkdir(sockaddr
.sun_path
, 0750) == -1 && errno
!= EEXIST
)
232 int len2
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/.%s/.abduco-%d", dir
, server
.name
, getpid());
233 if (len2
< 0 || (size_t)len2
>= maxlen
)
235 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
236 if (bind(socketfd
, (struct sockaddr
*)&sockaddr
, socklen
) == -1)
238 unlink(sockaddr
.sun_path
);
240 sockaddr
.sun_path
[len
] = '\0';
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])
254 } else if (name
[0] == '.' && (name
[1] == '.' || name
[1] == '/')) {
255 char buf
[maxlen
], *cwd
= getcwd(buf
, sizeof buf
);
258 int len
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/%s", cwd
, name
);
259 if (len
< 0 || (size_t)len
>= maxlen
)
262 int dir_len
= create_socket_dir();
263 if (dir_len
== -1 || dir_len
+ strlen(name
) + strlen(server
.host
) >= maxlen
)
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];
286 if (pipe(client_pipe
) == -1)
288 if ((server
.socket
= server_create_socket(name
)) == -1)
291 switch ((pid
= fork())) {
292 case 0: /* child process */
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]);
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]);
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]);
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
);
340 int fd
= open("/dev/null", O_RDWR
);
345 close(client_pipe
[1]);
346 close(server_pipe
[1]);
347 if (read_all(server_pipe
[0], errormsg
, sizeof(errormsg
)) > 0)
349 close(server_pipe
[0]);
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]);
360 default: /* parent = intermediate process */
361 close(client_pipe
[1]);
366 case -1: /* fork failed */
367 close(client_pipe
[0]);
368 close(client_pipe
[1]);
370 default: /* parent = client process */
371 close(client_pipe
[1]);
373 wait(&status
); /* wait for first fork */
374 ssize_t len
= read_all(client_pipe
[0], errormsg
, sizeof(errormsg
));
376 write_all(STDERR_FILENO
, errormsg
, len
);
377 unlink(sockaddr
.sun_path
);
380 close(client_pipe
[0]);
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)
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)
393 if (server_set_socket_non_blocking(server
.socket
) == -1)
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();
420 } else if (status
== -EIO
) {
421 info("exited due to I/O errors");
423 info("session terminated with exit status %d", status
);
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
) {
436 if (stat((*a
)->d_name
, &sa
) != 0)
438 if (stat((*b
)->d_name
, &sb
) != 0)
440 return sa
.st_atime
< sb
.st_atime
? -1 : 1;
443 static int list_session() {
444 if (create_socket_dir() == -1)
446 chdir(sockaddr
.sun_path
);
447 struct dirent
**namelist
;
448 int n
= scandir(sockaddr
.sun_path
, &namelist
, session_filter
, session_comparator
);
451 printf("Active sessions (on host %s)\n", server
.host
+1);
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
));
457 if (sb
.st_mode
& S_IXUSR
)
459 else if (sb
.st_mode
& S_IXGRP
)
461 char *name
= strstr(namelist
[n
]->d_name
, server
.host
);
464 printf("%c %s\t%s\n", status
, buf
, namelist
[n
]->d_name
);
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);
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
];
488 if (server
.session_name
)
490 switch (argv
[arg
][1]) {
495 action
= argv
[arg
][1];
500 char *esc
= argv
[++arg
];
501 if (esc
[0] == '^' && esc
[1])
506 client
.readonly
= true;
509 puts("abduco-"VERSION
" © 2013-2014 Marc André Tanner");
517 cmd
= (char*[]){ getenv("ABDUCO_CMD"), NULL
};
522 if (!action
|| !server
.session_name
|| ((action
== 'c' || action
== 'A') && client
.readonly
))
525 if (tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
526 server
.term
= orig_term
;
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');
541 if (!create_session(server
.session_name
, cmd
))
542 die("create-session");
547 if (!attach_session(server
.session_name
)) {
552 die("attach-session");