199e242abSchristos /* $OpenBSD$ */ 2698d5317Sjmmv 3698d5317Sjmmv /* 4f26e8bc9Schristos * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> 5698d5317Sjmmv * 6698d5317Sjmmv * Permission to use, copy, modify, and distribute this software for any 7698d5317Sjmmv * purpose with or without fee is hereby granted, provided that the above 8698d5317Sjmmv * copyright notice and this permission notice appear in all copies. 9698d5317Sjmmv * 10698d5317Sjmmv * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11698d5317Sjmmv * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12698d5317Sjmmv * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13698d5317Sjmmv * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14698d5317Sjmmv * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15698d5317Sjmmv * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16698d5317Sjmmv * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17698d5317Sjmmv */ 18698d5317Sjmmv 19698d5317Sjmmv #include <sys/types.h> 20e271dbb8Schristos #include <sys/ioctl.h> 21698d5317Sjmmv #include <sys/socket.h> 22e271dbb8Schristos #include <sys/wait.h> 23698d5317Sjmmv 24698d5317Sjmmv #include <fcntl.h> 2599e242abSchristos #include <signal.h> 2661fba46bSchristos #include <stdlib.h> 27698d5317Sjmmv #include <string.h> 28698d5317Sjmmv #include <unistd.h> 29698d5317Sjmmv 30698d5317Sjmmv #include "tmux.h" 31698d5317Sjmmv 32698d5317Sjmmv /* 33698d5317Sjmmv * Job scheduling. Run queued commands in the background and record their 34698d5317Sjmmv * output. 35698d5317Sjmmv */ 36698d5317Sjmmv 37fe99a117Schristos static void job_read_callback(struct bufferevent *, void *); 38e9a2d6faSchristos static void job_write_callback(struct bufferevent *, void *); 39fe99a117Schristos static void job_error_callback(struct bufferevent *, short, void *); 40698d5317Sjmmv 410a274e86Schristos /* A single job. */ 420a274e86Schristos struct job { 430a274e86Schristos enum { 440a274e86Schristos JOB_RUNNING, 450a274e86Schristos JOB_DEAD, 460a274e86Schristos JOB_CLOSED 470a274e86Schristos } state; 480a274e86Schristos 490a274e86Schristos int flags; 500a274e86Schristos 510a274e86Schristos char *cmd; 520a274e86Schristos pid_t pid; 5346548964Swiz char tty[TTY_NAME_MAX]; 540a274e86Schristos int status; 550a274e86Schristos 560a274e86Schristos int fd; 570a274e86Schristos struct bufferevent *event; 580a274e86Schristos 590a274e86Schristos job_update_cb updatecb; 600a274e86Schristos job_complete_cb completecb; 610a274e86Schristos job_free_cb freecb; 620a274e86Schristos void *data; 630a274e86Schristos 640a274e86Schristos LIST_ENTRY(job) entry; 650a274e86Schristos }; 660a274e86Schristos 670f3d2746Sjmmv /* All jobs list. */ 680a274e86Schristos static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs); 69698d5317Sjmmv 70e271dbb8Schristos /* Start a job running. */ 71698d5317Sjmmv struct job * 72*890b6d91Swiz job_run(const char *cmd, int argc, char **argv, struct environ *e, 73*890b6d91Swiz struct session *s, const char *cwd, job_update_cb updatecb, 74*890b6d91Swiz job_complete_cb completecb, job_free_cb freecb, void *data, int flags, 75*890b6d91Swiz int sx, int sy) 76698d5317Sjmmv { 77698d5317Sjmmv struct job *job; 78f26e8bc9Schristos struct environ *env; 790f3d2746Sjmmv pid_t pid; 80e271dbb8Schristos int nullfd, out[2], master; 81*890b6d91Swiz const char *home, *shell; 82fe99a117Schristos sigset_t set, oldset; 83e271dbb8Schristos struct winsize ws; 84*890b6d91Swiz char **argvp, tty[TTY_NAME_MAX], *argv0; 85*890b6d91Swiz struct options *oo; 86698d5317Sjmmv 87fe99a117Schristos /* 88*890b6d91Swiz * Do not set TERM during .tmux.conf (second argument here), it is nice 89*890b6d91Swiz * to be able to use if-shell to decide on default-terminal based on 90*890b6d91Swiz * outside TERM. 91fe99a117Schristos */ 92fe99a117Schristos env = environ_for_session(s, !cfg_finished); 9346548964Swiz if (e != NULL) 9446548964Swiz environ_copy(e, env); 95fe99a117Schristos 96*890b6d91Swiz if (~flags & JOB_DEFAULTSHELL) 97*890b6d91Swiz shell = _PATH_BSHELL; 98*890b6d91Swiz else { 99*890b6d91Swiz if (s != NULL) 100*890b6d91Swiz oo = s->options; 101*890b6d91Swiz else 102*890b6d91Swiz oo = global_s_options; 103*890b6d91Swiz shell = options_get_string(oo, "default-shell"); 104*890b6d91Swiz if (!checkshell(shell)) 105*890b6d91Swiz shell = _PATH_BSHELL; 106*890b6d91Swiz } 107*890b6d91Swiz argv0 = shell_argv0(shell, 0); 108*890b6d91Swiz 109fe99a117Schristos sigfillset(&set); 110fe99a117Schristos sigprocmask(SIG_BLOCK, &set, &oldset); 111e271dbb8Schristos 112e271dbb8Schristos if (flags & JOB_PTY) { 113e271dbb8Schristos memset(&ws, 0, sizeof ws); 114e271dbb8Schristos ws.ws_col = sx; 115e271dbb8Schristos ws.ws_row = sy; 11646548964Swiz pid = fdforkpty(ptm_fd, &master, tty, NULL, &ws); 117e271dbb8Schristos } else { 118e271dbb8Schristos if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0) 119e271dbb8Schristos goto fail; 120e271dbb8Schristos pid = fork(); 121e271dbb8Schristos } 122e271dbb8Schristos if (cmd == NULL) { 123e271dbb8Schristos cmd_log_argv(argc, argv, "%s:", __func__); 124*890b6d91Swiz log_debug("%s: cwd=%s, shell=%s", __func__, 125*890b6d91Swiz cwd == NULL ? "" : cwd, shell); 126e271dbb8Schristos } else { 127*890b6d91Swiz log_debug("%s: cmd=%s, cwd=%s, shell=%s", __func__, cmd, 128*890b6d91Swiz cwd == NULL ? "" : cwd, shell); 129e271dbb8Schristos } 130e271dbb8Schristos 131e271dbb8Schristos switch (pid) { 132698d5317Sjmmv case -1: 133e271dbb8Schristos if (~flags & JOB_PTY) { 13499e242abSchristos close(out[0]); 13599e242abSchristos close(out[1]); 136e271dbb8Schristos } 137e271dbb8Schristos goto fail; 138fe99a117Schristos case 0: 139fe99a117Schristos proc_clear_signals(server_proc, 1); 140fe99a117Schristos sigprocmask(SIG_SETMASK, &oldset, NULL); 141698d5317Sjmmv 142e271dbb8Schristos if ((cwd == NULL || chdir(cwd) != 0) && 143e271dbb8Schristos ((home = find_home()) == NULL || chdir(home) != 0) && 144e271dbb8Schristos chdir("/") != 0) 145e271dbb8Schristos fatal("chdir failed"); 14699e242abSchristos 147f26e8bc9Schristos environ_push(env); 148f26e8bc9Schristos environ_free(env); 149698d5317Sjmmv 150e271dbb8Schristos if (~flags & JOB_PTY) { 15161fba46bSchristos if (dup2(out[1], STDIN_FILENO) == -1) 15261fba46bSchristos fatal("dup2 failed"); 153698d5317Sjmmv if (dup2(out[1], STDOUT_FILENO) == -1) 154698d5317Sjmmv fatal("dup2 failed"); 15561fba46bSchristos if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO) 156698d5317Sjmmv close(out[1]); 157698d5317Sjmmv close(out[0]); 158698d5317Sjmmv 15946548964Swiz nullfd = open(_PATH_DEVNULL, O_RDWR); 16030744affSchristos if (nullfd == -1) 161698d5317Sjmmv fatal("open failed"); 162698d5317Sjmmv if (dup2(nullfd, STDERR_FILENO) == -1) 163698d5317Sjmmv fatal("dup2 failed"); 16461fba46bSchristos if (nullfd != STDERR_FILENO) 165698d5317Sjmmv close(nullfd); 166e271dbb8Schristos } 167698d5317Sjmmv closefrom(STDERR_FILENO + 1); 168698d5317Sjmmv 169e271dbb8Schristos if (cmd != NULL) { 170*890b6d91Swiz setenv("SHELL", shell, 1); 171*890b6d91Swiz execl(shell, argv0, "-c", cmd, (char *)NULL); 172698d5317Sjmmv fatal("execl failed"); 173e271dbb8Schristos } else { 174e271dbb8Schristos argvp = cmd_copy_argv(argc, argv); 175e271dbb8Schristos execvp(argvp[0], argvp); 176e271dbb8Schristos fatal("execvp failed"); 177e271dbb8Schristos } 1780f3d2746Sjmmv } 1790f3d2746Sjmmv 180fe99a117Schristos sigprocmask(SIG_SETMASK, &oldset, NULL); 181f26e8bc9Schristos environ_free(env); 182*890b6d91Swiz free(argv0); 183698d5317Sjmmv 1840f3d2746Sjmmv job = xmalloc(sizeof *job); 18599e242abSchristos job->state = JOB_RUNNING; 186c7e17de0Schristos job->flags = flags; 18799e242abSchristos 188e271dbb8Schristos if (cmd != NULL) 1890f3d2746Sjmmv job->cmd = xstrdup(cmd); 190e271dbb8Schristos else 191e271dbb8Schristos job->cmd = cmd_stringify_argv(argc, argv); 1920f3d2746Sjmmv job->pid = pid; 19346548964Swiz strlcpy(job->tty, tty, sizeof job->tty); 1940f3d2746Sjmmv job->status = 0; 195698d5317Sjmmv 196fe99a117Schristos LIST_INSERT_HEAD(&all_jobs, job, entry); 1970f3d2746Sjmmv 198fe99a117Schristos job->updatecb = updatecb; 199fe99a117Schristos job->completecb = completecb; 200fe99a117Schristos job->freecb = freecb; 2010f3d2746Sjmmv job->data = data; 2020f3d2746Sjmmv 203e271dbb8Schristos if (~flags & JOB_PTY) { 204e271dbb8Schristos close(out[1]); 2050f3d2746Sjmmv job->fd = out[0]; 206e271dbb8Schristos } else 207e271dbb8Schristos job->fd = master; 2080f3d2746Sjmmv setblocking(job->fd, 0); 2090f3d2746Sjmmv 210fe99a117Schristos job->event = bufferevent_new(job->fd, job_read_callback, 211fe99a117Schristos job_write_callback, job_error_callback, job); 2120a274e86Schristos if (job->event == NULL) 2130a274e86Schristos fatalx("out of memory"); 21461fba46bSchristos bufferevent_enable(job->event, EV_READ|EV_WRITE); 215698d5317Sjmmv 2160f3d2746Sjmmv log_debug("run job %p: %s, pid %ld", job, job->cmd, (long)job->pid); 2170f3d2746Sjmmv return (job); 218e271dbb8Schristos 219e271dbb8Schristos fail: 220e271dbb8Schristos sigprocmask(SIG_SETMASK, &oldset, NULL); 221e271dbb8Schristos environ_free(env); 222*890b6d91Swiz free(argv0); 223e271dbb8Schristos return (NULL); 224698d5317Sjmmv } 2250f3d2746Sjmmv 22646548964Swiz /* Take job's file descriptor and free the job. */ 22746548964Swiz int 22846548964Swiz job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen) 22946548964Swiz { 23046548964Swiz int fd = job->fd; 23146548964Swiz 23246548964Swiz log_debug("transfer job %p: %s", job, job->cmd); 23346548964Swiz 23446548964Swiz if (pid != NULL) 23546548964Swiz *pid = job->pid; 23646548964Swiz if (tty != NULL) 23746548964Swiz strlcpy(tty, job->tty, ttylen); 23846548964Swiz 23946548964Swiz LIST_REMOVE(job, entry); 24046548964Swiz free(job->cmd); 24146548964Swiz 24246548964Swiz if (job->freecb != NULL && job->data != NULL) 24346548964Swiz job->freecb(job->data); 24446548964Swiz 24546548964Swiz if (job->event != NULL) 24646548964Swiz bufferevent_free(job->event); 24746548964Swiz 24846548964Swiz free(job); 24946548964Swiz return (fd); 25046548964Swiz } 25146548964Swiz 2520f3d2746Sjmmv /* Kill and free an individual job. */ 2530f3d2746Sjmmv void 2540f3d2746Sjmmv job_free(struct job *job) 2550f3d2746Sjmmv { 2560f3d2746Sjmmv log_debug("free job %p: %s", job, job->cmd); 2570f3d2746Sjmmv 258fe99a117Schristos LIST_REMOVE(job, entry); 25961fba46bSchristos free(job->cmd); 2600f3d2746Sjmmv 261fe99a117Schristos if (job->freecb != NULL && job->data != NULL) 262fe99a117Schristos job->freecb(job->data); 2630f3d2746Sjmmv 2640f3d2746Sjmmv if (job->pid != -1) 2650f3d2746Sjmmv kill(job->pid, SIGTERM); 2660f3d2746Sjmmv if (job->event != NULL) 2670f3d2746Sjmmv bufferevent_free(job->event); 26861fba46bSchristos if (job->fd != -1) 26961fba46bSchristos close(job->fd); 2700f3d2746Sjmmv 27161fba46bSchristos free(job); 27261fba46bSchristos } 27361fba46bSchristos 274e271dbb8Schristos /* Resize job. */ 275e271dbb8Schristos void 276e271dbb8Schristos job_resize(struct job *job, u_int sx, u_int sy) 277e271dbb8Schristos { 278e271dbb8Schristos struct winsize ws; 279e271dbb8Schristos 280e271dbb8Schristos if (job->fd == -1 || (~job->flags & JOB_PTY)) 281e271dbb8Schristos return; 282e271dbb8Schristos 283e271dbb8Schristos log_debug("resize job %p: %ux%u", job, sx, sy); 284e271dbb8Schristos 285e271dbb8Schristos memset(&ws, 0, sizeof ws); 286e271dbb8Schristos ws.ws_col = sx; 287e271dbb8Schristos ws.ws_row = sy; 288e271dbb8Schristos if (ioctl(job->fd, TIOCSWINSZ, &ws) == -1) 289e271dbb8Schristos fatal("ioctl failed"); 290e271dbb8Schristos } 291e271dbb8Schristos 292fe99a117Schristos /* Job buffer read callback. */ 293fe99a117Schristos static void 294fe99a117Schristos job_read_callback(__unused struct bufferevent *bufev, void *data) 295fe99a117Schristos { 296fe99a117Schristos struct job *job = data; 297fe99a117Schristos 298fe99a117Schristos if (job->updatecb != NULL) 299fe99a117Schristos job->updatecb(job); 300fe99a117Schristos } 301fe99a117Schristos 302fe99a117Schristos /* 303fe99a117Schristos * Job buffer write callback. Fired when the buffer falls below watermark 304fe99a117Schristos * (default is empty). If all the data has been written, disable the write 305fe99a117Schristos * event. 306fe99a117Schristos */ 307e9a2d6faSchristos static void 308f26e8bc9Schristos job_write_callback(__unused struct bufferevent *bufev, void *data) 30961fba46bSchristos { 31061fba46bSchristos struct job *job = data; 31161fba46bSchristos size_t len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event)); 31261fba46bSchristos 31361fba46bSchristos log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd, 31461fba46bSchristos (long) job->pid, len); 31561fba46bSchristos 316e271dbb8Schristos if (len == 0 && (~job->flags & JOB_KEEPWRITE)) { 31761fba46bSchristos shutdown(job->fd, SHUT_WR); 31861fba46bSchristos bufferevent_disable(job->event, EV_WRITE); 31961fba46bSchristos } 320698d5317Sjmmv } 321698d5317Sjmmv 322698d5317Sjmmv /* Job buffer error callback. */ 323e9a2d6faSchristos static void 324fe99a117Schristos job_error_callback(__unused struct bufferevent *bufev, __unused short events, 325f26e8bc9Schristos void *data) 326698d5317Sjmmv { 327698d5317Sjmmv struct job *job = data; 328698d5317Sjmmv 3290f3d2746Sjmmv log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid); 330698d5317Sjmmv 33199e242abSchristos if (job->state == JOB_DEAD) { 332fe99a117Schristos if (job->completecb != NULL) 333fe99a117Schristos job->completecb(job); 334698d5317Sjmmv job_free(job); 3350f3d2746Sjmmv } else { 3360f3d2746Sjmmv bufferevent_disable(job->event, EV_READ); 33799e242abSchristos job->state = JOB_CLOSED; 338698d5317Sjmmv } 339698d5317Sjmmv } 340698d5317Sjmmv 341698d5317Sjmmv /* Job died (waitpid() returned its pid). */ 342698d5317Sjmmv void 3430a274e86Schristos job_check_died(pid_t pid, int status) 344698d5317Sjmmv { 3450a274e86Schristos struct job *job; 3460a274e86Schristos 3470a274e86Schristos LIST_FOREACH(job, &all_jobs, entry) { 3480a274e86Schristos if (pid == job->pid) 3490a274e86Schristos break; 3500a274e86Schristos } 3510a274e86Schristos if (job == NULL) 3520a274e86Schristos return; 353e271dbb8Schristos if (WIFSTOPPED(status)) { 354e271dbb8Schristos if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU) 355e271dbb8Schristos return; 356e271dbb8Schristos killpg(job->pid, SIGCONT); 357e271dbb8Schristos return; 358e271dbb8Schristos } 3590f3d2746Sjmmv log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); 3600f3d2746Sjmmv 361698d5317Sjmmv job->status = status; 362698d5317Sjmmv 36399e242abSchristos if (job->state == JOB_CLOSED) { 364fe99a117Schristos if (job->completecb != NULL) 365fe99a117Schristos job->completecb(job); 366698d5317Sjmmv job_free(job); 36799e242abSchristos } else { 368698d5317Sjmmv job->pid = -1; 36999e242abSchristos job->state = JOB_DEAD; 37099e242abSchristos } 371698d5317Sjmmv } 3720a274e86Schristos 3730a274e86Schristos /* Get job status. */ 3740a274e86Schristos int 3750a274e86Schristos job_get_status(struct job *job) 3760a274e86Schristos { 3770a274e86Schristos return (job->status); 3780a274e86Schristos } 3790a274e86Schristos 3800a274e86Schristos /* Get job data. */ 3810a274e86Schristos void * 3820a274e86Schristos job_get_data(struct job *job) 3830a274e86Schristos { 3840a274e86Schristos return (job->data); 3850a274e86Schristos } 3860a274e86Schristos 3870a274e86Schristos /* Get job event. */ 3880a274e86Schristos struct bufferevent * 3890a274e86Schristos job_get_event(struct job *job) 3900a274e86Schristos { 3910a274e86Schristos return (job->event); 3920a274e86Schristos } 3930a274e86Schristos 3940a274e86Schristos /* Kill all jobs. */ 3950a274e86Schristos void 3960a274e86Schristos job_kill_all(void) 3970a274e86Schristos { 3980a274e86Schristos struct job *job; 3990a274e86Schristos 4000a274e86Schristos LIST_FOREACH(job, &all_jobs, entry) { 4010a274e86Schristos if (job->pid != -1) 4020a274e86Schristos kill(job->pid, SIGTERM); 4030a274e86Schristos } 4040a274e86Schristos } 4050a274e86Schristos 4060a274e86Schristos /* Are any jobs still running? */ 4070a274e86Schristos int 4080a274e86Schristos job_still_running(void) 4090a274e86Schristos { 4100a274e86Schristos struct job *job; 4110a274e86Schristos 4120a274e86Schristos LIST_FOREACH(job, &all_jobs, entry) { 4130a274e86Schristos if ((~job->flags & JOB_NOWAIT) && job->state == JOB_RUNNING) 4140a274e86Schristos return (1); 4150a274e86Schristos } 4160a274e86Schristos return (0); 4170a274e86Schristos } 4180a274e86Schristos 4190a274e86Schristos /* Print job summary. */ 4200a274e86Schristos void 4210a274e86Schristos job_print_summary(struct cmdq_item *item, int blank) 4220a274e86Schristos { 4230a274e86Schristos struct job *job; 4240a274e86Schristos u_int n = 0; 4250a274e86Schristos 4260a274e86Schristos LIST_FOREACH(job, &all_jobs, entry) { 4270a274e86Schristos if (blank) { 4280a274e86Schristos cmdq_print(item, "%s", ""); 4290a274e86Schristos blank = 0; 4300a274e86Schristos } 4310a274e86Schristos cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]", 4320a274e86Schristos n, job->cmd, job->fd, (long)job->pid, job->status); 4330a274e86Schristos n++; 4340a274e86Schristos } 4350a274e86Schristos } 436