1 /* $OpenBSD: job.c,v 1.49 2018/03/08 08:09:10 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/socket.h> 21 22 #include <fcntl.h> 23 #include <paths.h> 24 #include <signal.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <unistd.h> 28 29 #include "tmux.h" 30 31 /* 32 * Job scheduling. Run queued commands in the background and record their 33 * output. 34 */ 35 36 static void job_read_callback(struct bufferevent *, void *); 37 static void job_write_callback(struct bufferevent *, void *); 38 static void job_error_callback(struct bufferevent *, short, void *); 39 40 /* All jobs list. */ 41 struct joblist all_jobs = LIST_HEAD_INITIALIZER(all_jobs); 42 43 /* Start a job running, if it isn't already. */ 44 struct job * 45 job_run(const char *cmd, struct session *s, const char *cwd, 46 job_update_cb updatecb, job_complete_cb completecb, job_free_cb freecb, 47 void *data, int flags) 48 { 49 struct job *job; 50 struct environ *env; 51 pid_t pid; 52 int nullfd, out[2]; 53 const char *home; 54 sigset_t set, oldset; 55 56 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0) 57 return (NULL); 58 59 /* 60 * Do not set TERM during .tmux.conf, it is nice to be able to use 61 * if-shell to decide on default-terminal based on outside TERM. 62 */ 63 env = environ_for_session(s, !cfg_finished); 64 65 sigfillset(&set); 66 sigprocmask(SIG_BLOCK, &set, &oldset); 67 switch (pid = fork()) { 68 case -1: 69 sigprocmask(SIG_SETMASK, &oldset, NULL); 70 environ_free(env); 71 close(out[0]); 72 close(out[1]); 73 return (NULL); 74 case 0: 75 proc_clear_signals(server_proc, 1); 76 sigprocmask(SIG_SETMASK, &oldset, NULL); 77 78 if (cwd == NULL || chdir(cwd) != 0) { 79 if ((home = find_home()) == NULL || chdir(home) != 0) 80 chdir("/"); 81 } 82 83 environ_push(env); 84 environ_free(env); 85 86 if (dup2(out[1], STDIN_FILENO) == -1) 87 fatal("dup2 failed"); 88 if (dup2(out[1], STDOUT_FILENO) == -1) 89 fatal("dup2 failed"); 90 if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO) 91 close(out[1]); 92 close(out[0]); 93 94 nullfd = open(_PATH_DEVNULL, O_RDWR, 0); 95 if (nullfd < 0) 96 fatal("open failed"); 97 if (dup2(nullfd, STDERR_FILENO) == -1) 98 fatal("dup2 failed"); 99 if (nullfd != STDERR_FILENO) 100 close(nullfd); 101 102 closefrom(STDERR_FILENO + 1); 103 104 execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL); 105 fatal("execl failed"); 106 } 107 108 sigprocmask(SIG_SETMASK, &oldset, NULL); 109 environ_free(env); 110 close(out[1]); 111 112 job = xmalloc(sizeof *job); 113 job->state = JOB_RUNNING; 114 job->flags = flags; 115 116 job->cmd = xstrdup(cmd); 117 job->pid = pid; 118 job->status = 0; 119 120 LIST_INSERT_HEAD(&all_jobs, job, entry); 121 122 job->updatecb = updatecb; 123 job->completecb = completecb; 124 job->freecb = freecb; 125 job->data = data; 126 127 job->fd = out[0]; 128 setblocking(job->fd, 0); 129 130 job->event = bufferevent_new(job->fd, job_read_callback, 131 job_write_callback, job_error_callback, job); 132 bufferevent_enable(job->event, EV_READ|EV_WRITE); 133 134 log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid); 135 return (job); 136 } 137 138 /* Kill and free an individual job. */ 139 void 140 job_free(struct job *job) 141 { 142 log_debug("free job %p: %s", job, job->cmd); 143 144 LIST_REMOVE(job, entry); 145 free(job->cmd); 146 147 if (job->freecb != NULL && job->data != NULL) 148 job->freecb(job->data); 149 150 if (job->pid != -1) 151 kill(job->pid, SIGTERM); 152 if (job->event != NULL) 153 bufferevent_free(job->event); 154 if (job->fd != -1) 155 close(job->fd); 156 157 free(job); 158 } 159 160 /* Job buffer read callback. */ 161 static void 162 job_read_callback(__unused struct bufferevent *bufev, void *data) 163 { 164 struct job *job = data; 165 166 if (job->updatecb != NULL) 167 job->updatecb(job); 168 } 169 170 /* 171 * Job buffer write callback. Fired when the buffer falls below watermark 172 * (default is empty). If all the data has been written, disable the write 173 * event. 174 */ 175 static void 176 job_write_callback(__unused struct bufferevent *bufev, void *data) 177 { 178 struct job *job = data; 179 size_t len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event)); 180 181 log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd, 182 (long) job->pid, len); 183 184 if (len == 0) { 185 shutdown(job->fd, SHUT_WR); 186 bufferevent_disable(job->event, EV_WRITE); 187 } 188 } 189 190 /* Job buffer error callback. */ 191 static void 192 job_error_callback(__unused struct bufferevent *bufev, __unused short events, 193 void *data) 194 { 195 struct job *job = data; 196 197 log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid); 198 199 if (job->state == JOB_DEAD) { 200 if (job->completecb != NULL) 201 job->completecb(job); 202 job_free(job); 203 } else { 204 bufferevent_disable(job->event, EV_READ); 205 job->state = JOB_CLOSED; 206 } 207 } 208 209 /* Job died (waitpid() returned its pid). */ 210 void 211 job_died(struct job *job, int status) 212 { 213 log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); 214 215 job->status = status; 216 217 if (job->state == JOB_CLOSED) { 218 if (job->completecb != NULL) 219 job->completecb(job); 220 job_free(job); 221 } else { 222 job->pid = -1; 223 job->state = JOB_DEAD; 224 } 225 } 226