xref: /openbsd-src/usr.bin/tmux/cmd-run-shell.c (revision 2db6a388619f8be004243d57b5f1b15578033771)
1 /* $OpenBSD: cmd-run-shell.c,v 1.77 2021/08/23 12:33:55 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
5  * Copyright (c) 2009 Nicholas Marriott <nicm@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 cmd_retval	cmd_run_shell_exec(struct cmd *, struct cmdq_item *);
34 
35 static void	cmd_run_shell_timer(int, short, void *);
36 static void	cmd_run_shell_callback(struct job *);
37 static void	cmd_run_shell_free(void *);
38 static void	cmd_run_shell_print(struct job *, const char *);
39 
40 const struct cmd_entry cmd_run_shell_entry = {
41 	.name = "run-shell",
42 	.alias = "run",
43 
44 	.args = { "bd:Ct:", 0, 1, NULL },
45 	.usage = "[-bC] [-d delay] " CMD_TARGET_PANE_USAGE " [shell-command]",
46 
47 	.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
48 
49 	.flags = 0,
50 	.exec = cmd_run_shell_exec
51 };
52 
53 struct cmd_run_shell_data {
54 	struct client		*client;
55 	char			*cmd;
56 	struct cmd_list		*cmdlist;
57 	char			*cwd;
58 	struct cmdq_item	*item;
59 	struct session		*s;
60 	int			 wp_id;
61 	struct event		 timer;
62 	int			 flags;
63 };
64 
65 static void
66 cmd_run_shell_print(struct job *job, const char *msg)
67 {
68 	struct cmd_run_shell_data	*cdata = job_get_data(job);
69 	struct window_pane		*wp = NULL;
70 	struct cmd_find_state		 fs;
71 	struct window_mode_entry	*wme;
72 
73 	if (cdata->wp_id != -1)
74 		wp = window_pane_find_by_id(cdata->wp_id);
75 	if (wp == NULL) {
76 		if (cdata->item != NULL) {
77 			cmdq_print(cdata->item, "%s", msg);
78 			return;
79 		}
80 		if (cmd_find_from_nothing(&fs, 0) != 0)
81 			return;
82 		wp = fs.wp;
83 		if (wp == NULL)
84 			return;
85 	}
86 
87 	wme = TAILQ_FIRST(&wp->modes);
88 	if (wme == NULL || wme->mode != &window_view_mode)
89 		window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL);
90 	window_copy_add(wp, "%s", msg);
91 }
92 
93 static enum cmd_retval
94 cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
95 {
96 	struct args			*args = cmd_get_args(self);
97 	struct cmd_find_state		*target = cmdq_get_target(item);
98 	struct cmd_run_shell_data	*cdata;
99 	struct client			*tc = cmdq_get_target_client(item);
100 	struct session			*s = target->s;
101 	struct window_pane		*wp = target->wp;
102 	const char			*delay, *cmd;
103 	double				 d;
104 	struct timeval			 tv;
105 	char				*end;
106 	int				 wait = !args_has(args, 'b');
107 
108 	if ((delay = args_get(args, 'd')) != NULL) {
109 		d = strtod(delay, &end);
110 		if (*end != '\0') {
111 			cmdq_error(item, "invalid delay time: %s", delay);
112 			return (CMD_RETURN_ERROR);
113 		}
114 	} else if (args_count(args) == 0)
115 		return (CMD_RETURN_NORMAL);
116 
117 	cdata = xcalloc(1, sizeof *cdata);
118 	if (!args_has(args, 'C')) {
119 		cmd = args_string(args, 0);
120 		if (cmd != NULL)
121 			cdata->cmd = format_single_from_target(item, cmd);
122 	} else {
123 		cdata->cmdlist = args_make_commands_now(self, item, 0);
124 		if (cdata->cmdlist == NULL)
125 			return (CMD_RETURN_ERROR);
126 	}
127 
128 	if (args_has(args, 't') && wp != NULL)
129 		cdata->wp_id = wp->id;
130 	else
131 		cdata->wp_id = -1;
132 
133 	if (wait) {
134 		cdata->client = cmdq_get_client(item);
135 		cdata->item = item;
136 	} else {
137 		cdata->client = tc;
138 		cdata->flags |= JOB_NOWAIT;
139 	}
140 	if (cdata->client != NULL)
141 		cdata->client->references++;
142 
143 	cdata->cwd = xstrdup(server_client_get_cwd(cmdq_get_client(item), s));
144 
145 	cdata->s = s;
146 	if (s != NULL)
147 		session_add_ref(s, __func__);
148 
149 	evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
150 	if (delay != NULL) {
151 		timerclear(&tv);
152 		tv.tv_sec = (time_t)d;
153 		tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
154 		evtimer_add(&cdata->timer, &tv);
155 	} else
156 		event_active(&cdata->timer, EV_TIMEOUT, 1);
157 
158 	if (!wait)
159 		return (CMD_RETURN_NORMAL);
160 	return (CMD_RETURN_WAIT);
161 }
162 
163 static void
164 cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
165 {
166 	struct cmd_run_shell_data	*cdata = arg;
167 	struct client			*c = cdata->client;
168 	const char			*cmd = cdata->cmd;
169 	struct cmdq_item		*item = cdata->item, *new_item;
170 
171 	if (cdata->cmdlist == NULL && cmd != NULL) {
172 		if (job_run(cmd, 0, NULL, cdata->s, cdata->cwd, NULL,
173 		    cmd_run_shell_callback, cmd_run_shell_free, cdata,
174 		    cdata->flags, -1, -1) == NULL)
175 			cmd_run_shell_free(cdata);
176 		return;
177 	}
178 
179 	if (cdata->cmdlist != NULL) {
180 		if (item == NULL) {
181 			new_item = cmdq_get_command(cdata->cmdlist, NULL);
182 			cmdq_append(c, new_item);
183 		} else {
184 			new_item = cmdq_get_command(cdata->cmdlist,
185 			    cmdq_get_state(item));
186 			cmdq_insert_after(item, new_item);
187 		}
188 	}
189 
190 	if (cdata->item != NULL)
191 		cmdq_continue(cdata->item);
192 	cmd_run_shell_free(cdata);
193 }
194 
195 static void
196 cmd_run_shell_callback(struct job *job)
197 {
198 	struct cmd_run_shell_data	*cdata = job_get_data(job);
199 	struct bufferevent		*event = job_get_event(job);
200 	struct cmdq_item		*item = cdata->item;
201 	char				*cmd = cdata->cmd, *msg = NULL, *line;
202 	size_t				 size;
203 	int				 retcode, status;
204 
205 	do {
206 		if ((line = evbuffer_readline(event->input)) != NULL) {
207 			cmd_run_shell_print(job, line);
208 			free(line);
209 		}
210 	} while (line != NULL);
211 
212 	size = EVBUFFER_LENGTH(event->input);
213 	if (size != 0) {
214 		line = xmalloc(size + 1);
215 		memcpy(line, EVBUFFER_DATA(event->input), size);
216 		line[size] = '\0';
217 
218 		cmd_run_shell_print(job, line);
219 
220 		free(line);
221 	}
222 
223 	status = job_get_status(job);
224 	if (WIFEXITED(status)) {
225 		if ((retcode = WEXITSTATUS(status)) != 0)
226 			xasprintf(&msg, "'%s' returned %d", cmd, retcode);
227 	} else if (WIFSIGNALED(status)) {
228 		retcode = WTERMSIG(status);
229 		xasprintf(&msg, "'%s' terminated by signal %d", cmd, retcode);
230 		retcode += 128;
231 	} else
232 		retcode = 0;
233 	if (msg != NULL)
234 		cmd_run_shell_print(job, msg);
235 	free(msg);
236 
237 	if (item != NULL) {
238 		if (cmdq_get_client(item) != NULL &&
239 		    cmdq_get_client(item)->session == NULL)
240 			cmdq_get_client(item)->retval = retcode;
241 		cmdq_continue(item);
242 	}
243 }
244 
245 static void
246 cmd_run_shell_free(void *data)
247 {
248 	struct cmd_run_shell_data	*cdata = data;
249 
250 	evtimer_del(&cdata->timer);
251 	if (cdata->s != NULL)
252 		session_remove_ref(cdata->s, __func__);
253 	if (cdata->client != NULL)
254 		server_client_unref(cdata->client);
255 	if (cdata->cmdlist != NULL)
256 		cmd_list_free(cdata->cmdlist);
257 	free(cdata->cwd);
258 	free(cdata->cmd);
259 	free(cdata);
260 }
261