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 #define CLIENT_TIMEOUT 100
59 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
90 typedef struct Client Client
;
92 ServerPacketState output
; /* display output as received from server */
93 ClientPacketState input
; /* input as sent to the server */
101 time_t last_activity
;
111 ClientPacketState pty_input
;
113 unsigned int queue_count
;
114 unsigned int queue_insert
;
115 unsigned int queue_remove
;
120 volatile sig_atomic_t running
;
122 const char *session_name
;
125 static Server server
= { .running
= true, .exit_status
= -1 };
126 static struct termios orig_term
, cur_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;
161 static void info(const char *str
, ...) {
164 fprintf(stdout
, "\e[999H\r\n");
166 fprintf(stdout
, "%s: ", server
.name
);
167 vfprintf(stdout
, str
, ap
);
168 fprintf(stdout
, "\r\n");
174 static void die(const char *s
) {
179 static void usage() {
180 fprintf(stderr
, "usage: abduco [-a|-A|-c|-n] [-e detachkey] name command\n");
184 static int create_socket_dir() {
185 size_t maxlen
= sizeof(sockaddr
.sun_path
);
186 char *dir
= getenv("HOME");
189 int len
= snprintf(sockaddr
.sun_path
, maxlen
, "%s/.%s/", dir
, server
.name
);
190 if (len
< 0 || (size_t)len
>= maxlen
)
192 if (mkdir(sockaddr
.sun_path
, 0750) == -1 && errno
!= EEXIST
)
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])
204 int len
= create_socket_dir(), rem
= strlen(name
);
205 if (len
== -1 || maxlen
- len
- rem
<= 0)
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
[]) {
218 if (pipe(pipefds
) == -1)
220 if ((server
.socket
= server_create_socket(name
)) == -1)
223 switch ((pid
= fork())) {
224 case 0: /* child process */
227 switch ((pid
= fork())) {
228 case 0: /* child process */
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
));
244 die("server-forkpty");
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
);
258 int fd
= open("/dev/null", O_RDWR
);
274 case -1: /* fork failed */
276 default: /* parent */
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
);
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)
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)
298 if (server_set_socket_non_blocking(server
.socket
) == -1)
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();
325 } else if (status
== -EIO
) {
326 info("exited due to I/O errors");
328 info("session terminated with exit status %d", status
);
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
) {
341 if (stat((*a
)->d_name
, &sa
) != 0)
343 if (stat((*b
)->d_name
, &sb
) != 0)
345 return sa
.st_atime
< sb
.st_atime
? -1 : 1;
348 static int list_session() {
349 if (create_socket_dir() == -1)
351 chdir(sockaddr
.sun_path
);
352 struct dirent
**namelist
;
353 int n
= scandir(sockaddr
.sun_path
, &namelist
, session_filter
, session_comparator
);
356 puts("Active sessions");
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
);
369 int main(int argc
, char *argv
[]) {
370 char **cmd
= NULL
, action
= '\0';
371 server
.name
= basename(argv
[0]);
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
];
384 if (server
.session_name
)
386 switch (argv
[arg
][1]) {
391 action
= argv
[arg
][1];
396 char *esc
= argv
[++arg
];
397 if (esc
[0] == '^' && esc
[1])
402 puts("abduco-"VERSION
" © 2013-2014 Marc André Tanner");
409 if (!action
|| !server
.session_name
|| (action
!= 'a' && !cmd
))
412 if (tcgetattr(STDIN_FILENO
, &orig_term
) != -1) {
413 server
.term
= orig_term
;
421 if (!create_session(server
.session_name
, cmd
))
422 die("create-session");
427 if (!attach_session(server
.session_name
)) {
432 die("attach-session");