Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD$ */
      2 
      3 /*
      4  * Copyright (c) 2009 Tiago Cunha <me (at) tiagocunha.org>
      5  * Copyright (c) 2009 Nicholas Marriott <nicm (at) openbsd.org>
      6  *
      7  * Permission to use, copy, modify, and distribute this software for any
      8  * purpose with or without fee is hereby granted, provided that the above
      9  * copyright notice and this permission notice appear in all copies.
     10  *
     11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     15  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
     16  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
     17  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     18  */
     19 
     20 #include <sys/types.h>
     21 #include <sys/wait.h>
     22 
     23 #include <ctype.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 
     27 #include "tmux.h"
     28 
     29 /*
     30  * Runs a command without a window.
     31  */
     32 
     33 static enum args_parse_type	cmd_run_shell_args_parse(struct args *, u_int,
     34 				    char **);
     35 static enum cmd_retval		cmd_run_shell_exec(struct cmd *,
     36 				    struct cmdq_item *);
     37 
     38 static void	cmd_run_shell_timer(int, short, void *);
     39 static void	cmd_run_shell_callback(struct job *);
     40 static void	cmd_run_shell_free(void *);
     41 static void	cmd_run_shell_print(struct job *, const char *);
     42 
     43 const struct cmd_entry cmd_run_shell_entry = {
     44 	.name = "run-shell",
     45 	.alias = "run",
     46 
     47 	.args = { "bd:Ct:Es:c:", 0, 1, cmd_run_shell_args_parse },
     48 	.usage = "[-bCE] [-c start-directory] [-d delay] " CMD_TARGET_PANE_USAGE
     49 	         " [shell-command]",
     50 
     51 	.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
     52 
     53 	.flags = 0,
     54 	.exec = cmd_run_shell_exec
     55 };
     56 
     57 struct cmd_run_shell_data {
     58 	struct client			*client;
     59 	char				*cmd;
     60 	struct args_command_state	*state;
     61 	char				*cwd;
     62 	struct cmdq_item		*item;
     63 	struct session			*s;
     64 	int				 wp_id;
     65 	struct event			 timer;
     66 	int				 flags;
     67 };
     68 
     69 static enum args_parse_type
     70 cmd_run_shell_args_parse(struct args *args, __unused u_int idx,
     71     __unused char **cause)
     72 {
     73 	if (args_has(args, 'C'))
     74 		return (ARGS_PARSE_COMMANDS_OR_STRING);
     75 	return (ARGS_PARSE_STRING);
     76 }
     77 
     78 static void
     79 cmd_run_shell_print(struct job *job, const char *msg)
     80 {
     81 	struct cmd_run_shell_data	*cdata = job_get_data(job);
     82 	struct window_pane		*wp = NULL;
     83 	struct cmd_find_state		 fs;
     84 	struct window_mode_entry	*wme;
     85 
     86 	if (cdata->wp_id != -1)
     87 		wp = window_pane_find_by_id(cdata->wp_id);
     88 	if (wp == NULL) {
     89 		if (cdata->item != NULL) {
     90 			cmdq_print(cdata->item, "%s", msg);
     91 			return;
     92 		}
     93 		if (cdata->item != NULL && cdata->client != NULL)
     94 			wp = server_client_get_pane(cdata->client);
     95 		if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0)
     96 			wp = fs.wp;
     97 		if (wp == NULL)
     98 			return;
     99 	}
    100 
    101 	wme = TAILQ_FIRST(&wp->modes);
    102 	if (wme == NULL || wme->mode != &window_view_mode)
    103 		window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL);
    104 	window_copy_add(wp, 1, "%s", msg);
    105 }
    106 
    107 static enum cmd_retval
    108 cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
    109 {
    110 	struct args			*args = cmd_get_args(self);
    111 	struct cmd_find_state		*target = cmdq_get_target(item);
    112 	struct cmd_run_shell_data	*cdata;
    113 	struct client			*c = cmdq_get_client(item);
    114 	struct client			*tc = cmdq_get_target_client(item);
    115 	struct session			*s = target->s;
    116 	struct window_pane		*wp = target->wp;
    117 	const char			*delay, *cmd;
    118 	double				 d;
    119 	struct timeval			 tv;
    120 	char				*end;
    121 	int				 wait = !args_has(args, 'b');
    122 
    123 	if ((delay = args_get(args, 'd')) != NULL) {
    124 		d = strtod(delay, &end);
    125 		if (*end != '\0') {
    126 			cmdq_error(item, "invalid delay time: %s", delay);
    127 			return (CMD_RETURN_ERROR);
    128 		}
    129 	} else if (args_count(args) == 0)
    130 		return (CMD_RETURN_NORMAL);
    131 
    132 	cdata = xcalloc(1, sizeof *cdata);
    133 	if (!args_has(args, 'C')) {
    134 		cmd = args_string(args, 0);
    135 		if (cmd != NULL)
    136 			cdata->cmd = format_single_from_target(item, cmd);
    137 	} else {
    138 		cdata->state = args_make_commands_prepare(self, item, 0, NULL,
    139 		    wait, 1);
    140 	}
    141 
    142 	if (args_has(args, 't') && wp != NULL)
    143 		cdata->wp_id = wp->id;
    144 	else
    145 		cdata->wp_id = -1;
    146 
    147 	if (wait) {
    148 		cdata->client = c;
    149 		cdata->item = item;
    150 	} else {
    151 		cdata->client = tc;
    152 		cdata->flags |= JOB_NOWAIT;
    153 	}
    154 	if (cdata->client != NULL)
    155 		cdata->client->references++;
    156 	if (args_has(args, 'c'))
    157 		cdata->cwd = xstrdup(args_get(args, 'c'));
    158 	else
    159 		cdata->cwd = xstrdup(server_client_get_cwd(c, s));
    160 
    161 	if (args_has(args, 'E'))
    162 		cdata->flags |= JOB_SHOWSTDERR;
    163 
    164 	cdata->s = s;
    165 	if (s != NULL)
    166 		session_add_ref(s, __func__);
    167 
    168 	evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
    169 	if (delay != NULL) {
    170 		timerclear(&tv);
    171 		tv.tv_sec = (time_t)d;
    172 		tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
    173 		evtimer_add(&cdata->timer, &tv);
    174 	} else
    175 		event_active(&cdata->timer, EV_TIMEOUT, 1);
    176 
    177 	if (!wait)
    178 		return (CMD_RETURN_NORMAL);
    179 	return (CMD_RETURN_WAIT);
    180 }
    181 
    182 static void
    183 cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
    184 {
    185 	struct cmd_run_shell_data	*cdata = arg;
    186 	struct client			*c = cdata->client;
    187 	const char			*cmd = cdata->cmd;
    188 	struct cmdq_item		*item = cdata->item, *new_item;
    189 	struct cmd_list			*cmdlist;
    190 	char				*error;
    191 
    192 	if (cdata->state == NULL) {
    193 		if (cmd == NULL) {
    194 			if (cdata->item != NULL)
    195 				cmdq_continue(cdata->item);
    196 			cmd_run_shell_free(cdata);
    197 			return;
    198 		}
    199 		if (job_run(cmd, 0, NULL, NULL, cdata->s, cdata->cwd, NULL,
    200 		    cmd_run_shell_callback, cmd_run_shell_free, cdata,
    201 		    cdata->flags, -1, -1) == NULL)
    202 			cmd_run_shell_free(cdata);
    203 		return;
    204 	}
    205 
    206 	cmdlist = args_make_commands(cdata->state, 0, NULL, &error);
    207 	if (cmdlist == NULL) {
    208 		if (cdata->item == NULL) {
    209 			*error = toupper((u_char)*error);
    210 			status_message_set(c, -1, 1, 0, 0, "%s", error);
    211 		} else
    212 			cmdq_error(cdata->item, "%s", error);
    213 		free(error);
    214 	} else if (item == NULL) {
    215 		new_item = cmdq_get_command(cmdlist, NULL);
    216 		cmdq_append(c, new_item);
    217 	} else {
    218 		new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
    219 		cmdq_insert_after(item, new_item);
    220 	}
    221 
    222 	if (cdata->item != NULL)
    223 		cmdq_continue(cdata->item);
    224 	cmd_run_shell_free(cdata);
    225 }
    226 
    227 static void
    228 cmd_run_shell_callback(struct job *job)
    229 {
    230 	struct cmd_run_shell_data	*cdata = job_get_data(job);
    231 	struct bufferevent		*event = job_get_event(job);
    232 	struct cmdq_item		*item = cdata->item;
    233 	char				*cmd = cdata->cmd, *msg = NULL, *line;
    234 	size_t				 size;
    235 	int				 retcode, status;
    236 
    237 	do {
    238 		line = evbuffer_readln(event->input, NULL, EVBUFFER_EOL_LF);
    239 		if (line != NULL) {
    240 			cmd_run_shell_print(job, line);
    241 			free(line);
    242 		}
    243 	} while (line != NULL);
    244 
    245 	size = EVBUFFER_LENGTH(event->input);
    246 	if (size != 0) {
    247 		line = xmalloc(size + 1);
    248 		memcpy(line, EVBUFFER_DATA(event->input), size);
    249 		line[size] = '\0';
    250 
    251 		cmd_run_shell_print(job, line);
    252 
    253 		free(line);
    254 	}
    255 
    256 	status = job_get_status(job);
    257 	if (WIFEXITED(status)) {
    258 		if ((retcode = WEXITSTATUS(status)) != 0)
    259 			xasprintf(&msg, "'%s' returned %d", cmd, retcode);
    260 	} else if (WIFSIGNALED(status)) {
    261 		retcode = WTERMSIG(status);
    262 		xasprintf(&msg, "'%s' terminated by signal %d", cmd, retcode);
    263 		retcode += 128;
    264 	} else
    265 		retcode = 0;
    266 	if (msg != NULL)
    267 		cmd_run_shell_print(job, msg);
    268 	free(msg);
    269 
    270 	if (item != NULL) {
    271 		if (cmdq_get_client(item) != NULL &&
    272 		    cmdq_get_client(item)->session == NULL)
    273 			cmdq_get_client(item)->retval = retcode;
    274 		cmdq_continue(item);
    275 	}
    276 }
    277 
    278 static void
    279 cmd_run_shell_free(void *data)
    280 {
    281 	struct cmd_run_shell_data	*cdata = data;
    282 
    283 	evtimer_del(&cdata->timer);
    284 	if (cdata->s != NULL)
    285 		session_remove_ref(cdata->s, __func__);
    286 	if (cdata->client != NULL)
    287 		server_client_unref(cdata->client);
    288 	if (cdata->state != NULL)
    289 		args_make_commands_free(cdata->state);
    290 	free(cdata->cwd);
    291 	free(cdata->cmd);
    292 	free(cdata);
    293 }
    294