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