xref: /openbsd-src/usr.bin/tmux/cmd-run-shell.c (revision d0fc3bb68efd6c434b4053cd7adb29023cbec341)
1 /* $OpenBSD: cmd-run-shell.c,v 1.73 2021/04/12 09:36:12 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 },
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 	int			 shell;
57 	char			*cwd;
58 	struct cmdq_item	*item;
59 	struct session		*s;
60 	int			 wp_id;
61 	struct event		 timer;
62 	int			 flags;
63 	struct cmd_parse_input	 pi;
64 };
65 
66 static void
67 cmd_run_shell_print(struct job *job, const char *msg)
68 {
69 	struct cmd_run_shell_data	*cdata = job_get_data(job);
70 	struct window_pane		*wp = NULL;
71 	struct cmd_find_state		 fs;
72 	struct window_mode_entry	*wme;
73 
74 	if (cdata->wp_id != -1)
75 		wp = window_pane_find_by_id(cdata->wp_id);
76 	if (wp == NULL) {
77 		if (cdata->item != NULL) {
78 			cmdq_print(cdata->item, "%s", msg);
79 			return;
80 		}
81 		if (cmd_find_from_nothing(&fs, 0) != 0)
82 			return;
83 		wp = fs.wp;
84 		if (wp == NULL)
85 			return;
86 	}
87 
88 	wme = TAILQ_FIRST(&wp->modes);
89 	if (wme == NULL || wme->mode != &window_view_mode)
90 		window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL);
91 	window_copy_add(wp, "%s", msg);
92 }
93 
94 static enum cmd_retval
95 cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
96 {
97 	struct args			*args = cmd_get_args(self);
98 	struct cmd_find_state		*target = cmdq_get_target(item);
99 	struct cmd_run_shell_data	*cdata;
100 	struct client			*tc = cmdq_get_target_client(item);
101 	struct session			*s = target->s;
102 	struct window_pane		*wp = target->wp;
103 	const char			*delay;
104 	double				 d;
105 	struct timeval			 tv;
106 	char				*end;
107 	int				 wait = !args_has(args, 'b');
108 
109 	if ((delay = args_get(args, 'd')) != NULL) {
110 		d = strtod(delay, &end);
111 		if (*end != '\0') {
112 			cmdq_error(item, "invalid delay time: %s", delay);
113 			return (CMD_RETURN_ERROR);
114 		}
115 	} else if (args->argc == 0)
116 		return (CMD_RETURN_NORMAL);
117 
118 	cdata = xcalloc(1, sizeof *cdata);
119 	if (args->argc != 0)
120 		cdata->cmd = format_single_from_target(item, args->argv[0]);
121 
122 	cdata->shell = !args_has(args, 'C');
123 	if (!cdata->shell) {
124 		memset(&cdata->pi, 0, sizeof cdata->pi);
125 		cmd_get_source(self, &cdata->pi.file, &cdata->pi.line);
126 		if (wait)
127 			cdata->pi.item = item;
128 		cdata->pi.c = tc;
129 		cmd_find_copy_state(&cdata->pi.fs, target);
130 	}
131 
132 	if (args_has(args, 't') && wp != NULL)
133 		cdata->wp_id = wp->id;
134 	else
135 		cdata->wp_id = -1;
136 
137 	if (wait) {
138 		cdata->client = cmdq_get_client(item);
139 		cdata->item = item;
140 	} else {
141 		cdata->client = tc;
142 		cdata->flags |= JOB_NOWAIT;
143 	}
144 	if (cdata->client != NULL)
145 		cdata->client->references++;
146 
147 	cdata->cwd = xstrdup(server_client_get_cwd(cmdq_get_client(item), s));
148 
149 	cdata->s = s;
150 	if (s != NULL)
151 		session_add_ref(s, __func__);
152 
153 	evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
154 	if (delay != NULL) {
155 		timerclear(&tv);
156 		tv.tv_sec = (time_t)d;
157 		tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
158 		evtimer_add(&cdata->timer, &tv);
159 	} else
160 		event_active(&cdata->timer, EV_TIMEOUT, 1);
161 
162 	if (!wait)
163 		return (CMD_RETURN_NORMAL);
164 	return (CMD_RETURN_WAIT);
165 }
166 
167 static void
168 cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
169 {
170 	struct cmd_run_shell_data	*cdata = arg;
171 	struct client			*c = cdata->client;
172 	const char			*cmd = cdata->cmd;
173 	char				*error;
174 	struct cmdq_item		*item = cdata->item;
175 	enum cmd_parse_status		 status;
176 
177 	if (cmd != NULL && cdata->shell) {
178 		if (job_run(cmd, 0, NULL, cdata->s, cdata->cwd, NULL,
179 		    cmd_run_shell_callback, cmd_run_shell_free, cdata,
180 		    cdata->flags, -1, -1) == NULL)
181 			cmd_run_shell_free(cdata);
182 		return;
183 	}
184 
185 	if (cmd != NULL) {
186 		if (item != NULL) {
187 			status = cmd_parse_and_insert(cmd, &cdata->pi, item,
188 			    cmdq_get_state(item), &error);
189 		} else {
190 			status = cmd_parse_and_append(cmd, &cdata->pi, c, NULL,
191 			    &error);
192 		}
193 		if (status == CMD_PARSE_ERROR) {
194 			if (cdata->item == NULL) {
195 				*error = toupper((u_char)*error);
196 				status_message_set(c, -1, 1, 0, "%s", error);
197 			} else
198 				cmdq_error(cdata->item, "%s", error);
199 			free(error);
200 		}
201 	}
202 
203 	if (cdata->item != NULL)
204 		cmdq_continue(cdata->item);
205 	cmd_run_shell_free(cdata);
206 }
207 
208 static void
209 cmd_run_shell_callback(struct job *job)
210 {
211 	struct cmd_run_shell_data	*cdata = job_get_data(job);
212 	struct bufferevent		*event = job_get_event(job);
213 	struct cmdq_item		*item = cdata->item;
214 	char				*cmd = cdata->cmd, *msg = NULL, *line;
215 	size_t				 size;
216 	int				 retcode, status;
217 
218 	do {
219 		if ((line = evbuffer_readline(event->input)) != NULL) {
220 			cmd_run_shell_print(job, line);
221 			free(line);
222 		}
223 	} while (line != NULL);
224 
225 	size = EVBUFFER_LENGTH(event->input);
226 	if (size != 0) {
227 		line = xmalloc(size + 1);
228 		memcpy(line, EVBUFFER_DATA(event->input), size);
229 		line[size] = '\0';
230 
231 		cmd_run_shell_print(job, line);
232 
233 		free(line);
234 	}
235 
236 	status = job_get_status(job);
237 	if (WIFEXITED(status)) {
238 		if ((retcode = WEXITSTATUS(status)) != 0)
239 			xasprintf(&msg, "'%s' returned %d", cmd, retcode);
240 	} else if (WIFSIGNALED(status)) {
241 		retcode = WTERMSIG(status);
242 		xasprintf(&msg, "'%s' terminated by signal %d", cmd, retcode);
243 		retcode += 128;
244 	} else
245 		retcode = 0;
246 	if (msg != NULL)
247 		cmd_run_shell_print(job, msg);
248 	free(msg);
249 
250 	if (item != NULL) {
251 		if (cmdq_get_client(item) != NULL &&
252 		    cmdq_get_client(item)->session == NULL)
253 			cmdq_get_client(item)->retval = retcode;
254 		cmdq_continue(item);
255 	}
256 }
257 
258 static void
259 cmd_run_shell_free(void *data)
260 {
261 	struct cmd_run_shell_data	*cdata = data;
262 
263 	evtimer_del(&cdata->timer);
264 	if (cdata->s != NULL)
265 		session_remove_ref(cdata->s, __func__);
266 	if (cdata->client != NULL)
267 		server_client_unref(cdata->client);
268 	free(cdata->cwd);
269 	free(cdata->cmd);
270 	free(cdata);
271 }
272