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