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