1 /* $OpenBSD: cmd-run-shell.c,v 1.70 2021/01/01 08:36:51 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 <stdlib.h> 24 #include <string.h> 25 26 #include "tmux.h" 27 28 /* 29 * Runs a command without a window. 30 */ 31 32 static enum cmd_retval cmd_run_shell_exec(struct cmd *, struct cmdq_item *); 33 34 static void cmd_run_shell_timer(int, short, void *); 35 static void cmd_run_shell_callback(struct job *); 36 static void cmd_run_shell_free(void *); 37 static void cmd_run_shell_print(struct job *, const char *); 38 39 const struct cmd_entry cmd_run_shell_entry = { 40 .name = "run-shell", 41 .alias = "run", 42 43 .args = { "bd:Ct:", 0, 1 }, 44 .usage = "[-bC] [-d delay] " CMD_TARGET_PANE_USAGE " [shell-command]", 45 46 .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, 47 48 .flags = 0, 49 .exec = cmd_run_shell_exec 50 }; 51 52 struct cmd_run_shell_data { 53 struct client *client; 54 char *cmd; 55 int shell; 56 char *cwd; 57 struct cmdq_item *item; 58 struct session *s; 59 int wp_id; 60 struct event timer; 61 int flags; 62 struct cmd_parse_input pi; 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; 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->argc == 0) 115 return (CMD_RETURN_NORMAL); 116 117 cdata = xcalloc(1, sizeof *cdata); 118 if (args->argc != 0) 119 cdata->cmd = format_single_from_target(item, args->argv[0]); 120 121 cdata->shell = !args_has(args, 'C'); 122 if (!cdata->shell) { 123 memset(&cdata->pi, 0, sizeof cdata->pi); 124 cmd_get_source(self, &cdata->pi.file, &cdata->pi.line); 125 if (wait) 126 cdata->pi.item = item; 127 cdata->pi.c = tc; 128 cmd_find_copy_state(&cdata->pi.fs, target); 129 } 130 131 if (args_has(args, 't') && wp != NULL) 132 cdata->wp_id = wp->id; 133 else 134 cdata->wp_id = -1; 135 136 if (wait) { 137 cdata->client = cmdq_get_client(item); 138 cdata->item = item; 139 } else { 140 cdata->client = tc; 141 cdata->flags |= JOB_NOWAIT; 142 } 143 if (cdata->client != NULL) 144 cdata->client->references++; 145 146 cdata->cwd = xstrdup(server_client_get_cwd(cmdq_get_client(item), s)); 147 148 cdata->s = s; 149 if (s != NULL) 150 session_add_ref(s, __func__); 151 152 evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata); 153 if (delay != NULL) { 154 timerclear(&tv); 155 tv.tv_sec = (time_t)d; 156 tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U; 157 evtimer_add(&cdata->timer, &tv); 158 } else 159 event_active(&cdata->timer, EV_TIMEOUT, 1); 160 161 if (!wait) 162 return (CMD_RETURN_NORMAL); 163 return (CMD_RETURN_WAIT); 164 } 165 166 static void 167 cmd_run_shell_timer(__unused int fd, __unused short events, void* arg) 168 { 169 struct cmd_run_shell_data *cdata = arg; 170 struct client *c = cdata->client; 171 const char *cmd = cdata->cmd; 172 char *error; 173 struct cmdq_item *item = cdata->item; 174 enum cmd_parse_status status; 175 176 if (cmd != NULL && cdata->shell) { 177 if (job_run(cmd, cdata->s, cdata->cwd, NULL, 178 cmd_run_shell_callback, cmd_run_shell_free, cdata, 179 cdata->flags, -1, -1) == NULL) 180 cmd_run_shell_free(cdata); 181 return; 182 } 183 184 if (cmd != NULL) { 185 if (item != NULL) { 186 status = cmd_parse_and_insert(cmd, &cdata->pi, item, 187 cmdq_get_state(item), &error); 188 } else { 189 status = cmd_parse_and_append(cmd, &cdata->pi, c, NULL, 190 &error); 191 } 192 if (status == CMD_PARSE_ERROR) { 193 cmdq_error(cdata->item, "%s", error); 194 free(error); 195 } 196 } 197 198 if (cdata->item != NULL) 199 cmdq_continue(cdata->item); 200 cmd_run_shell_free(cdata); 201 } 202 203 static void 204 cmd_run_shell_callback(struct job *job) 205 { 206 struct cmd_run_shell_data *cdata = job_get_data(job); 207 struct bufferevent *event = job_get_event(job); 208 struct cmdq_item *item = cdata->item; 209 char *cmd = cdata->cmd, *msg = NULL, *line; 210 size_t size; 211 int retcode, status; 212 213 do { 214 if ((line = evbuffer_readline(event->input)) != NULL) { 215 cmd_run_shell_print(job, line); 216 free(line); 217 } 218 } while (line != NULL); 219 220 size = EVBUFFER_LENGTH(event->input); 221 if (size != 0) { 222 line = xmalloc(size + 1); 223 memcpy(line, EVBUFFER_DATA(event->input), size); 224 line[size] = '\0'; 225 226 cmd_run_shell_print(job, line); 227 228 free(line); 229 } 230 231 status = job_get_status(job); 232 if (WIFEXITED(status)) { 233 if ((retcode = WEXITSTATUS(status)) != 0) 234 xasprintf(&msg, "'%s' returned %d", cmd, retcode); 235 } else if (WIFSIGNALED(status)) { 236 retcode = WTERMSIG(status); 237 xasprintf(&msg, "'%s' terminated by signal %d", cmd, retcode); 238 retcode += 128; 239 } else 240 retcode = 0; 241 if (msg != NULL) 242 cmd_run_shell_print(job, msg); 243 free(msg); 244 245 if (item != NULL) { 246 if (cmdq_get_client(item) != NULL && 247 cmdq_get_client(item)->session == NULL) 248 cmdq_get_client(item)->retval = retcode; 249 cmdq_continue(item); 250 } 251 } 252 253 static void 254 cmd_run_shell_free(void *data) 255 { 256 struct cmd_run_shell_data *cdata = data; 257 258 evtimer_del(&cdata->timer); 259 if (cdata->s != NULL) 260 session_remove_ref(cdata->s, __func__); 261 if (cdata->client != NULL) 262 server_client_unref(cdata->client); 263 free(cdata->cwd); 264 free(cdata->cmd); 265 free(cdata); 266 } 267