summaryrefslogtreecommitdiffstats
path: root/client.c
diff options
context:
space:
mode:
authorNicholas Marriott <nicm@openbsd.org>2010-10-18 20:00:02 +0000
committerNicholas Marriott <nicm@openbsd.org>2010-10-18 20:00:02 +0000
commit248fb14f089216bf384c8914163cc77b4f219154 (patch)
tree7c55d3a23461ce64809f39a3ad01c487e1de53b2 /client.c
parent31954339d1487d2a179f6180867e67cbd22aabd1 (diff)
Merge the before and after attach client code into one in client.c
(instead of two in tmux.c and client.c).
Diffstat (limited to 'client.c')
-rw-r--r--client.c335
1 files changed, 242 insertions, 93 deletions
diff --git a/client.c b/client.c
index d9682b49..35a2e4e8 100644
--- a/client.c
+++ b/client.c
@@ -28,7 +28,6 @@
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
-#include <syslog.h>
#include <unistd.h>
#include "tmux.h"
@@ -37,77 +36,171 @@ struct imsgbuf client_ibuf;
struct event client_event;
const char *client_exitmsg;
int client_exitval;
+int client_attached;
+int client_connect(char *, int);
void client_send_identify(int);
void client_send_environ(void);
void client_write_server(enum msgtype, void *, size_t);
void client_update_event(void);
void client_signal(int, short, void *);
void client_callback(int, short, void *);
-int client_dispatch(void);
+int client_dispatch_attached(void);
+int client_dispatch_wait(void *);
-struct imsgbuf *
-client_init(char *path, int cmdflags, int flags)
+/* Connect client to server. */
+int
+client_connect(char *path, int start_server)
{
struct sockaddr_un sa;
size_t size;
int fd, mode;
- char rpathbuf[MAXPATHLEN];
-
- if (realpath(path, rpathbuf) == NULL)
- strlcpy(rpathbuf, path, sizeof rpathbuf);
- setproctitle("client (%s)", rpathbuf);
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;
- goto not_found;
+ return (-1);
}
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
fatal("socket failed");
if (connect(fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) == -1) {
- if (!(cmdflags & CMD_STARTSERVER))
- goto not_found;
+ if (!start_server)
+ goto failed;
switch (errno) {
case ECONNREFUSED:
if (unlink(path) != 0)
- goto not_found;
+ goto failed;
/* FALLTHROUGH */
case ENOENT:
- if ((fd = server_start(path)) == -1)
- goto start_failed;
- goto server_started;
+ if ((fd = server_start()) == -1)
+ goto failed;
+ break;
+ default:
+ goto failed;
}
- goto not_found;
}
-server_started:
if ((mode = fcntl(fd, F_GETFL)) == -1)
fatal("fcntl failed");
if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1)
fatal("fcntl failed");
+ return (fd);
+
+failed:
+ close(fd);
+ return (-1);
+}
+
+/* Client main loop. */
+int
+client_main(int argc, char **argv, int flags)
+{
+ struct cmd *cmd;
+ struct cmd_list *cmdlist;
+ struct msg_command_data cmddata;
+ int cmdflags, fd;
+ enum msgtype msg;
+ char *cause;
+
+ /* Set up the initial command. */
+ cmdflags = 0;
+ if (shell_cmd != NULL) {
+ msg = MSG_SHELL;
+ cmdflags = CMD_STARTSERVER;
+ } else if (argc == 0) {
+ msg = MSG_COMMAND;
+ cmdflags = CMD_STARTSERVER|CMD_SENDENVIRON|CMD_CANTNEST;
+ } else {
+ msg = MSG_COMMAND;
+
+ /*
+ * It sucks parsing the command string twice (in client and
+ * later in server) but it is necessary to get the start server
+ * flag.
+ */
+ if ((cmdlist = cmd_list_parse(argc, argv, &cause)) == NULL) {
+ log_warnx("%s", cause);
+ return (1);
+ }
+ cmdflags &= ~CMD_STARTSERVER;
+ TAILQ_FOREACH(cmd, &cmdlist->list, qentry) {
+ if (cmd->entry->flags & CMD_STARTSERVER)
+ cmdflags |= CMD_STARTSERVER;
+ if (cmd->entry->flags & CMD_SENDENVIRON)
+ cmdflags |= CMD_SENDENVIRON;
+ if (cmd->entry->flags & CMD_CANTNEST)
+ cmdflags |= CMD_CANTNEST;
+ }
+ cmd_list_free(cmdlist);
+ }
+
+ /*
+ * Check if this could be a nested session, if the command can't nest:
+ * if the socket path matches $TMUX, this is probably the same server.
+ */
+ if (shell_cmd == NULL && environ_path != NULL &&
+ cmdflags & CMD_CANTNEST && strcmp(socket_path, environ_path) == 0) {
+ log_warnx("sessions should be nested with care. "
+ "unset $TMUX to force.");
+ return (1);
+ }
+
+ /* Initialise the client socket and start the server. */
+ fd = client_connect(socket_path, cmdflags & CMD_STARTSERVER);
+ if (fd == -1) {
+ log_warn("failed to connect to server");
+ return (1);
+ }
+
+ /* Set process title, log and signals now this is the client. */
+ setproctitle("client (%s)", socket_path);
+ logfile("client");
+
+ /* Create imsg. */
imsg_init(&client_ibuf, fd);
- event_set(&client_event, fd, EV_READ, client_callback, NULL);
+ event_set(&client_event, fd, EV_READ, client_callback, shell_cmd);
+ /* Establish signal handlers. */
+ set_signals(client_signal);
+
+ /* Send initial environment. */
if (cmdflags & CMD_SENDENVIRON)
client_send_environ();
client_send_identify(flags);
- return (&client_ibuf);
+ /* Send first command. */
+ if (msg == MSG_COMMAND) {
+ /* Fill in command line arguments. */
+ cmddata.pid = environ_pid;
+ cmddata.idx = environ_idx;
+
+ /* Prepare command for server. */
+ cmddata.argc = argc;
+ if (cmd_pack_argv(
+ argc, argv, cmddata.argv, sizeof cmddata.argv) != 0) {
+ log_warnx("command too long");
+ return (1);
+ }
+
+ client_write_server(msg, &cmddata, sizeof cmddata);
+ } else if (msg == MSG_SHELL)
+ client_write_server(msg, NULL, 0);
-start_failed:
- log_warnx("server failed to start");
- return (NULL);
+ /* Set the event and dispatch. */
+ client_update_event();
+ event_dispatch();
-not_found:
- log_warn("server not found");
- return (NULL);
+ /* Print the exit message, if any, and exit. */
+ if (client_attached && client_exitmsg != NULL && !login_shell)
+ printf("[%s]\n", client_exitmsg);
+ return (client_exitval);
}
+/* Send identify message to server with the file descriptors. */
void
client_send_identify(int flags)
{
@@ -132,13 +225,16 @@ client_send_identify(int flags)
if ((fd = dup(STDOUT_FILENO)) == -1)
fatal("dup failed");
- imsg_compose(&client_ibuf, MSG_STDOUT, PROTOCOL_VERSION, -1, fd, NULL, 0);
+ imsg_compose(&client_ibuf,
+ MSG_STDOUT, PROTOCOL_VERSION, -1, fd, NULL, 0);
if ((fd = dup(STDERR_FILENO)) == -1)
fatal("dup failed");
- imsg_compose(&client_ibuf, MSG_STDERR, PROTOCOL_VERSION, -1, fd, NULL, 0);
+ imsg_compose(&client_ibuf,
+ MSG_STDERR, PROTOCOL_VERSION, -1, fd, NULL, 0);
}
+/* Forward entire environment to server. */
void
client_send_environ(void)
{
@@ -152,12 +248,14 @@ client_send_environ(void)
}
}
+/* Write a message to the server without a file descriptor. */
void
client_write_server(enum msgtype type, void *buf, size_t len)
{
imsg_compose(&client_ibuf, type, PROTOCOL_VERSION, -1, -1, buf, len);
}
+/* Update client event based on whether it needs to read or read and write. */
void
client_update_event(void)
{
@@ -167,91 +265,74 @@ client_update_event(void)
events = EV_READ;
if (client_ibuf.w.queued > 0)
events |= EV_WRITE;
- event_set(&client_event, client_ibuf.fd, events, client_callback, NULL);
+ event_set(
+ &client_event, client_ibuf.fd, events, client_callback, shell_cmd);
event_add(&client_event, NULL);
}
-__dead void
-client_main(void)
-{
- logfile("client");
-
- /* Note: event_init() has already been called. */
-
- /* Set up signals. */
- set_signals(client_signal);
-
- /*
- * Send a resize message immediately in case the terminal size has
- * changed between the identify message to the server and the MSG_READY
- * telling us to move into the client code.
- */
- client_write_server(MSG_RESIZE, NULL, 0);
-
- /*
- * imsg_read in the first client poll loop (before the terminal has
- * been initialised) may have read messages into the buffer after the
- * MSG_READY switched to here. Process anything outstanding now to
- * avoid hanging waiting for messages that have already arrived.
- */
- if (client_dispatch() != 0)
- goto out;
-
- /* Set the event and dispatch. */
- client_update_event();
- event_dispatch();
-
-out:
- /* Print the exit message, if any, and exit. */
- if (client_exitmsg != NULL && !login_shell)
- printf("[%s]\n", client_exitmsg);
- exit(client_exitval);
-}
-
+/* Callback to handle signals in the client. */
/* ARGSUSED */
void
client_signal(int sig, unused short events, unused void *data)
{
- struct sigaction sigact;
+ struct sigaction sigact;
+ int status;
- switch (sig) {
- case SIGHUP:
- client_exitmsg = "lost tty";
- client_exitval = 1;
- client_write_server(MSG_EXITING, NULL, 0);
- break;
- case SIGTERM:
- client_exitmsg = "terminated";
- client_exitval = 1;
- client_write_server(MSG_EXITING, NULL, 0);
- break;
- case SIGWINCH:
- client_write_server(MSG_RESIZE, 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");
- client_write_server(MSG_WAKEUP, NULL, 0);
- break;
+ if (!client_attached) {
+ switch (sig) {
+ case SIGCHLD:
+ waitpid(WAIT_ANY, &status, WNOHANG);
+ break;
+ case SIGTERM:
+ event_loopexit(NULL);
+ break;
+ }
+ } else {
+ switch (sig) {
+ case SIGHUP:
+ client_exitmsg = "lost tty";
+ client_exitval = 1;
+ client_write_server(MSG_EXITING, NULL, 0);
+ break;
+ case SIGTERM:
+ client_exitmsg = "terminated";
+ client_exitval = 1;
+ client_write_server(MSG_EXITING, NULL, 0);
+ break;
+ case SIGWINCH:
+ client_write_server(MSG_RESIZE, 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");
+ client_write_server(MSG_WAKEUP, NULL, 0);
+ break;
+ }
}
client_update_event();
}
+/* Callback for client imsg read events. */
/* ARGSUSED */
void
-client_callback(unused int fd, short events, unused void *data)
+client_callback(unused int fd, short events, void *data)
{
ssize_t n;
+ int retval;
if (events & EV_READ) {
if ((n = imsg_read(&client_ibuf)) == -1 || n == 0)
goto lost_server;
- if (client_dispatch() != 0) {
+ if (client_attached)
+ retval = client_dispatch_attached();
+ else
+ retval = client_dispatch_wait(data);
+ if (retval != 0) {
event_loopexit(NULL);
return;
}
@@ -271,8 +352,76 @@ lost_server:
event_loopexit(NULL);
}
+/* Dispatch imsgs when in wait state (before MSG_READY). */
+int
+client_dispatch_wait(void *data)
+{
+ struct imsg imsg;
+ ssize_t n, datalen;
+ struct msg_shell_data shelldata;
+ struct msg_exit_data exitdata;
+ const char *shellcmd = data;
+
+ if ((n = imsg_read(&client_ibuf)) == -1 || n == 0)
+ fatalx("imsg_read failed");
+
+ for (;;) {
+ if ((n = imsg_get(&client_ibuf, &imsg)) == -1)
+ fatalx("imsg_get failed");
+ if (n == 0)
+ return (0);
+ datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+
+ switch (imsg.hdr.type) {
+ case MSG_EXIT:
+ case MSG_SHUTDOWN:
+ if (datalen != sizeof exitdata) {
+ if (datalen != 0)
+ fatalx("bad MSG_EXIT size");
+ } else {
+ memcpy(&exitdata, imsg.data, sizeof exitdata);
+ client_exitval = exitdata.retcode;
+ }
+ imsg_free(&imsg);
+ return (-1);
+ case MSG_READY:
+ if (datalen != 0)
+ fatalx("bad MSG_READY size");
+
+ client_attached = 1;
+ break;
+ case MSG_VERSION:
+ if (datalen != 0)
+ fatalx("bad MSG_VERSION size");
+
+ log_warnx("protocol version mismatch (client %u, "
+ "server %u)", PROTOCOL_VERSION, imsg.hdr.peerid);
+ client_exitval = 1;
+
+ imsg_free(&imsg);
+ return (-1);
+ case MSG_SHELL:
+ if (datalen != sizeof shelldata)
+ fatalx("bad MSG_SHELL size");
+ memcpy(&shelldata, imsg.data, sizeof shelldata);
+ shelldata.shell[(sizeof shelldata.shell) - 1] = '\0';
+
+ clear_signals(0);
+
+ shell_exec(shelldata.shell, shellcmd);
+ /* NOTREACHED */
+ default:
+ fatalx("unexpected message");
+ }
+
+ imsg_free(&imsg);
+ }
+}
+
+/* Dispatch imsgs in attached state (after MSG_READY). */
+/* ARGSUSED */
int
-client_dispatch(void)
+client_dispatch_attached(void)
{
struct imsg imsg;
struct msg_lock_data lockdata;