1*668a68adSnicm /* $OpenBSD: job.c,v 1.69 2024/09/30 07:54:51 nicm Exp $ */ 2bf87e351Snicm 3bf87e351Snicm /* 498ca8272Snicm * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> 5bf87e351Snicm * 6bf87e351Snicm * Permission to use, copy, modify, and distribute this software for any 7bf87e351Snicm * purpose with or without fee is hereby granted, provided that the above 8bf87e351Snicm * copyright notice and this permission notice appear in all copies. 9bf87e351Snicm * 10bf87e351Snicm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11bf87e351Snicm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12bf87e351Snicm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13bf87e351Snicm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14bf87e351Snicm * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15bf87e351Snicm * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16bf87e351Snicm * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17bf87e351Snicm */ 18bf87e351Snicm 19bf87e351Snicm #include <sys/types.h> 200cac3d2dSnicm #include <sys/ioctl.h> 2124abd014Snicm #include <sys/socket.h> 22a6c9106fSnicm #include <sys/wait.h> 23bf87e351Snicm 24bf87e351Snicm #include <fcntl.h> 25bf87e351Snicm #include <paths.h> 2626529d6fSnicm #include <signal.h> 277d053cf9Snicm #include <stdlib.h> 28bf87e351Snicm #include <string.h> 29bf87e351Snicm #include <unistd.h> 30a6396ca2Snicm #include <util.h> 31bf87e351Snicm 32bf87e351Snicm #include "tmux.h" 33bf87e351Snicm 34bf87e351Snicm /* 35bf87e351Snicm * Job scheduling. Run queued commands in the background and record their 36bf87e351Snicm * output. 37bf87e351Snicm */ 38bf87e351Snicm 391a432fefSnicm static void job_read_callback(struct bufferevent *, void *); 409883b791Snicm static void job_write_callback(struct bufferevent *, void *); 411a432fefSnicm static void job_error_callback(struct bufferevent *, short, void *); 42fc92b485Snicm 4365b02dc6Snicm /* A single job. */ 449430fe9eSnicm struct job { 459430fe9eSnicm enum { 469430fe9eSnicm JOB_RUNNING, 479430fe9eSnicm JOB_DEAD, 489430fe9eSnicm JOB_CLOSED 499430fe9eSnicm } state; 509430fe9eSnicm 519430fe9eSnicm int flags; 529430fe9eSnicm 539430fe9eSnicm char *cmd; 549430fe9eSnicm pid_t pid; 555edeef37Snicm char tty[TTY_NAME_MAX]; 569430fe9eSnicm int status; 579430fe9eSnicm 589430fe9eSnicm int fd; 599430fe9eSnicm struct bufferevent *event; 609430fe9eSnicm 619430fe9eSnicm job_update_cb updatecb; 629430fe9eSnicm job_complete_cb completecb; 639430fe9eSnicm job_free_cb freecb; 649430fe9eSnicm void *data; 659430fe9eSnicm 669430fe9eSnicm LIST_ENTRY(job) entry; 679430fe9eSnicm }; 689430fe9eSnicm 693ddd7bd6Snicm /* All jobs list. */ 7065b02dc6Snicm static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs); 713ddd7bd6Snicm 72309e6403Snicm /* Start a job running. */ 73bf87e351Snicm struct job * 74*668a68adSnicm job_run(const char *cmd, int argc, char **argv, struct environ *e, 75*668a68adSnicm struct session *s, const char *cwd, job_update_cb updatecb, 76*668a68adSnicm job_complete_cb completecb, job_free_cb freecb, void *data, int flags, 77*668a68adSnicm int sx, int sy) 78bf87e351Snicm { 79bf87e351Snicm struct job *job; 80fb46cb3dSnicm struct environ *env; 81fc92b485Snicm pid_t pid; 82a6396ca2Snicm int nullfd, out[2], master; 83611df774Snicm const char *home, *shell; 84189d1393Snicm sigset_t set, oldset; 85a6396ca2Snicm struct winsize ws; 86611df774Snicm char **argvp, tty[TTY_NAME_MAX], *argv0; 87*668a68adSnicm struct options *oo; 88bf87e351Snicm 89ff7b5ef0Snicm /* 90611df774Snicm * Do not set TERM during .tmux.conf (second argument here), it is nice 91611df774Snicm * to be able to use if-shell to decide on default-terminal based on 92611df774Snicm * outside TERM. 93ff7b5ef0Snicm */ 94ff7b5ef0Snicm env = environ_for_session(s, !cfg_finished); 9527ce41bfSnicm if (e != NULL) 9603a1f8ddSnicm environ_copy(e, env); 97ff7b5ef0Snicm 98*668a68adSnicm if (~flags & JOB_DEFAULTSHELL) 99*668a68adSnicm shell = _PATH_BSHELL; 100*668a68adSnicm else { 101611df774Snicm if (s != NULL) 102*668a68adSnicm oo = s->options; 103611df774Snicm else 104*668a68adSnicm oo = global_s_options; 105*668a68adSnicm shell = options_get_string(oo, "default-shell"); 106611df774Snicm if (!checkshell(shell)) 107611df774Snicm shell = _PATH_BSHELL; 108*668a68adSnicm } 109611df774Snicm argv0 = shell_argv0(shell, 0); 110611df774Snicm 111189d1393Snicm sigfillset(&set); 112189d1393Snicm sigprocmask(SIG_BLOCK, &set, &oldset); 113a6396ca2Snicm 114a6396ca2Snicm if (flags & JOB_PTY) { 115a6396ca2Snicm memset(&ws, 0, sizeof ws); 116a6396ca2Snicm ws.ws_col = sx; 117a6396ca2Snicm ws.ws_row = sy; 1185edeef37Snicm pid = fdforkpty(ptm_fd, &master, tty, NULL, &ws); 119a6396ca2Snicm } else { 120a6396ca2Snicm if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0) 121a6396ca2Snicm goto fail; 122a6396ca2Snicm pid = fork(); 123a6396ca2Snicm } 124309e6403Snicm if (cmd == NULL) { 125309e6403Snicm cmd_log_argv(argc, argv, "%s:", __func__); 126611df774Snicm log_debug("%s: cwd=%s, shell=%s", __func__, 127611df774Snicm cwd == NULL ? "" : cwd, shell); 128309e6403Snicm } else { 129611df774Snicm log_debug("%s: cmd=%s, cwd=%s, shell=%s", __func__, cmd, 130611df774Snicm cwd == NULL ? "" : cwd, shell); 131309e6403Snicm } 132a6396ca2Snicm 133a6396ca2Snicm switch (pid) { 134bf87e351Snicm case -1: 135a6396ca2Snicm if (~flags & JOB_PTY) { 1362f7ca1eaSnicm close(out[0]); 1372f7ca1eaSnicm close(out[1]); 138a6396ca2Snicm } 139a6396ca2Snicm goto fail; 140189d1393Snicm case 0: 14189b52a5bSnicm proc_clear_signals(server_proc, 1); 142189d1393Snicm sigprocmask(SIG_SETMASK, &oldset, NULL); 143bcaf4493Snicm 144f93f4929Snicm if ((cwd == NULL || chdir(cwd) != 0) && 145f93f4929Snicm ((home = find_home()) == NULL || chdir(home) != 0) && 146f93f4929Snicm chdir("/") != 0) 147f93f4929Snicm fatal("chdir failed"); 1480bc4603eSnicm 149fb46cb3dSnicm environ_push(env); 150fb46cb3dSnicm environ_free(env); 151bf87e351Snicm 152a6396ca2Snicm if (~flags & JOB_PTY) { 15397a3b7e2Snicm if (dup2(out[1], STDIN_FILENO) == -1) 15497a3b7e2Snicm fatal("dup2 failed"); 155d9b750bcSnicm if (dup2(out[1], STDOUT_FILENO) == -1) 156a9275bb6Snicm fatal("dup2 failed"); 15797a3b7e2Snicm if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO) 158d9b750bcSnicm close(out[1]); 159a9275bb6Snicm close(out[0]); 160a9275bb6Snicm 161b7041c07Sderaadt nullfd = open(_PATH_DEVNULL, O_RDWR); 1623aaa63ebSderaadt if (nullfd == -1) 163bf87e351Snicm fatal("open failed"); 164bf87e351Snicm if (dup2(nullfd, STDERR_FILENO) == -1) 165bf87e351Snicm fatal("dup2 failed"); 16697a3b7e2Snicm if (nullfd != STDERR_FILENO) 167bf87e351Snicm close(nullfd); 168a6396ca2Snicm } 169498ee386Snicm closefrom(STDERR_FILENO + 1); 170498ee386Snicm 171309e6403Snicm if (cmd != NULL) { 172611df774Snicm setenv("SHELL", shell, 1); 173611df774Snicm execl(shell, argv0, "-c", cmd, (char *)NULL); 174bf87e351Snicm fatal("execl failed"); 175309e6403Snicm } else { 176309e6403Snicm argvp = cmd_copy_argv(argc, argv); 177309e6403Snicm execvp(argvp[0], argvp); 178309e6403Snicm fatal("execvp failed"); 179309e6403Snicm } 180fc92b485Snicm } 181fc92b485Snicm 182189d1393Snicm sigprocmask(SIG_SETMASK, &oldset, NULL); 183fb46cb3dSnicm environ_free(env); 184611df774Snicm free(argv0); 185bf87e351Snicm 186fc92b485Snicm job = xmalloc(sizeof *job); 1877ef73ed3Snicm job->state = JOB_RUNNING; 18849505a58Snicm job->flags = flags; 1897ef73ed3Snicm 190309e6403Snicm if (cmd != NULL) 191fc92b485Snicm job->cmd = xstrdup(cmd); 192309e6403Snicm else 193309e6403Snicm job->cmd = cmd_stringify_argv(argc, argv); 194fc92b485Snicm job->pid = pid; 1955edeef37Snicm strlcpy(job->tty, tty, sizeof job->tty); 196fc92b485Snicm job->status = 0; 197fc92b485Snicm 1981a432fefSnicm LIST_INSERT_HEAD(&all_jobs, job, entry); 199fc92b485Snicm 2001a432fefSnicm job->updatecb = updatecb; 2011a432fefSnicm job->completecb = completecb; 2021a432fefSnicm job->freecb = freecb; 203fc92b485Snicm job->data = data; 204fc92b485Snicm 205a6396ca2Snicm if (~flags & JOB_PTY) { 206a6396ca2Snicm close(out[1]); 207d9b750bcSnicm job->fd = out[0]; 208a6396ca2Snicm } else 209a6396ca2Snicm job->fd = master; 210a0ee4254Snicm setblocking(job->fd, 0); 211bf87e351Snicm 2121a432fefSnicm job->event = bufferevent_new(job->fd, job_read_callback, 2131a432fefSnicm job_write_callback, job_error_callback, job); 214b32e1d34Snicm if (job->event == NULL) 215b32e1d34Snicm fatalx("out of memory"); 216d37a6343Snicm bufferevent_enable(job->event, EV_READ|EV_WRITE); 217bf87e351Snicm 218fc92b485Snicm log_debug("run job %p: %s, pid %ld", job, job->cmd, (long)job->pid); 219fc92b485Snicm return (job); 220a6396ca2Snicm 221a6396ca2Snicm fail: 222a6396ca2Snicm sigprocmask(SIG_SETMASK, &oldset, NULL); 223a6396ca2Snicm environ_free(env); 224611df774Snicm free(argv0); 225a6396ca2Snicm return (NULL); 226bf87e351Snicm } 227fc92b485Snicm 2287caf7dd1Snicm /* Take job's file descriptor and free the job. */ 2297caf7dd1Snicm int 2305edeef37Snicm job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen) 2317caf7dd1Snicm { 2327caf7dd1Snicm int fd = job->fd; 2337caf7dd1Snicm 2347caf7dd1Snicm log_debug("transfer job %p: %s", job, job->cmd); 2357caf7dd1Snicm 2365edeef37Snicm if (pid != NULL) 2375edeef37Snicm *pid = job->pid; 2385edeef37Snicm if (tty != NULL) 2395edeef37Snicm strlcpy(tty, job->tty, ttylen); 2405edeef37Snicm 2417caf7dd1Snicm LIST_REMOVE(job, entry); 2427caf7dd1Snicm free(job->cmd); 2437caf7dd1Snicm 2447caf7dd1Snicm if (job->freecb != NULL && job->data != NULL) 2457caf7dd1Snicm job->freecb(job->data); 2467caf7dd1Snicm 2477caf7dd1Snicm if (job->event != NULL) 2487caf7dd1Snicm bufferevent_free(job->event); 2497caf7dd1Snicm 2507caf7dd1Snicm free(job); 2517caf7dd1Snicm return (fd); 2527caf7dd1Snicm } 2537caf7dd1Snicm 254fc92b485Snicm /* Kill and free an individual job. */ 255fc92b485Snicm void 256fc92b485Snicm job_free(struct job *job) 257fc92b485Snicm { 258fc92b485Snicm log_debug("free job %p: %s", job, job->cmd); 259fc92b485Snicm 2601a432fefSnicm LIST_REMOVE(job, entry); 2617d053cf9Snicm free(job->cmd); 262fc92b485Snicm 2631a432fefSnicm if (job->freecb != NULL && job->data != NULL) 2641a432fefSnicm job->freecb(job->data); 265fc92b485Snicm 266fc92b485Snicm if (job->pid != -1) 267fc92b485Snicm kill(job->pid, SIGTERM); 268fc92b485Snicm if (job->event != NULL) 269fc92b485Snicm bufferevent_free(job->event); 27084045c9aSnicm if (job->fd != -1) 27184045c9aSnicm close(job->fd); 272fc92b485Snicm 2737d053cf9Snicm free(job); 274bf87e351Snicm } 275bf87e351Snicm 2760cac3d2dSnicm /* Resize job. */ 2770cac3d2dSnicm void 2780cac3d2dSnicm job_resize(struct job *job, u_int sx, u_int sy) 2790cac3d2dSnicm { 2800cac3d2dSnicm struct winsize ws; 2810cac3d2dSnicm 2820cac3d2dSnicm if (job->fd == -1 || (~job->flags & JOB_PTY)) 2830cac3d2dSnicm return; 2840cac3d2dSnicm 2850cac3d2dSnicm log_debug("resize job %p: %ux%u", job, sx, sy); 2860cac3d2dSnicm 2870cac3d2dSnicm memset(&ws, 0, sizeof ws); 2880cac3d2dSnicm ws.ws_col = sx; 2890cac3d2dSnicm ws.ws_row = sy; 2900cac3d2dSnicm if (ioctl(job->fd, TIOCSWINSZ, &ws) == -1) 2910cac3d2dSnicm fatal("ioctl failed"); 2920cac3d2dSnicm } 2930cac3d2dSnicm 2941a432fefSnicm /* Job buffer read callback. */ 2951a432fefSnicm static void 2961a432fefSnicm job_read_callback(__unused struct bufferevent *bufev, void *data) 2971a432fefSnicm { 2981a432fefSnicm struct job *job = data; 2991a432fefSnicm 3001a432fefSnicm if (job->updatecb != NULL) 3011a432fefSnicm job->updatecb(job); 3021a432fefSnicm } 3031a432fefSnicm 3041a432fefSnicm /* 3051a432fefSnicm * Job buffer write callback. Fired when the buffer falls below watermark 3061a432fefSnicm * (default is empty). If all the data has been written, disable the write 3071a432fefSnicm * event. 3081a432fefSnicm */ 3099883b791Snicm static void 310d0e2e7f1Snicm job_write_callback(__unused struct bufferevent *bufev, void *data) 31197a3b7e2Snicm { 31297a3b7e2Snicm struct job *job = data; 31397a3b7e2Snicm size_t len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event)); 31497a3b7e2Snicm 315330f5293Snicm log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd, 316330f5293Snicm (long) job->pid, len); 31797a3b7e2Snicm 318a6396ca2Snicm if (len == 0 && (~job->flags & JOB_KEEPWRITE)) { 31997a3b7e2Snicm shutdown(job->fd, SHUT_WR); 32097a3b7e2Snicm bufferevent_disable(job->event, EV_WRITE); 32197a3b7e2Snicm } 32297a3b7e2Snicm } 32397a3b7e2Snicm 32424abd014Snicm /* Job buffer error callback. */ 3259883b791Snicm static void 3261a432fefSnicm job_error_callback(__unused struct bufferevent *bufev, __unused short events, 327d0e2e7f1Snicm void *data) 32824abd014Snicm { 32924abd014Snicm struct job *job = data; 33024abd014Snicm 331fc92b485Snicm log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid); 33224abd014Snicm 3337ef73ed3Snicm if (job->state == JOB_DEAD) { 3341a432fefSnicm if (job->completecb != NULL) 3351a432fefSnicm job->completecb(job); 336f257509cSnicm job_free(job); 337fc92b485Snicm } else { 338fc92b485Snicm bufferevent_disable(job->event, EV_READ); 3397ef73ed3Snicm job->state = JOB_CLOSED; 340f257509cSnicm } 34124abd014Snicm } 34224abd014Snicm 34324abd014Snicm /* Job died (waitpid() returned its pid). */ 34424abd014Snicm void 3459430fe9eSnicm job_check_died(pid_t pid, int status) 34624abd014Snicm { 3479430fe9eSnicm struct job *job; 3489430fe9eSnicm 3499430fe9eSnicm LIST_FOREACH(job, &all_jobs, entry) { 3509430fe9eSnicm if (pid == job->pid) 3519430fe9eSnicm break; 3529430fe9eSnicm } 3539430fe9eSnicm if (job == NULL) 3549430fe9eSnicm return; 355a6c9106fSnicm if (WIFSTOPPED(status)) { 356a6c9106fSnicm if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU) 357a6c9106fSnicm return; 358a6c9106fSnicm killpg(job->pid, SIGCONT); 359a6c9106fSnicm return; 360a6c9106fSnicm } 361fc92b485Snicm log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); 362fc92b485Snicm 36324abd014Snicm job->status = status; 36424abd014Snicm 3657ef73ed3Snicm if (job->state == JOB_CLOSED) { 3661a432fefSnicm if (job->completecb != NULL) 3671a432fefSnicm job->completecb(job); 368f257509cSnicm job_free(job); 3697ef73ed3Snicm } else { 370bf87e351Snicm job->pid = -1; 3717ef73ed3Snicm job->state = JOB_DEAD; 3727ef73ed3Snicm } 373bf87e351Snicm } 3749430fe9eSnicm 3759430fe9eSnicm /* Get job status. */ 3769430fe9eSnicm int 3779430fe9eSnicm job_get_status(struct job *job) 3789430fe9eSnicm { 3799430fe9eSnicm return (job->status); 3809430fe9eSnicm } 3819430fe9eSnicm 3829430fe9eSnicm /* Get job data. */ 3839430fe9eSnicm void * 3849430fe9eSnicm job_get_data(struct job *job) 3859430fe9eSnicm { 3869430fe9eSnicm return (job->data); 3879430fe9eSnicm } 3889430fe9eSnicm 3899430fe9eSnicm /* Get job event. */ 3909430fe9eSnicm struct bufferevent * 3919430fe9eSnicm job_get_event(struct job *job) 3929430fe9eSnicm { 3939430fe9eSnicm return (job->event); 3949430fe9eSnicm } 3959430fe9eSnicm 3969430fe9eSnicm /* Kill all jobs. */ 3979430fe9eSnicm void 3989430fe9eSnicm job_kill_all(void) 3999430fe9eSnicm { 4009430fe9eSnicm struct job *job; 4019430fe9eSnicm 4029430fe9eSnicm LIST_FOREACH(job, &all_jobs, entry) { 4039430fe9eSnicm if (job->pid != -1) 4049430fe9eSnicm kill(job->pid, SIGTERM); 4059430fe9eSnicm } 4069430fe9eSnicm } 4079430fe9eSnicm 4089430fe9eSnicm /* Are any jobs still running? */ 4099430fe9eSnicm int 4109430fe9eSnicm job_still_running(void) 4119430fe9eSnicm { 4129430fe9eSnicm struct job *job; 4139430fe9eSnicm 4149430fe9eSnicm LIST_FOREACH(job, &all_jobs, entry) { 4159430fe9eSnicm if ((~job->flags & JOB_NOWAIT) && job->state == JOB_RUNNING) 4169430fe9eSnicm return (1); 4179430fe9eSnicm } 4189430fe9eSnicm return (0); 4199430fe9eSnicm } 4209430fe9eSnicm 4219430fe9eSnicm /* Print job summary. */ 4229430fe9eSnicm void 4239430fe9eSnicm job_print_summary(struct cmdq_item *item, int blank) 4249430fe9eSnicm { 4259430fe9eSnicm struct job *job; 4269430fe9eSnicm u_int n = 0; 4279430fe9eSnicm 4289430fe9eSnicm LIST_FOREACH(job, &all_jobs, entry) { 4299430fe9eSnicm if (blank) { 4309430fe9eSnicm cmdq_print(item, "%s", ""); 4319430fe9eSnicm blank = 0; 4329430fe9eSnicm } 4339430fe9eSnicm cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]", 4349430fe9eSnicm n, job->cmd, job->fd, (long)job->pid, job->status); 4359430fe9eSnicm n++; 4369430fe9eSnicm } 4379430fe9eSnicm } 438