1 /* $OpenBSD: job.c,v 1.68 2024/05/15 09:59:12 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/ioctl.h> 21 #include <sys/socket.h> 22 #include <sys/wait.h> 23 24 #include <fcntl.h> 25 #include <paths.h> 26 #include <signal.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <unistd.h> 30 #include <util.h> 31 32 #include "tmux.h" 33 34 /* 35 * Job scheduling. Run queued commands in the background and record their 36 * output. 37 */ 38 39 static void job_read_callback(struct bufferevent *, void *); 40 static void job_write_callback(struct bufferevent *, void *); 41 static void job_error_callback(struct bufferevent *, short, void *); 42 43 /* A single job. */ 44 struct job { 45 enum { 46 JOB_RUNNING, 47 JOB_DEAD, 48 JOB_CLOSED 49 } state; 50 51 int flags; 52 53 char *cmd; 54 pid_t pid; 55 char tty[TTY_NAME_MAX]; 56 int status; 57 58 int fd; 59 struct bufferevent *event; 60 61 job_update_cb updatecb; 62 job_complete_cb completecb; 63 job_free_cb freecb; 64 void *data; 65 66 LIST_ENTRY(job) entry; 67 }; 68 69 /* All jobs list. */ 70 static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs); 71 72 /* Start a job running. */ 73 struct job * 74 job_run(const char *cmd, int argc, char **argv, struct environ *e, struct session *s, 75 const char *cwd, job_update_cb updatecb, job_complete_cb completecb, 76 job_free_cb freecb, void *data, int flags, int sx, int sy) 77 { 78 struct job *job; 79 struct environ *env; 80 pid_t pid; 81 int nullfd, out[2], master; 82 const char *home, *shell; 83 sigset_t set, oldset; 84 struct winsize ws; 85 char **argvp, tty[TTY_NAME_MAX], *argv0; 86 87 /* 88 * Do not set TERM during .tmux.conf (second argument here), it is nice 89 * to be able to use if-shell to decide on default-terminal based on 90 * outside TERM. 91 */ 92 env = environ_for_session(s, !cfg_finished); 93 if (e != NULL) 94 environ_copy(e, env); 95 96 if (s != NULL) 97 shell = options_get_string(s->options, "default-shell"); 98 else 99 shell = options_get_string(global_s_options, "default-shell"); 100 if (!checkshell(shell)) 101 shell = _PATH_BSHELL; 102 argv0 = shell_argv0(shell, 0); 103 104 sigfillset(&set); 105 sigprocmask(SIG_BLOCK, &set, &oldset); 106 107 if (flags & JOB_PTY) { 108 memset(&ws, 0, sizeof ws); 109 ws.ws_col = sx; 110 ws.ws_row = sy; 111 pid = fdforkpty(ptm_fd, &master, tty, NULL, &ws); 112 } else { 113 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0) 114 goto fail; 115 pid = fork(); 116 } 117 if (cmd == NULL) { 118 cmd_log_argv(argc, argv, "%s:", __func__); 119 log_debug("%s: cwd=%s, shell=%s", __func__, 120 cwd == NULL ? "" : cwd, shell); 121 } else { 122 log_debug("%s: cmd=%s, cwd=%s, shell=%s", __func__, cmd, 123 cwd == NULL ? "" : cwd, shell); 124 } 125 126 switch (pid) { 127 case -1: 128 if (~flags & JOB_PTY) { 129 close(out[0]); 130 close(out[1]); 131 } 132 goto fail; 133 case 0: 134 proc_clear_signals(server_proc, 1); 135 sigprocmask(SIG_SETMASK, &oldset, NULL); 136 137 if ((cwd == NULL || chdir(cwd) != 0) && 138 ((home = find_home()) == NULL || chdir(home) != 0) && 139 chdir("/") != 0) 140 fatal("chdir failed"); 141 142 environ_push(env); 143 environ_free(env); 144 145 if (~flags & JOB_PTY) { 146 if (dup2(out[1], STDIN_FILENO) == -1) 147 fatal("dup2 failed"); 148 if (dup2(out[1], STDOUT_FILENO) == -1) 149 fatal("dup2 failed"); 150 if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO) 151 close(out[1]); 152 close(out[0]); 153 154 nullfd = open(_PATH_DEVNULL, O_RDWR); 155 if (nullfd == -1) 156 fatal("open failed"); 157 if (dup2(nullfd, STDERR_FILENO) == -1) 158 fatal("dup2 failed"); 159 if (nullfd != STDERR_FILENO) 160 close(nullfd); 161 } 162 closefrom(STDERR_FILENO + 1); 163 164 if (cmd != NULL) { 165 setenv("SHELL", shell, 1); 166 execl(shell, argv0, "-c", cmd, (char *)NULL); 167 fatal("execl failed"); 168 } else { 169 argvp = cmd_copy_argv(argc, argv); 170 execvp(argvp[0], argvp); 171 fatal("execvp failed"); 172 } 173 } 174 175 sigprocmask(SIG_SETMASK, &oldset, NULL); 176 environ_free(env); 177 free(argv0); 178 179 job = xmalloc(sizeof *job); 180 job->state = JOB_RUNNING; 181 job->flags = flags; 182 183 if (cmd != NULL) 184 job->cmd = xstrdup(cmd); 185 else 186 job->cmd = cmd_stringify_argv(argc, argv); 187 job->pid = pid; 188 strlcpy(job->tty, tty, sizeof job->tty); 189 job->status = 0; 190 191 LIST_INSERT_HEAD(&all_jobs, job, entry); 192 193 job->updatecb = updatecb; 194 job->completecb = completecb; 195 job->freecb = freecb; 196 job->data = data; 197 198 if (~flags & JOB_PTY) { 199 close(out[1]); 200 job->fd = out[0]; 201 } else 202 job->fd = master; 203 setblocking(job->fd, 0); 204 205 job->event = bufferevent_new(job->fd, job_read_callback, 206 job_write_callback, job_error_callback, job); 207 if (job->event == NULL) 208 fatalx("out of memory"); 209 bufferevent_enable(job->event, EV_READ|EV_WRITE); 210 211 log_debug("run job %p: %s, pid %ld", job, job->cmd, (long)job->pid); 212 return (job); 213 214 fail: 215 sigprocmask(SIG_SETMASK, &oldset, NULL); 216 environ_free(env); 217 free(argv0); 218 return (NULL); 219 } 220 221 /* Take job's file descriptor and free the job. */ 222 int 223 job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen) 224 { 225 int fd = job->fd; 226 227 log_debug("transfer job %p: %s", job, job->cmd); 228 229 if (pid != NULL) 230 *pid = job->pid; 231 if (tty != NULL) 232 strlcpy(tty, job->tty, ttylen); 233 234 LIST_REMOVE(job, entry); 235 free(job->cmd); 236 237 if (job->freecb != NULL && job->data != NULL) 238 job->freecb(job->data); 239 240 if (job->event != NULL) 241 bufferevent_free(job->event); 242 243 free(job); 244 return (fd); 245 } 246 247 /* Kill and free an individual job. */ 248 void 249 job_free(struct job *job) 250 { 251 log_debug("free job %p: %s", job, job->cmd); 252 253 LIST_REMOVE(job, entry); 254 free(job->cmd); 255 256 if (job->freecb != NULL && job->data != NULL) 257 job->freecb(job->data); 258 259 if (job->pid != -1) 260 kill(job->pid, SIGTERM); 261 if (job->event != NULL) 262 bufferevent_free(job->event); 263 if (job->fd != -1) 264 close(job->fd); 265 266 free(job); 267 } 268 269 /* Resize job. */ 270 void 271 job_resize(struct job *job, u_int sx, u_int sy) 272 { 273 struct winsize ws; 274 275 if (job->fd == -1 || (~job->flags & JOB_PTY)) 276 return; 277 278 log_debug("resize job %p: %ux%u", job, sx, sy); 279 280 memset(&ws, 0, sizeof ws); 281 ws.ws_col = sx; 282 ws.ws_row = sy; 283 if (ioctl(job->fd, TIOCSWINSZ, &ws) == -1) 284 fatal("ioctl failed"); 285 } 286 287 /* Job buffer read callback. */ 288 static void 289 job_read_callback(__unused struct bufferevent *bufev, void *data) 290 { 291 struct job *job = data; 292 293 if (job->updatecb != NULL) 294 job->updatecb(job); 295 } 296 297 /* 298 * Job buffer write callback. Fired when the buffer falls below watermark 299 * (default is empty). If all the data has been written, disable the write 300 * event. 301 */ 302 static void 303 job_write_callback(__unused struct bufferevent *bufev, void *data) 304 { 305 struct job *job = data; 306 size_t len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event)); 307 308 log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd, 309 (long) job->pid, len); 310 311 if (len == 0 && (~job->flags & JOB_KEEPWRITE)) { 312 shutdown(job->fd, SHUT_WR); 313 bufferevent_disable(job->event, EV_WRITE); 314 } 315 } 316 317 /* Job buffer error callback. */ 318 static void 319 job_error_callback(__unused struct bufferevent *bufev, __unused short events, 320 void *data) 321 { 322 struct job *job = data; 323 324 log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid); 325 326 if (job->state == JOB_DEAD) { 327 if (job->completecb != NULL) 328 job->completecb(job); 329 job_free(job); 330 } else { 331 bufferevent_disable(job->event, EV_READ); 332 job->state = JOB_CLOSED; 333 } 334 } 335 336 /* Job died (waitpid() returned its pid). */ 337 void 338 job_check_died(pid_t pid, int status) 339 { 340 struct job *job; 341 342 LIST_FOREACH(job, &all_jobs, entry) { 343 if (pid == job->pid) 344 break; 345 } 346 if (job == NULL) 347 return; 348 if (WIFSTOPPED(status)) { 349 if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU) 350 return; 351 killpg(job->pid, SIGCONT); 352 return; 353 } 354 log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); 355 356 job->status = status; 357 358 if (job->state == JOB_CLOSED) { 359 if (job->completecb != NULL) 360 job->completecb(job); 361 job_free(job); 362 } else { 363 job->pid = -1; 364 job->state = JOB_DEAD; 365 } 366 } 367 368 /* Get job status. */ 369 int 370 job_get_status(struct job *job) 371 { 372 return (job->status); 373 } 374 375 /* Get job data. */ 376 void * 377 job_get_data(struct job *job) 378 { 379 return (job->data); 380 } 381 382 /* Get job event. */ 383 struct bufferevent * 384 job_get_event(struct job *job) 385 { 386 return (job->event); 387 } 388 389 /* Kill all jobs. */ 390 void 391 job_kill_all(void) 392 { 393 struct job *job; 394 395 LIST_FOREACH(job, &all_jobs, entry) { 396 if (job->pid != -1) 397 kill(job->pid, SIGTERM); 398 } 399 } 400 401 /* Are any jobs still running? */ 402 int 403 job_still_running(void) 404 { 405 struct job *job; 406 407 LIST_FOREACH(job, &all_jobs, entry) { 408 if ((~job->flags & JOB_NOWAIT) && job->state == JOB_RUNNING) 409 return (1); 410 } 411 return (0); 412 } 413 414 /* Print job summary. */ 415 void 416 job_print_summary(struct cmdq_item *item, int blank) 417 { 418 struct job *job; 419 u_int n = 0; 420 421 LIST_FOREACH(job, &all_jobs, entry) { 422 if (blank) { 423 cmdq_print(item, "%s", ""); 424 blank = 0; 425 } 426 cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]", 427 n, job->cmd, job->fd, (long)job->pid, job->status); 428 n++; 429 } 430 } 431