| File: | src/usr.bin/tmux/job.c |
| Warning: | line 191, column 11 Assigned value is garbage or undefined |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* $OpenBSD: job.c,v 1.66 2021/10/24 21:24:17 deraadt 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_MAX260]; | |||
| 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)struct { struct job *le_next; struct job **le_prev; } entry; | |||
| 67 | }; | |||
| 68 | ||||
| 69 | /* All jobs list. */ | |||
| 70 | static LIST_HEAD(joblist, job)struct joblist { struct job *lh_first; } all_jobs = LIST_HEAD_INITIALIZER(all_jobs){ ((void *)0) }; | |||
| 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; | |||
| 83 | sigset_t set, oldset; | |||
| 84 | struct winsize ws; | |||
| 85 | char **argvp, tty[TTY_NAME_MAX260]; | |||
| 86 | ||||
| 87 | /* | |||
| 88 | * Do not set TERM during .tmux.conf, it is nice to be able to use | |||
| 89 | * if-shell to decide on default-terminal based on outside TERM. | |||
| 90 | */ | |||
| 91 | env = environ_for_session(s, !cfg_finished); | |||
| 92 | if (e != NULL((void *)0)) { | |||
| 93 | environ_copy(e, env); | |||
| 94 | } | |||
| 95 | ||||
| 96 | sigfillset(&set); | |||
| 97 | sigprocmask(SIG_BLOCK1, &set, &oldset); | |||
| 98 | ||||
| 99 | if (flags & JOB_PTY0x4) { | |||
| 100 | memset(&ws, 0, sizeof ws); | |||
| 101 | ws.ws_col = sx; | |||
| 102 | ws.ws_row = sy; | |||
| 103 | pid = fdforkpty(ptm_fd, &master, tty, NULL((void *)0), &ws); | |||
| 104 | } else { | |||
| 105 | if (socketpair(AF_UNIX1, SOCK_STREAM1, PF_UNSPEC0, out) != 0) | |||
| 106 | goto fail; | |||
| 107 | pid = fork(); | |||
| 108 | } | |||
| 109 | if (cmd == NULL((void *)0)) { | |||
| 110 | cmd_log_argv(argc, argv, "%s:", __func__); | |||
| 111 | log_debug("%s: cwd=%s", __func__, cwd == NULL((void *)0) ? "" : cwd); | |||
| 112 | } else { | |||
| 113 | log_debug("%s: cmd=%s, cwd=%s", __func__, cmd, | |||
| 114 | cwd == NULL((void *)0) ? "" : cwd); | |||
| 115 | } | |||
| 116 | ||||
| 117 | switch (pid) { | |||
| 118 | case -1: | |||
| 119 | if (~flags & JOB_PTY0x4) { | |||
| 120 | close(out[0]); | |||
| 121 | close(out[1]); | |||
| 122 | } | |||
| 123 | goto fail; | |||
| 124 | case 0: | |||
| 125 | proc_clear_signals(server_proc, 1); | |||
| 126 | sigprocmask(SIG_SETMASK3, &oldset, NULL((void *)0)); | |||
| 127 | ||||
| 128 | if ((cwd == NULL((void *)0) || chdir(cwd) != 0) && | |||
| 129 | ((home = find_home()) == NULL((void *)0) || chdir(home) != 0) && | |||
| 130 | chdir("/") != 0) | |||
| 131 | fatal("chdir failed"); | |||
| 132 | ||||
| 133 | environ_push(env); | |||
| 134 | environ_free(env); | |||
| 135 | ||||
| 136 | if (~flags & JOB_PTY0x4) { | |||
| 137 | if (dup2(out[1], STDIN_FILENO0) == -1) | |||
| 138 | fatal("dup2 failed"); | |||
| 139 | if (dup2(out[1], STDOUT_FILENO1) == -1) | |||
| 140 | fatal("dup2 failed"); | |||
| 141 | if (out[1] != STDIN_FILENO0 && out[1] != STDOUT_FILENO1) | |||
| 142 | close(out[1]); | |||
| 143 | close(out[0]); | |||
| 144 | ||||
| 145 | nullfd = open(_PATH_DEVNULL"/dev/null", O_RDWR0x0002); | |||
| 146 | if (nullfd == -1) | |||
| 147 | fatal("open failed"); | |||
| 148 | if (dup2(nullfd, STDERR_FILENO2) == -1) | |||
| 149 | fatal("dup2 failed"); | |||
| 150 | if (nullfd != STDERR_FILENO2) | |||
| 151 | close(nullfd); | |||
| 152 | } | |||
| 153 | closefrom(STDERR_FILENO2 + 1); | |||
| 154 | ||||
| 155 | if (cmd != NULL((void *)0)) { | |||
| 156 | execl(_PATH_BSHELL"/bin/sh", "sh", "-c", cmd, (char *) NULL((void *)0)); | |||
| 157 | fatal("execl failed"); | |||
| 158 | } else { | |||
| 159 | argvp = cmd_copy_argv(argc, argv); | |||
| 160 | execvp(argvp[0], argvp); | |||
| 161 | fatal("execvp failed"); | |||
| 162 | } | |||
| 163 | } | |||
| 164 | ||||
| 165 | sigprocmask(SIG_SETMASK3, &oldset, NULL((void *)0)); | |||
| 166 | environ_free(env); | |||
| 167 | ||||
| 168 | job = xmalloc(sizeof *job); | |||
| 169 | job->state = JOB_RUNNING; | |||
| 170 | job->flags = flags; | |||
| 171 | ||||
| 172 | if (cmd
| |||
| 173 | job->cmd = xstrdup(cmd); | |||
| 174 | else | |||
| 175 | job->cmd = cmd_stringify_argv(argc, argv); | |||
| 176 | job->pid = pid; | |||
| 177 | strlcpy(job->tty, tty, sizeof job->tty); | |||
| 178 | job->status = 0; | |||
| 179 | ||||
| 180 | LIST_INSERT_HEAD(&all_jobs, job, entry)do { if (((job)->entry.le_next = (&all_jobs)->lh_first ) != ((void *)0)) (&all_jobs)->lh_first->entry.le_prev = &(job)->entry.le_next; (&all_jobs)->lh_first = (job); (job)->entry.le_prev = &(&all_jobs)-> lh_first; } while (0); | |||
| 181 | ||||
| 182 | job->updatecb = updatecb; | |||
| 183 | job->completecb = completecb; | |||
| 184 | job->freecb = freecb; | |||
| 185 | job->data = data; | |||
| 186 | ||||
| 187 | if (~flags & JOB_PTY0x4) { | |||
| 188 | close(out[1]); | |||
| 189 | job->fd = out[0]; | |||
| 190 | } else | |||
| 191 | job->fd = master; | |||
| ||||
| 192 | setblocking(job->fd, 0); | |||
| 193 | ||||
| 194 | job->event = bufferevent_new(job->fd, job_read_callback, | |||
| 195 | job_write_callback, job_error_callback, job); | |||
| 196 | if (job->event == NULL((void *)0)) | |||
| 197 | fatalx("out of memory"); | |||
| 198 | bufferevent_enable(job->event, EV_READ0x02|EV_WRITE0x04); | |||
| 199 | ||||
| 200 | log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid); | |||
| 201 | return (job); | |||
| 202 | ||||
| 203 | fail: | |||
| 204 | sigprocmask(SIG_SETMASK3, &oldset, NULL((void *)0)); | |||
| 205 | environ_free(env); | |||
| 206 | return (NULL((void *)0)); | |||
| 207 | } | |||
| 208 | ||||
| 209 | /* Take job's file descriptor and free the job. */ | |||
| 210 | int | |||
| 211 | job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen) | |||
| 212 | { | |||
| 213 | int fd = job->fd; | |||
| 214 | ||||
| 215 | log_debug("transfer job %p: %s", job, job->cmd); | |||
| 216 | ||||
| 217 | if (pid != NULL((void *)0)) | |||
| 218 | *pid = job->pid; | |||
| 219 | if (tty != NULL((void *)0)) | |||
| 220 | strlcpy(tty, job->tty, ttylen); | |||
| 221 | ||||
| 222 | LIST_REMOVE(job, entry)do { if ((job)->entry.le_next != ((void *)0)) (job)->entry .le_next->entry.le_prev = (job)->entry.le_prev; *(job)-> entry.le_prev = (job)->entry.le_next; ; ; } while (0); | |||
| 223 | free(job->cmd); | |||
| 224 | ||||
| 225 | if (job->freecb != NULL((void *)0) && job->data != NULL((void *)0)) | |||
| 226 | job->freecb(job->data); | |||
| 227 | ||||
| 228 | if (job->event != NULL((void *)0)) | |||
| 229 | bufferevent_free(job->event); | |||
| 230 | ||||
| 231 | free(job); | |||
| 232 | return (fd); | |||
| 233 | } | |||
| 234 | ||||
| 235 | /* Kill and free an individual job. */ | |||
| 236 | void | |||
| 237 | job_free(struct job *job) | |||
| 238 | { | |||
| 239 | log_debug("free job %p: %s", job, job->cmd); | |||
| 240 | ||||
| 241 | LIST_REMOVE(job, entry)do { if ((job)->entry.le_next != ((void *)0)) (job)->entry .le_next->entry.le_prev = (job)->entry.le_prev; *(job)-> entry.le_prev = (job)->entry.le_next; ; ; } while (0); | |||
| 242 | free(job->cmd); | |||
| 243 | ||||
| 244 | if (job->freecb != NULL((void *)0) && job->data != NULL((void *)0)) | |||
| 245 | job->freecb(job->data); | |||
| 246 | ||||
| 247 | if (job->pid != -1) | |||
| 248 | kill(job->pid, SIGTERM15); | |||
| 249 | if (job->event != NULL((void *)0)) | |||
| 250 | bufferevent_free(job->event); | |||
| 251 | if (job->fd != -1) | |||
| 252 | close(job->fd); | |||
| 253 | ||||
| 254 | free(job); | |||
| 255 | } | |||
| 256 | ||||
| 257 | /* Resize job. */ | |||
| 258 | void | |||
| 259 | job_resize(struct job *job, u_int sx, u_int sy) | |||
| 260 | { | |||
| 261 | struct winsize ws; | |||
| 262 | ||||
| 263 | if (job->fd == -1 || (~job->flags & JOB_PTY0x4)) | |||
| 264 | return; | |||
| 265 | ||||
| 266 | log_debug("resize job %p: %ux%u", job, sx, sy); | |||
| 267 | ||||
| 268 | memset(&ws, 0, sizeof ws); | |||
| 269 | ws.ws_col = sx; | |||
| 270 | ws.ws_row = sy; | |||
| 271 | if (ioctl(job->fd, TIOCSWINSZ((unsigned long)0x80000000 | ((sizeof(struct winsize) & 0x1fff ) << 16) | ((('t')) << 8) | ((103))), &ws) == -1) | |||
| 272 | fatal("ioctl failed"); | |||
| 273 | } | |||
| 274 | ||||
| 275 | /* Job buffer read callback. */ | |||
| 276 | static void | |||
| 277 | job_read_callback(__unused__attribute__((__unused__)) struct bufferevent *bufev, void *data) | |||
| 278 | { | |||
| 279 | struct job *job = data; | |||
| 280 | ||||
| 281 | if (job->updatecb != NULL((void *)0)) | |||
| 282 | job->updatecb(job); | |||
| 283 | } | |||
| 284 | ||||
| 285 | /* | |||
| 286 | * Job buffer write callback. Fired when the buffer falls below watermark | |||
| 287 | * (default is empty). If all the data has been written, disable the write | |||
| 288 | * event. | |||
| 289 | */ | |||
| 290 | static void | |||
| 291 | job_write_callback(__unused__attribute__((__unused__)) struct bufferevent *bufev, void *data) | |||
| 292 | { | |||
| 293 | struct job *job = data; | |||
| 294 | size_t len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event))((job->event)->output)->off; | |||
| 295 | ||||
| 296 | log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd, | |||
| 297 | (long) job->pid, len); | |||
| 298 | ||||
| 299 | if (len == 0 && (~job->flags & JOB_KEEPWRITE0x2)) { | |||
| 300 | shutdown(job->fd, SHUT_WR1); | |||
| 301 | bufferevent_disable(job->event, EV_WRITE0x04); | |||
| 302 | } | |||
| 303 | } | |||
| 304 | ||||
| 305 | /* Job buffer error callback. */ | |||
| 306 | static void | |||
| 307 | job_error_callback(__unused__attribute__((__unused__)) struct bufferevent *bufev, __unused__attribute__((__unused__)) short events, | |||
| 308 | void *data) | |||
| 309 | { | |||
| 310 | struct job *job = data; | |||
| 311 | ||||
| 312 | log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid); | |||
| 313 | ||||
| 314 | if (job->state == JOB_DEAD) { | |||
| 315 | if (job->completecb != NULL((void *)0)) | |||
| 316 | job->completecb(job); | |||
| 317 | job_free(job); | |||
| 318 | } else { | |||
| 319 | bufferevent_disable(job->event, EV_READ0x02); | |||
| 320 | job->state = JOB_CLOSED; | |||
| 321 | } | |||
| 322 | } | |||
| 323 | ||||
| 324 | /* Job died (waitpid() returned its pid). */ | |||
| 325 | void | |||
| 326 | job_check_died(pid_t pid, int status) | |||
| 327 | { | |||
| 328 | struct job *job; | |||
| 329 | ||||
| 330 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
| 331 | if (pid == job->pid) | |||
| 332 | break; | |||
| 333 | } | |||
| 334 | if (job == NULL((void *)0)) | |||
| 335 | return; | |||
| 336 | if (WIFSTOPPED(status)(((status) & 0xff) == 0177)) { | |||
| 337 | if (WSTOPSIG(status)(int)(((unsigned)(status) >> 8) & 0xff) == SIGTTIN21 || WSTOPSIG(status)(int)(((unsigned)(status) >> 8) & 0xff) == SIGTTOU22) | |||
| 338 | return; | |||
| 339 | killpg(job->pid, SIGCONT19); | |||
| 340 | return; | |||
| 341 | } | |||
| 342 | log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); | |||
| 343 | ||||
| 344 | job->status = status; | |||
| 345 | ||||
| 346 | if (job->state == JOB_CLOSED) { | |||
| 347 | if (job->completecb != NULL((void *)0)) | |||
| 348 | job->completecb(job); | |||
| 349 | job_free(job); | |||
| 350 | } else { | |||
| 351 | job->pid = -1; | |||
| 352 | job->state = JOB_DEAD; | |||
| 353 | } | |||
| 354 | } | |||
| 355 | ||||
| 356 | /* Get job status. */ | |||
| 357 | int | |||
| 358 | job_get_status(struct job *job) | |||
| 359 | { | |||
| 360 | return (job->status); | |||
| 361 | } | |||
| 362 | ||||
| 363 | /* Get job data. */ | |||
| 364 | void * | |||
| 365 | job_get_data(struct job *job) | |||
| 366 | { | |||
| 367 | return (job->data); | |||
| 368 | } | |||
| 369 | ||||
| 370 | /* Get job event. */ | |||
| 371 | struct bufferevent * | |||
| 372 | job_get_event(struct job *job) | |||
| 373 | { | |||
| 374 | return (job->event); | |||
| 375 | } | |||
| 376 | ||||
| 377 | /* Kill all jobs. */ | |||
| 378 | void | |||
| 379 | job_kill_all(void) | |||
| 380 | { | |||
| 381 | struct job *job; | |||
| 382 | ||||
| 383 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
| 384 | if (job->pid != -1) | |||
| 385 | kill(job->pid, SIGTERM15); | |||
| 386 | } | |||
| 387 | } | |||
| 388 | ||||
| 389 | /* Are any jobs still running? */ | |||
| 390 | int | |||
| 391 | job_still_running(void) | |||
| 392 | { | |||
| 393 | struct job *job; | |||
| 394 | ||||
| 395 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
| 396 | if ((~job->flags & JOB_NOWAIT0x1) && job->state == JOB_RUNNING) | |||
| 397 | return (1); | |||
| 398 | } | |||
| 399 | return (0); | |||
| 400 | } | |||
| 401 | ||||
| 402 | /* Print job summary. */ | |||
| 403 | void | |||
| 404 | job_print_summary(struct cmdq_item *item, int blank) | |||
| 405 | { | |||
| 406 | struct job *job; | |||
| 407 | u_int n = 0; | |||
| 408 | ||||
| 409 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
| 410 | if (blank) { | |||
| 411 | cmdq_print(item, "%s", ""); | |||
| 412 | blank = 0; | |||
| 413 | } | |||
| 414 | cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]", | |||
| 415 | n, job->cmd, job->fd, (long)job->pid, job->status); | |||
| 416 | n++; | |||
| 417 | } | |||
| 418 | } |