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.
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
37 #include <sys/socket.h>
39 #if defined(__linux__) || defined(__CYGWIN__)
41 #elif defined(__FreeBSD__)
43 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
47 #if defined CTRL && defined _AIX
51 #define CTRL(k) ((k) & 0x1F)
57 # include "forkpty-aix.c"
60 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
82 typedef struct Client Client
;
104 volatile sig_atomic_t running
;
106 const char *session_name
;
109 static Server server
= { .running
= true, .exit_status
= -1 };
110 static Client client
;
111 static struct termios orig_term
, cur_term
;
114 static struct sockaddr_un sockaddr
= {
115 .sun_family
= AF_UNIX
,
118 static int create_socket(const char *name
);
119 static void die(const char *s
);
120 static void info(const char *str
, ...);
124 static inline size_t packet_header_size() {
125 return offsetof(Packet
, u
);
128 static size_t packet_size(Packet
*pkt
) {
129 return packet_header_size() + pkt
->len
;
132 static ssize_t
write_all(int fd
, const char *buf
, size_t len
) {
133 debug("write_all(%d)\n", len
);
136 ssize_t res
= write(fd
, buf
, len
);
138 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
150 static ssize_t
read_all(int fd
, char *buf
, size_t len
) {
151 debug("read_all(%d)\n", len
);
154 ssize_t res
= read(fd
, buf
, len
);
156 if (errno
== EWOULDBLOCK
)
158 if (errno
== EAGAIN
|| errno
== EINTR
)
170 static bool send_packet(int socket
, Packet
*pkt
) {
171 size_t size
= packet_size(pkt
);
172 return write_all(socket
, (char *)pkt
, size
) == size
;
175 static bool recv_packet(int socket
, Packet
*pkt
) {
176 ssize_t len
= read_all(socket
, (char*)pkt
, packet_header_size());
177 if (len
<= 0 || len
!= packet_header_size())
180 len
= read_all(socket
, pkt
->u
.msg
, pkt
->len
);
181 if (len
<= 0 || len
!= pkt
->len
)
190 static void info(const char *str
, ...) {
193 fprintf(stderr
, "\e[999H");
195 fprintf(stderr
, "%s: %s: ", server
.name
, server
.session_name
);
196 vfprintf(stderr
, str
, ap
);
197 fprintf(stderr
, "\r\n");
203 static void die(const char *s
) {
208 static void usage() {
209 fprintf(stderr
, "usage: abduco [-a|-A|-c|-n] [-r] [-e detachkey] name command\n");
213 static int create_socket_dir() {
214 size_t maxlen
= sizeof(sockaddr
.sun_path
);
215 char *dir
= getenv("HOME");
217 dir
= getenv("TMPDIR");
220 int len
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/.%s/", dir
, server
.name
);
221 if (len
< 0 || (size_t)len
>= maxlen
)
223 if (mkdir(sockaddr
.sun_path
, 0750) == -1 && errno
!= EEXIST
)
228 static int create_socket(const char *name
) {
229 size_t maxlen
= sizeof(sockaddr
.sun_path
);
230 if (name
[0] == '/') {
231 strncpy(sockaddr
.sun_path
, name
, maxlen
);
232 if (sockaddr
.sun_path
[maxlen
-1])
234 } else if (name
[0] == '.') {
235 char buf
[maxlen
], *cwd
= getcwd(buf
, sizeof buf
);
238 int len
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/%s", cwd
, name
);
239 if (len
< 0 || (size_t)len
>= maxlen
)
242 int len
= create_socket_dir(), rem
= strlen(name
);
243 if (len
== -1 || maxlen
- len
- rem
<= 0)
245 strncat(sockaddr
.sun_path
, name
, maxlen
- len
- 1);
247 return socket(AF_LOCAL
, SOCK_STREAM
, 0);
250 static bool create_session(const char *name
, char * const argv
[]) {
256 if (pipe(pipefds
) == -1)
258 if ((server
.socket
= server_create_socket(name
)) == -1)
261 switch ((pid
= fork())) {
262 case 0: /* child process */
265 switch ((pid
= fork())) {
266 case 0: /* child process */
268 sigemptyset(&sa
.sa_mask
);
269 sa
.sa_handler
= server_pty_died_handler
;
270 sigaction(SIGCHLD
, &sa
, NULL
);
271 switch (server
.pid
= forkpty(&server
.pty
, NULL
, has_term
? &server
.term
: NULL
, NULL
)) {
272 case 0: /* child process */
273 fcntl(pipefds
[1], F_SETFD
, FD_CLOEXEC
);
274 close(server
.socket
);
275 execvp(argv
[0], argv
);
276 snprintf(errormsg
, sizeof(errormsg
), "server-execvp: %s\n", strerror(errno
));
277 write_all(pipefds
[1], errormsg
, strlen(errormsg
));
282 die("server-forkpty");
285 /* SIGTTIN, SIGTTU */
286 sa
.sa_handler
= server_sigterm_handler
;
287 sigaction(SIGTERM
, &sa
, NULL
);
288 sigaction(SIGINT
, &sa
, NULL
);
289 sa
.sa_handler
= server_sigusr1_handler
;
290 sigaction(SIGUSR1
, &sa
, NULL
);
291 sa
.sa_handler
= SIG_IGN
;
292 sigaction(SIGPIPE
, &sa
, NULL
);
293 sigaction(SIGHUP
, &sa
, NULL
);
296 int fd
= open("/dev/null", O_RDWR
);
302 struct pollfd pipe_status
= { .fd
= pipefds
[0], .events
= POLLIN
};
303 if (poll(&pipe_status
, 1, -1) == 1 && pipe_status
.revents
== POLLHUP
)
315 case -1: /* fork failed */
317 default: /* parent */
320 wait(&status
); /* wait for first fork */
321 ssize_t len
= read_all(pipefds
[0], errormsg
, sizeof(errormsg
));
323 write_all(STDERR_FILENO
, errormsg
, len
);
324 unlink(sockaddr
.sun_path
);
332 static bool attach_session(const char *name
) {
333 if (server
.socket
> 0)
334 close(server
.socket
);
335 if ((server
.socket
= create_socket(name
)) == -1)
337 socklen_t socklen
= offsetof(struct sockaddr_un
, sun_path
) + strlen(sockaddr
.sun_path
) + 1;
338 if (connect(server
.socket
, (struct sockaddr
*)&sockaddr
, socklen
) == -1)
340 if (server_set_socket_non_blocking(server
.socket
) == -1)
345 sigemptyset(&sa
.sa_mask
);
346 sa
.sa_handler
= client_sigwinch_handler
;
347 sigaction(SIGWINCH
, &sa
, NULL
);
348 sa
.sa_handler
= SIG_IGN
;
349 sigaction(SIGPIPE
, &sa
, NULL
);
350 atexit(client_restore_terminal
);
352 cur_term
= orig_term
;
353 cur_term
.c_iflag
&= ~(IGNBRK
|BRKINT
|PARMRK
|ISTRIP
|INLCR
|IGNCR
|ICRNL
|IXON
|IXOFF
);
354 cur_term
.c_oflag
&= ~(OPOST
);
355 cur_term
.c_lflag
&= ~(ECHO
|ECHONL
|ICANON
|ISIG
|IEXTEN
);
356 cur_term
.c_cflag
&= ~(CSIZE
|PARENB
);
357 cur_term
.c_cflag
|= CS8
;
358 cur_term
.c_cc
[VLNEXT
] = _POSIX_VDISABLE
;
359 cur_term
.c_cc
[VMIN
] = 1;
360 cur_term
.c_cc
[VTIME
] = 0;
361 tcsetattr(STDIN_FILENO
, TCSADRAIN
, &cur_term
);
363 int status
= client_mainloop();
364 client_restore_terminal();
367 } else if (status
== -EIO
) {
368 info("exited due to I/O errors");
370 info("session terminated with exit status %d", status
);
377 static int session_filter(const struct dirent
*d
) {
378 return d
->d_name
[0] != '.';
381 static int session_comparator(const struct dirent
**a
, const struct dirent
**b
) {
383 if (stat((*a
)->d_name
, &sa
) != 0)
385 if (stat((*b
)->d_name
, &sb
) != 0)
387 return sa
.st_atime
< sb
.st_atime
? -1 : 1;
390 static int list_session() {
391 if (create_socket_dir() == -1)
393 chdir(sockaddr
.sun_path
);
394 struct dirent
**namelist
;
395 int n
= scandir(sockaddr
.sun_path
, &namelist
, session_filter
, session_comparator
);
398 puts("Active sessions");
400 struct stat sb
; char buf
[255];
401 if (stat(namelist
[n
]->d_name
, &sb
) == 0) {
402 strftime(buf
, sizeof(buf
), "%a%t %F %T", localtime(&sb
.st_atime
));
403 printf("%c %s\t%s\n", sb
.st_mode
& S_IXUSR
? '*' : ' ', buf
, namelist
[n
]->d_name
);
411 int main(int argc
, char *argv
[]) {
412 char **cmd
= NULL
, action
= '\0';
413 server
.name
= basename(argv
[0]);
415 exit(list_session());
416 for (int arg
= 1; arg
< argc
; arg
++) {
417 if (argv
[arg
][0] != '-') {
418 if (!server
.session_name
) {
419 server
.session_name
= argv
[arg
];
426 if (server
.session_name
)
428 switch (argv
[arg
][1]) {
433 action
= argv
[arg
][1];
438 char *esc
= argv
[++arg
];
439 if (esc
[0] == '^' && esc
[1])
444 client
.readonly
= true;
447 puts("abduco-"VERSION
" © 2013-2014 Marc André Tanner");
455 cmd
= (char*[]){ getenv("ABDUCO_CMD"), NULL
};
460 if (!action
|| !server
.session_name
|| ((action
== 'c' || action
== 'A') && client
.readonly
))
463 if (tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
464 server
.term
= orig_term
;
472 if (!create_session(server
.session_name
, cmd
))
473 die("create-session");
478 if (!attach_session(server
.session_name
)) {
483 die("attach-session");