From 9200a0be7a515cd04c1a05d120002c73932cbf98 Mon Sep 17 00:00:00 2001
From: Nicholas Marriott <nicm@openbsd.org>
Date: Wed, 23 Sep 2009 12:03:30 +0000
Subject: [PATCH] Support -c like sh(1) to execute a command, useful when tmux
 is a login shell. Suggested by halex@.

This includes another protocol version increase (the last for now) so again
restart the tmux server before upgrading.
---
 client.c     |  2 ++
 server-msg.c | 25 ++++++++++++++++++++
 tmux.1       | 10 ++++++++
 tmux.c       | 66 ++++++++++++++++++++++++++++++++++++++++++++--------
 tmux.h       | 10 ++++++--
 tty.c        | 17 +++++++++-----
 6 files changed, 112 insertions(+), 18 deletions(-)

diff --git a/client.c b/client.c
index ceb054bb..e93272de 100644
--- a/client.c
+++ b/client.c
@@ -91,6 +91,8 @@ server_started:
 		fatal("fcntl failed");
 	if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1)
 		fatal("fcntl failed");
+	if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
+		fatal("fcntl failed");
 	imsg_init(&cctx->ibuf, fd);
 
 	if (cmdflags & CMD_SENDENVIRON)
diff --git a/server-msg.c b/server-msg.c
index 0b9b2a90..b7b45c6c 100644
--- a/server-msg.c
+++ b/server-msg.c
@@ -20,6 +20,7 @@
 #include <sys/ioctl.h>
 
 #include <errno.h>
+#include <paths.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -29,6 +30,7 @@
 
 void	server_msg_command(struct client *, struct msg_command_data *);
 void	server_msg_identify(struct client *, struct msg_identify_data *, int);
+void	server_msg_shell(struct client *);
 
 void printflike2 server_msg_command_error(struct cmd_ctx *, const char *, ...);
 void printflike2 server_msg_command_print(struct cmd_ctx *, const char *, ...);
@@ -113,6 +115,12 @@ server_msg_dispatch(struct client *c)
 			if (strchr(environdata.var, '=') != NULL)
 				environ_put(&c->environ, environdata.var);
 			break;
+		case MSG_SHELL:
+			if (datalen != 0)
+				fatalx("bad MSG_SHELL size");
+
+			server_msg_shell(c);
+			break;
 		default:
 			fatalx("unexpected message");
 		}
@@ -248,3 +256,20 @@ server_msg_identify(struct client *c, struct msg_identify_data *data, int fd)
 
 	c->flags |= CLIENT_TERMINAL;
 }
+
+void
+server_msg_shell(struct client *c)
+{
+	struct msg_shell_data	 data;
+	const char		*shell;
+	
+	shell = options_get_string(&global_s_options, "default-shell");
+
+	if (*shell == '\0' || areshell(shell))
+		shell = _PATH_BSHELL;
+	if (strlcpy(data.shell, shell, sizeof data.shell) >= sizeof data.shell)
+		strlcpy(data.shell, _PATH_BSHELL, sizeof data.shell);
+	
+	server_write_client(c, MSG_SHELL, &data, sizeof data);
+	c->flags |= CLIENT_BAD;	/* it will die after exec */
+}
diff --git a/tmux.1 b/tmux.1
index 7155dcd2..542663d7 100644
--- a/tmux.1
+++ b/tmux.1
@@ -24,6 +24,7 @@
 .Nm tmux
 .Bk -words
 .Op Fl 28dlquv
+.Op Fl c Ar shell-command
 .Op Fl f Ar file
 .Op Fl L Ar socket-name
 .Op Fl S Ar socket-path
@@ -101,6 +102,15 @@ to assume the terminal supports 256 colours.
 Like
 .Fl 2 ,
 but indicates that the terminal supports 88 colours.
+.It Fl c Ar shell-command
+Execute
+.Ar shell-command
+using the default shell.
+If necessary, the
+.Nm
+server will be started to retrieve the
+.Ic default-shell
+option.
 .It Fl d
 Force
 .Nm
diff --git a/tmux.c b/tmux.c
index bde1e7f2..5673282f 100644
--- a/tmux.c
+++ b/tmux.c
@@ -57,13 +57,14 @@ int		 login_shell;
 __dead void	 usage(void);
 char 		*makesockpath(const char *);
 int		 prepare_cmd(enum msgtype *, void **, size_t *, int, char **);
