diff options
Diffstat (limited to 'client.c')
-rw-r--r-- | client.c | 801 |
1 files changed, 801 insertions, 0 deletions
diff --git a/client.c b/client.c new file mode 100644 index 0000000..df6cee9 --- /dev/null +++ b/client.c @@ -0,0 +1,801 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <sys/file.h> + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "tmux.h" + +static struct tmuxproc *client_proc; +static struct tmuxpeer *client_peer; +static uint64_t client_flags; +static int client_suspended; +static enum { + CLIENT_EXIT_NONE, + CLIENT_EXIT_DETACHED, + CLIENT_EXIT_DETACHED_HUP, + CLIENT_EXIT_LOST_TTY, + CLIENT_EXIT_TERMINATED, + CLIENT_EXIT_LOST_SERVER, + CLIENT_EXIT_EXITED, + CLIENT_EXIT_SERVER_EXITED, + CLIENT_EXIT_MESSAGE_PROVIDED +} client_exitreason = CLIENT_EXIT_NONE; +static int client_exitflag; +static int client_exitval; +static enum msgtype client_exittype; +static const char *client_exitsession; +static char *client_exitmessage; +static const char *client_execshell; +static const char *client_execcmd; +static int client_attached; +static struct client_files client_files = RB_INITIALIZER(&client_files); + +static __dead void client_exec(const char *,const char *); +static int client_get_lock(char *); +static int client_connect(struct event_base *, const char *, + uint64_t); +static void client_send_identify(const char *, const char *, + char **, u_int, const char *, int); +static void client_signal(int); +static void client_dispatch(struct imsg *, void *); +static void client_dispatch_attached(struct imsg *); +static void client_dispatch_wait(struct imsg *); +static const char *client_exit_message(void); + +/* + * Get server create lock. If already held then server start is happening in + * another client, so block until the lock is released and return -2 to + * retry. Return -1 on failure to continue and start the server anyway. + */ +static int +client_get_lock(char *lockfile) +{ + int lockfd; + + log_debug("lock file is %s", lockfile); + + if ((lockfd = open(lockfile, O_WRONLY|O_CREAT, 0600)) == -1) { + log_debug("open failed: %s", strerror(errno)); + return (-1); + } + + if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) { + log_debug("flock failed: %s", strerror(errno)); + if (errno != EAGAIN) + return (lockfd); + while (flock(lockfd, LOCK_EX) == -1 && errno == EINTR) + /* nothing */; + close(lockfd); + return (-2); + } + log_debug("flock succeeded"); + + return (lockfd); +} + +/* Connect client to server. */ +static int +client_connect(struct event_base *base, const char *path, uint64_t flags) +{ + struct sockaddr_un sa; + size_t size; + int fd, lockfd = -1, locked = 0; + char *lockfile = NULL; + + memset(&sa, 0, sizeof sa); + sa.sun_family = AF_UNIX; + size = strlcpy(sa.sun_path, path, sizeof sa.sun_path); + if (size >= sizeof sa.sun_path) { + errno = ENAMETOOLONG; + return (-1); + } + log_debug("socket is %s", path); + +retry: + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + return (-1); + + log_debug("trying connect"); + if (connect(fd, (struct sockaddr *)&sa, sizeof sa) == -1) { + log_debug("connect failed: %s", strerror(errno)); + if (errno != ECONNREFUSED && errno != ENOENT) + goto failed; + if (flags & CLIENT_NOSTARTSERVER) + goto failed; + if (~flags & CLIENT_STARTSERVER) + goto failed; + close(fd); + + if (!locked) { + xasprintf(&lockfile, "%s.lock", path); + if ((lockfd = client_get_lock(lockfile)) < 0) { + log_debug("didn't get lock (%d)", lockfd); + + free(lockfile); + lockfile = NULL; + + if (lockfd == -2) + goto retry; + } + log_debug("got lock (%d)", lockfd); + + /* + * Always retry at least once, even if we got the lock, + * because another client could have taken the lock, + * started the server and released the lock between our + * connect() and flock(). + */ + locked = 1; + goto retry; + } + + if (lockfd >= 0 && unlink(path) != 0 && errno != ENOENT) { + free(lockfile); + close(lockfd); + return (-1); + } + fd = server_start(client_proc, flags, base, lockfd, lockfile); + } + + if (locked && lockfd >= 0) { + free(lockfile); + close(lockfd); + } + setblocking(fd, 0); + return (fd); + +failed: + if (locked) { + free(lockfile); + close(lockfd); + } + close(fd); + return (-1); +} + +/* Get exit string from reason number. */ +const char * +client_exit_message(void) +{ + static char msg[256]; + + switch (client_exitreason) { + case CLIENT_EXIT_NONE: + break; + case CLIENT_EXIT_DETACHED: + if (client_exitsession != NULL) { + xsnprintf(msg, sizeof msg, "detached " + "(from session %s)", client_exitsession); + return (msg); + } + return ("detached"); + case CLIENT_EXIT_DETACHED_HUP: + if (client_exitsession != NULL) { + xsnprintf(msg, sizeof msg, "detached and SIGHUP " + "(from session %s)", client_exitsession); + return (msg); + } + return ("detached and SIGHUP"); + case CLIENT_EXIT_LOST_TTY: + return ("lost tty"); + case CLIENT_EXIT_TERMINATED: + return ("terminated"); + case CLIENT_EXIT_LOST_SERVER: + return ("server exited unexpectedly"); + case CLIENT_EXIT_EXITED: + return ("exited"); + case CLIENT_EXIT_SERVER_EXITED: + return ("server exited"); + case CLIENT_EXIT_MESSAGE_PROVIDED: + return (client_exitmessage); + } + return ("unknown reason"); +} + +/* Exit if all streams flushed. */ +static void +client_exit(void) +{ + if (!file_write_left(&client_files)) + proc_exit(client_proc); +} + +/* Client main loop. */ +int +client_main(struct event_base *base, int argc, char **argv, uint64_t flags, + int feat) +{ + struct cmd_parse_result *pr; + struct msg_command *data; + int fd, i; + const char *ttynam, *termname, *cwd; + pid_t ppid; + enum msgtype msg; + struct termios tio, saved_tio; + size_t size, linesize = 0; + ssize_t linelen; + char *line = NULL, **caps = NULL, *cause; + u_int ncaps = 0; + struct args_value *values; + + /* Ignore SIGCHLD now or daemon() in the server will leave a zombie. */ + signal(SIGCHLD, SIG_IGN); + + /* Set up the initial command. */ + if (shell_command != NULL) { + msg = MSG_SHELL; + flags |= CLIENT_STARTSERVER; + } else if (argc == 0) { + msg = MSG_COMMAND; + flags |= CLIENT_STARTSERVER; + } else { + msg = MSG_COMMAND; + + /* + * It's annoying parsing the command string twice (in client + * and later in server) but it is necessary to get the start + * server flag. + */ + values = args_from_vector(argc, argv); + pr = cmd_parse_from_arguments(values, argc, NULL); + if (pr->status == CMD_PARSE_SUCCESS) { + if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER)) + flags |= CLIENT_STARTSERVER; + cmd_list_free(pr->cmdlist); + } else + free(pr->error); + args_free_values(values, argc); + free(values); + } + + /* Create client process structure (starts logging). */ + client_proc = proc_start("client"); + proc_set_signals(client_proc, client_signal); + + /* Save the flags. */ + client_flags = flags; + log_debug("flags are %#llx", (unsigned long long)client_flags); + + /* Initialize the client socket and start the server. */ + fd = client_connect(base, socket_path, client_flags); + if (fd == -1) { + if (errno == ECONNREFUSED) { + fprintf(stderr, "no server running on %s\n", + socket_path); + } else { + fprintf(stderr, "error connecting to %s (%s)\n", + socket_path, strerror(errno)); + } + return (1); + } + client_peer = proc_add_peer(client_proc, fd, client_dispatch, NULL); + + /* Save these before pledge(). */ + if ((cwd = find_cwd()) == NULL && (cwd = find_home()) == NULL) + cwd = "/"; + if ((ttynam = ttyname(STDIN_FILENO)) == NULL) + ttynam = ""; + if ((termname = getenv("TERM")) == NULL) + termname = ""; + + /* + * Drop privileges for client. "proc exec" is needed for -c and for + * locking (which uses system(3)). + * + * "tty" is needed to restore termios(4) and also for some reason -CC + * does not work properly without it (input is not recognised). + * + * "sendfd" is dropped later in client_dispatch_wait(). + */ + if (pledge( + "stdio rpath wpath cpath unix sendfd proc exec tty", + NULL) != 0) + fatal("pledge failed"); + + /* Load terminfo entry if any. */ + if (isatty(STDIN_FILENO) && + *termname != '\0' && + tty_term_read_list(termname, STDIN_FILENO, &caps, &ncaps, + &cause) != 0) { + fprintf(stderr, "%s\n", cause); + free(cause); + return (1); + } + + /* Free stuff that is not used in the client. */ + if (ptm_fd != -1) + close(ptm_fd); + options_free(global_options); + options_free(global_s_options); + options_free(global_w_options); + environ_free(global_environ); + + /* Set up control mode. */ + if (client_flags & CLIENT_CONTROLCONTROL) { + if (tcgetattr(STDIN_FILENO, &saved_tio) != 0) { + fprintf(stderr, "tcgetattr failed: %s\n", + strerror(errno)); + return (1); + } + cfmakeraw(&tio); + tio.c_iflag = ICRNL|IXANY; + tio.c_oflag = OPOST|ONLCR; +#ifdef NOKERNINFO + tio.c_lflag = NOKERNINFO; +#endif + tio.c_cflag = CREAD|CS8|HUPCL; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + cfsetispeed(&tio, cfgetispeed(&saved_tio)); + cfsetospeed(&tio, cfgetospeed(&saved_tio)); + tcsetattr(STDIN_FILENO, TCSANOW, &tio); + } + + /* Send identify messages. */ + client_send_identify(ttynam, termname, caps, ncaps, cwd, feat); + tty_term_free_list(caps, ncaps); + proc_flush_peer(client_peer); + + /* Send first command. */ + if (msg == MSG_COMMAND) { + /* How big is the command? */ + size = 0; + for (i = 0; i < argc; i++) + size += strlen(argv[i]) + 1; + if (size > MAX_IMSGSIZE - (sizeof *data)) { + fprintf(stderr, "command too long\n"); + return (1); + } + data = xmalloc((sizeof *data) + size); + + /* Prepare command for server. */ + data->argc = argc; + if (cmd_pack_argv(argc, argv, (char *)(data + 1), size) != 0) { + fprintf(stderr, "command too long\n"); + free(data); + return (1); + } + size += sizeof *data; + + /* Send the command. */ + if (proc_send(client_peer, msg, -1, data, size) != 0) { + fprintf(stderr, "failed to send command\n"); + free(data); + return (1); + } + free(data); + } else if (msg == MSG_SHELL) + proc_send(client_peer, msg, -1, NULL, 0); + + /* Start main loop. */ + proc_loop(client_proc, NULL); + + /* Run command if user requested exec, instead of exiting. */ + if (client_exittype == MSG_EXEC) { + if (client_flags & CLIENT_CONTROLCONTROL) + tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio); + client_exec(client_execshell, client_execcmd); + } + + /* Restore streams to blocking. */ + setblocking(STDIN_FILENO, 1); + setblocking(STDOUT_FILENO, 1); + setblocking(STDERR_FILENO, 1); + + /* Print the exit message, if any, and exit. */ + if (client_attached) { + if (client_exitreason != CLIENT_EXIT_NONE) + printf("[%s]\n", client_exit_message()); + + ppid = getppid(); + if (client_exittype == MSG_DETACHKILL && ppid > 1) + kill(ppid, SIGHUP); + } else if (client_flags & CLIENT_CONTROL) { + if (client_exitreason != CLIENT_EXIT_NONE) + printf("%%exit %s\n", client_exit_message()); + else + printf("%%exit\n"); + fflush(stdout); + if (client_flags & CLIENT_CONTROL_WAITEXIT) { + setvbuf(stdin, NULL, _IOLBF, 0); + for (;;) { + linelen = getline(&line, &linesize, stdin); + if (linelen <= 1) + break; + } + free(line); + } + if (client_flags & CLIENT_CONTROLCONTROL) { + printf("\033\\"); + fflush(stdout); + tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio); + } + } else if (client_exitreason != CLIENT_EXIT_NONE) + fprintf(stderr, "%s\n", client_exit_message()); + return (client_exitval); +} + +/* Send identify messages to server. */ +static void +client_send_identify(const char *ttynam, const char *termname, char **caps, + u_int ncaps, const char *cwd, int feat) +{ + char **ss; + size_t sslen; + int fd, flags = client_flags; + pid_t pid; + u_int i; + + proc_send(client_peer, MSG_IDENTIFY_FLAGS, -1, &flags, sizeof flags); + proc_send(client_peer, MSG_IDENTIFY_LONGFLAGS, -1, &client_flags, + sizeof client_flags); + + proc_send(client_peer, MSG_IDENTIFY_TERM, -1, termname, + strlen(termname) + 1); + proc_send(client_peer, MSG_IDENTIFY_FEATURES, -1, &feat, sizeof feat); + + proc_send(client_peer, MSG_IDENTIFY_TTYNAME, -1, ttynam, + strlen(ttynam) + 1); + proc_send(client_peer, MSG_IDENTIFY_CWD, -1, cwd, strlen(cwd) + 1); + + for (i = 0; i < ncaps; i++) { + proc_send(client_peer, MSG_IDENTIFY_TERMINFO, -1, + caps[i], strlen(caps[i]) + 1); + } + + if ((fd = dup(STDIN_FILENO)) == -1) + fatal("dup failed"); + proc_send(client_peer, MSG_IDENTIFY_STDIN, fd, NULL, 0); + if ((fd = dup(STDOUT_FILENO)) == -1) + fatal("dup failed"); + proc_send(client_peer, MSG_IDENTIFY_STDOUT, fd, NULL, 0); + + pid = getpid(); + proc_send(client_peer, MSG_IDENTIFY_CLIENTPID, -1, &pid, sizeof pid); + + for (ss = environ; *ss != NULL; ss++) { + sslen = strlen(*ss) + 1; + if (sslen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) + continue; + proc_send(client_peer, MSG_IDENTIFY_ENVIRON, -1, *ss, sslen); + } + + proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0); +} + +/* Run command in shell; used for -c. */ +static __dead void +client_exec(const char *shell, const char *shellcmd) +{ + const char *name, *ptr; + char *argv0; + + log_debug("shell %s, command %s", shell, shellcmd); + + ptr = strrchr(shell, '/'); + if (ptr != NULL && *(ptr + 1) != '\0') + name = ptr + 1; + else + name = shell; + if (client_flags & CLIENT_LOGIN) + xasprintf(&argv0, "-%s", name); + else + xasprintf(&argv0, "%s", name); + setenv("SHELL", shell, 1); + + proc_clear_signals(client_proc, 1); + + setblocking(STDIN_FILENO, 1); + setblocking(STDOUT_FILENO, 1); + setblocking(STDERR_FILENO, 1); + closefrom(STDERR_FILENO + 1); + + execl(shell, argv0, "-c", shellcmd, (char *) NULL); + fatal("execl failed"); +} + +/* Callback to handle signals in the client. */ +static void +client_signal(int sig) +{ + struct sigaction sigact; + int status; + + log_debug("%s: %s", __func__, strsignal(sig)); + if (sig == SIGCHLD) + waitpid(WAIT_ANY, &status, WNOHANG); + else if (!client_attached) { + if (sig == SIGTERM || sig == SIGHUP) + proc_exit(client_proc); + } else { + switch (sig) { + case SIGHUP: + client_exitreason = CLIENT_EXIT_LOST_TTY; + client_exitval = 1; + proc_send(client_peer, MSG_EXITING, -1, NULL, 0); + break; + case SIGTERM: + if (!client_suspended) + client_exitreason = CLIENT_EXIT_TERMINATED; + client_exitval = 1; + proc_send(client_peer, MSG_EXITING, -1, NULL, 0); + break; + case SIGWINCH: + proc_send(client_peer, MSG_RESIZE, -1, NULL, 0); + break; + case SIGCONT: + memset(&sigact, 0, sizeof sigact); + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = SA_RESTART; + sigact.sa_handler = SIG_IGN; + if (sigaction(SIGTSTP, &sigact, NULL) != 0) + fatal("sigaction failed"); + proc_send(client_peer, MSG_WAKEUP, -1, NULL, 0); + client_suspended = 0; + break; + } + } +} + +/* Callback for file write error or close. */ +static void +client_file_check_cb(__unused struct client *c, __unused const char *path, + __unused int error, __unused int closed, __unused struct evbuffer *buffer, + __unused void *data) +{ + if (client_exitflag) + client_exit(); +} + +/* Callback for client read events. */ +static void +client_dispatch(struct imsg *imsg, __unused void *arg) +{ + if (imsg == NULL) { + if (!client_exitflag) { + client_exitreason = CLIENT_EXIT_LOST_SERVER; + client_exitval = 1; + } + proc_exit(client_proc); + return; + } + + if (client_attached) + client_dispatch_attached(imsg); + else + client_dispatch_wait(imsg); +} + +/* Process an exit message. */ +static void +client_dispatch_exit_message(char *data, size_t datalen) +{ + int retval; + + if (datalen < sizeof retval && datalen != 0) + fatalx("bad MSG_EXIT size"); + + if (datalen >= sizeof retval) { + memcpy(&retval, data, sizeof retval); + client_exitval = retval; + } + + if (datalen > sizeof retval) { + datalen -= sizeof retval; + data += sizeof retval; + + client_exitmessage = xmalloc(datalen); + memcpy(client_exitmessage, data, datalen); + client_exitmessage[datalen - 1] = '\0'; + + client_exitreason = CLIENT_EXIT_MESSAGE_PROVIDED; + } +} + +/* Dispatch imsgs when in wait state (before MSG_READY). */ +static void +client_dispatch_wait(struct imsg *imsg) +{ + char *data; + ssize_t datalen; + static int pledge_applied; + + /* + * "sendfd" is no longer required once all of the identify messages + * have been sent. We know the server won't send us anything until that + * point (because we don't ask it to), so we can drop "sendfd" once we + * get the first message from the server. + */ + if (!pledge_applied) { + if (pledge( + "stdio rpath wpath cpath unix proc exec tty", + NULL) != 0) + fatal("pledge failed"); + pledge_applied = 1; + } + + data = imsg->data; + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + + switch (imsg->hdr.type) { + case MSG_EXIT: + case MSG_SHUTDOWN: + client_dispatch_exit_message(data, datalen); + client_exitflag = 1; + client_exit(); + break; + case MSG_READY: + if (datalen != 0) + fatalx("bad MSG_READY size"); + + client_attached = 1; + proc_send(client_peer, MSG_RESIZE, -1, NULL, 0); + break; + case MSG_VERSION: + if (datalen != 0) + fatalx("bad MSG_VERSION size"); + + fprintf(stderr, "protocol version mismatch " + "(client %d, server %u)\n", PROTOCOL_VERSION, + imsg->hdr.peerid & 0xff); + client_exitval = 1; + proc_exit(client_proc); + break; + case MSG_FLAGS: + if (datalen != sizeof client_flags) + fatalx("bad MSG_FLAGS string"); + + memcpy(&client_flags, data, sizeof client_flags); + log_debug("new flags are %#llx", + (unsigned long long)client_flags); + break; + case MSG_SHELL: + if (datalen == 0 || data[datalen - 1] != '\0') + fatalx("bad MSG_SHELL string"); + + client_exec(data, shell_command); + /* NOTREACHED */ + case MSG_DETACH: + case MSG_DETACHKILL: + proc_send(client_peer, MSG_EXITING, -1, NULL, 0); + break; + case MSG_EXITED: + proc_exit(client_proc); + break; + case MSG_READ_OPEN: + file_read_open(&client_files, client_peer, imsg, 1, + !(client_flags & CLIENT_CONTROL), client_file_check_cb, + NULL); + break; + case MSG_WRITE_OPEN: + file_write_open(&client_files, client_peer, imsg, 1, + !(client_flags & CLIENT_CONTROL), client_file_check_cb, + NULL); + break; + case MSG_WRITE: + file_write_data(&client_files, imsg); + break; + case MSG_WRITE_CLOSE: + file_write_close(&client_files, imsg); + break; + case MSG_OLDSTDERR: + case MSG_OLDSTDIN: + case MSG_OLDSTDOUT: + fprintf(stderr, "server version is too old for client\n"); + proc_exit(client_proc); + break; + } +} + +/* Dispatch imsgs in attached state (after MSG_READY). */ +static void +client_dispatch_attached(struct imsg *imsg) +{ + struct sigaction sigact; + char *data; + ssize_t datalen; + + data = imsg->data; + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + + switch (imsg->hdr.type) { + case MSG_FLAGS: + if (datalen != sizeof client_flags) + fatalx("bad MSG_FLAGS string"); + + memcpy(&client_flags, data, sizeof client_flags); + log_debug("new flags are %#llx", + (unsigned long long)client_flags); + break; + case MSG_DETACH: + case MSG_DETACHKILL: + if (datalen == 0 || data[datalen - 1] != '\0') + fatalx("bad MSG_DETACH string"); + + client_exitsession = xstrdup(data); + client_exittype = imsg->hdr.type; + if (imsg->hdr.type == MSG_DETACHKILL) + client_exitreason = CLIENT_EXIT_DETACHED_HUP; + else + client_exitreason = CLIENT_EXIT_DETACHED; + proc_send(client_peer, MSG_EXITING, -1, NULL, 0); + break; + case MSG_EXEC: + if (datalen == 0 || data[datalen - 1] != '\0' || + strlen(data) + 1 == (size_t)datalen) + fatalx("bad MSG_EXEC string"); + client_execcmd = xstrdup(data); + client_execshell = xstrdup(data + strlen(data) + 1); + + client_exittype = imsg->hdr.type; + proc_send(client_peer, MSG_EXITING, -1, NULL, 0); + break; + case MSG_EXIT: + client_dispatch_exit_message(data, datalen); + if (client_exitreason == CLIENT_EXIT_NONE) + client_exitreason = CLIENT_EXIT_EXITED; + proc_send(client_peer, MSG_EXITING, -1, NULL, 0); + break; + case MSG_EXITED: + if (datalen != 0) + fatalx("bad MSG_EXITED size"); + + proc_exit(client_proc); + break; + case MSG_SHUTDOWN: + if (datalen != 0) + fatalx("bad MSG_SHUTDOWN size"); + + proc_send(client_peer, MSG_EXITING, -1, NULL, 0); + client_exitreason = CLIENT_EXIT_SERVER_EXITED; + client_exitval = 1; + break; + case MSG_SUSPEND: + if (datalen != 0) + fatalx("bad MSG_SUSPEND size"); + + memset(&sigact, 0, sizeof sigact); + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = SA_RESTART; + sigact.sa_handler = SIG_DFL; + if (sigaction(SIGTSTP, &sigact, NULL) != 0) + fatal("sigaction failed"); + client_suspended = 1; + kill(getpid(), SIGTSTP); + break; + case MSG_LOCK: + if (datalen == 0 || data[datalen - 1] != '\0') + fatalx("bad MSG_LOCK string"); + + system(data); + proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0); + break; + } +} |