-int		 dispatch_imsg(struct client_ctx *, int *);
+int		 dispatch_imsg(struct client_ctx *, const char *, int *);
+__dead void	 shell_exec(const char *, const char *);
 
 __dead void
 usage(void)
 {
 	fprintf(stderr,
-	    "usage: %s [-28dlquv] [-f file] [-L socket-name]\n"
+	    "usage: %s [-28dlquv] [-c shell-command] [-f file] [-L socket-name]\n"
 	    "            [-S socket-path] [command [flags]]\n",
 	    __progname);
 	exit(1);
@@ -275,17 +276,17 @@ main(int argc, char **argv)
 	struct passwd		*pw;
 	struct options		*so, *wo;
 	struct keylist		*keylist;
-	char			*s, *path, *label, *home, *cause, **var;
-	char			 cwd[MAXPATHLEN];
+	char			*s, *shellcmd, *path, *label, *home, *cause;
+	char			 cwd[MAXPATHLEN], **var;
 	void			*buf;
 	size_t			 len;
 	int	 		 retcode, opt, flags, cmdflags = 0;
 	int			 nfds;
 
 	flags = 0;
-	label = path = NULL;
+	shellcmd = label = path = NULL;
 	login_shell = (**argv == '-');
-	while ((opt = getopt(argc, argv, "28df:lL:qS:uUv")) != -1) {
+	while ((opt = getopt(argc, argv, "28c:df:lL:qS:uUv")) != -1) {
 		switch (opt) {
 		case '2':
 			flags |= IDENTIFY_256COLOURS;
@@ -295,6 +296,11 @@ main(int argc, char **argv)
 			flags |= IDENTIFY_88COLOURS;
 			flags &= ~IDENTIFY_256COLOURS;
 			break;
+		case 'c':
+			if (shellcmd != NULL)
+				xfree(shellcmd);
+			shellcmd = xstrdup(optarg);
+			break;
 		case 'd':
 			flags |= IDENTIFY_HASDEFAULTS;
 			break;
@@ -332,6 +338,9 @@ main(int argc, char **argv)
 	argc -= optind;
 	argv += optind;
 
+	if (shellcmd != NULL && argc != 0)
+		usage();
+
 	log_open_tty(debug_level);
 	siginit();
 
@@ -477,10 +486,16 @@ main(int argc, char **argv)
 	}
 	xfree(label);
 
-	if (prepare_cmd(&msg, &buf, &len, argc, argv) != 0)
+	if (shellcmd != NULL) {
+		msg = MSG_SHELL;
+		buf = NULL;
+		len = 0;
+	} else if (prepare_cmd(&msg, &buf, &len, argc, argv) != 0)
 		exit(1);
 
-	if (argc == 0)	/* new-session is the default */
+	if (shellcmd != NULL)
+		cmdflags |= CMD_STARTSERVER;
+	else if (argc == 0)	/* new-session is the default */
 		cmdflags |= CMD_STARTSERVER|CMD_SENDENVIRON;
 	else {
 		/*
@@ -529,7 +544,7 @@ main(int argc, char **argv)
 			fatalx("socket error");
 
                 if (pfd.revents & POLLIN) {
-			if (dispatch_imsg(&cctx, &retcode) != 0)
+			if (dispatch_imsg(&cctx, shellcmd, &retcode) != 0)
 				break;
 		}
 
@@ -546,11 +561,12 @@ main(int argc, char **argv)
 }
 
 int
-dispatch_imsg(struct client_ctx *cctx, int *retcode)
+dispatch_imsg(struct client_ctx *cctx, const char *shellcmd, int *retcode)
 {
 	struct imsg		imsg;
 	ssize_t			n, datalen;
 	struct msg_print_data	printdata;
+	struct msg_shell_data	shelldata;
 
         if ((n = imsg_read(&cctx->ibuf)) == -1 || n == 0)
 		fatalx("imsg_read failed");
@@ -594,6 +610,13 @@ dispatch_imsg(struct client_ctx *cctx, int *retcode)
 			    "server %u)", PROTOCOL_VERSION, imsg.hdr.peerid);
 			*retcode = 1;
 			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';
+			
+			shell_exec(shelldata.shell, shellcmd);
 		default:
 			fatalx("unexpected message");
 		}
@@ -601,3 +624,26 @@ dispatch_imsg(struct client_ctx *cctx, int *retcode)
 		imsg_free(&imsg);
 	}
 }
+
+__dead void
+shell_exec(const char *shell, const char *shellcmd)
+{
+	const char	*shellname, *ptr;
+	char		*argv0;
+
+	sigreset();
+				
+	ptr = strrchr(shell, '/');
+	if (ptr != NULL && *(ptr + 1) != '\0')
+		shellname = ptr + 1;
+	else
+		shellname = shell;
+	if (login_shell)
+		xasprintf(&argv0, "-%s", shellname);
+	else
+		xasprintf(&argv0, "%s", shellname);
+	setenv("SHELL", shell, 1);
+
+	execl(shell, argv0, "-c", shellcmd, (char *) NULL);
+	fatal("execl failed");
+}
diff --git a/tmux.h b/tmux.h
index cea6c194..5ec263e7 100644
--- a/tmux.h
+++ b/tmux.h
@@ -19,7 +19,7 @@
 #ifndef TMUX_H
 #define TMUX_H
 
-#define PROTOCOL_VERSION 4
+#define PROTOCOL_VERSION 5
 
 #include <sys/param.h>
 #include <sys/time.h>
@@ -130,6 +130,7 @@ enum key_code {
 
 	/* Function keys. */
 	KEYC_F1,
+
 	KEYC_F2,
 	KEYC_F3,
 	KEYC_F4,
@@ -308,7 +309,8 @@ enum msgtype {
 	MSG_WAKEUP,
 	MSG_ENVIRON,
 	MSG_UNLOCK,
-	MSG_LOCK
+	MSG_LOCK,
+	MSG_SHELL
 };
 
 /*
@@ -348,6 +350,10 @@ struct msg_environ_data {
 	char	     	var[ENVIRON_LENGTH];
 };
 
+struct msg_shell_data {
+	char	       	shell[MAXPATHLEN];
+};
+
 /* Mode key commands. */
 enum mode_key_cmd {
 	MODEKEY_NONE,
diff --git a/tty.c b/tty.c
index 2129747d..774808db 100644
--- a/tty.c
+++ b/tty.c
@@ -44,7 +44,6 @@ void	tty_cell(struct tty *,
 void
 tty_init(struct tty *tty, int fd, char *term)
 {
-	int	 mode;
 	char	*path;
 
 	memset(tty, 0, sizeof *tty);
@@ -55,10 +54,6 @@ tty_init(struct tty *tty, int fd, char *term)
 	else
 		tty->termname = xstrdup(term);
 
-	if ((mode = fcntl(fd, F_GETFL)) == -1)
-		fatal("fcntl failed");
-	if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1)
-		fatal("fcntl failed");
 	if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
 		fatal("fcntl failed");
 	tty->fd = fd;
@@ -129,11 +124,16 @@ void
 tty_start_tty(struct tty *tty)
 {
 	struct termios	 tio;
-	int		 what;
+	int		 what, mode;
 
 	if (tty->fd == -1)
 		return;
 
+	if ((mode = fcntl(tty->fd, F_GETFL)) == -1)
+		fatal("fcntl failed");
+	if (fcntl(tty->fd, F_SETFL, mode|O_NONBLOCK) == -1)
+		fatal("fcntl failed");
+
 #if 0
 	tty_detect_utf8(tty);
 #endif
@@ -183,6 +183,7 @@ void
 tty_stop_tty(struct tty *tty)
 {
 	struct winsize	ws;
+	int		mode;
 
 	if (!(tty->flags & TTY_STARTED))
 		return;
@@ -193,6 +194,10 @@ tty_stop_tty(struct tty *tty)
 	 * because the fd is invalid. Things like ssh -t can easily leave us
 	 * with a dead tty.
 	 */
+	if ((mode = fcntl(tty->fd, F_GETFL)) == -1)
+		return;
+	if (fcntl(tty->fd, F_SETFL, mode & ~O_NONBLOCK) == -1)
+		return;
 	if (ioctl(tty->fd, TIOCGWINSZ, &ws) == -1)
 		return;
 	if (tcsetattr(tty->fd, TCSANOW, &tty->tio) == -1)