Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD$ */
      2 
      3 /*
      4  * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott (at) 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 <signal.h>
     26 #include <stdlib.h>
     27 #include <string.h>
     28 #include <unistd.h>
     29 
     30 #include "tmux.h"
     31 
     32 /*
     33  * Job scheduling. Run queued commands in the background and record their
     34  * output.
     35  */
     36 
     37 static void	job_read_callback(struct bufferevent *, void *);
     38 static void	job_write_callback(struct bufferevent *, void *);
     39 static void	job_error_callback(struct bufferevent *, short, void *);
     40 
     41 /* A single job. */
     42 struct job {
     43 	enum {
     44 		JOB_RUNNING,
     45 		JOB_DEAD,
     46 		JOB_CLOSED
     47 	} state;
     48 
     49 	int			 flags;
     50 
     51 	char			*cmd;
     52 	pid_t			 pid;
     53 	char			 tty[TTY_NAME_MAX];
     54 	int			 status;
     55 
     56 	int			 fd;
     57 	struct bufferevent	*event;
     58 
     59 	job_update_cb		 updatecb;
     60 	job_complete_cb		 completecb;
     61 	job_free_cb		 freecb;
     62 	void			*data;
     63 
     64 	LIST_ENTRY(job)		 entry;
     65 };
     66 
     67 /* All jobs list. */
     68 static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs);
     69 
     70 /* Start a job running. */
     71 struct job *
     72 job_run(const char *cmd, int argc, char **argv, struct environ *e,
     73     struct session *s, const char *cwd, job_update_cb updatecb,
     74     job_complete_cb completecb, job_free_cb freecb, void *data, int flags,
     75     int sx, int sy)
     76 {
     77 	struct job	 *job;
     78 	struct environ	 *env;
     79 	pid_t		  pid;
     80 	int		  nullfd, out[2], master, do_close = 1;
     81 	const char	 *home, *shell;
     82 	sigset_t	  set, oldset;
     83 	struct winsize	  ws;
     84 	char		**argvp, tty[TTY_NAME_MAX], *argv0;
     85 	struct options	 *oo;
     86 
     87 	/*
     88 	 * Do not set TERM during .tmux.conf (second argument here), it is nice
     89 	 * to be able to use if-shell to decide on default-terminal based on
     90 	 * outside TERM.
     91 	 */
     92 	env = environ_for_session(s, !cfg_finished);
     93 	if (e != NULL)
     94 		environ_copy(e, env);
     95 
     96 	if (~flags & JOB_DEFAULTSHELL)
     97 		shell = _PATH_BSHELL;
     98 	else {
     99 		if (s != NULL)
    100 			oo = s->options;
    101 		else
    102 			oo = global_s_options;
    103 		shell = options_get_string(oo, "default-shell");
    104 		if (!checkshell(shell))
    105 			shell = _PATH_BSHELL;
    106 	}
    107 	argv0 = shell_argv0(shell, 0);
    108 
    109 	sigfillset(&set);
    110 	sigprocmask(SIG_BLOCK, &set, &oldset);
    111 
    112 	if (flags & JOB_PTY) {
    113 		memset(&ws, 0, sizeof ws);
    114 		ws.ws_col = sx;
    115 		ws.ws_row = sy;
    116 		pid = fdforkpty(ptm_fd, &master, tty, NULL, &ws);
    117 	} else {
    118 		if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0)
    119 			goto fail;
    120 		pid = fork();
    121 	}
    122 	if (cmd == NULL) {
    123 		cmd_log_argv(argc, argv, "%s:", __func__);
    124 		log_debug("%s: cwd=%s, shell=%s", __func__,
    125 		    cwd == NULL ? "" : cwd, shell);
    126 	} else {
    127 		log_debug("%s: cmd=%s, cwd=%s, shell=%s", __func__, cmd,
    128 		    cwd == NULL ? "" : cwd, shell);
    129 	}
    130 
    131 	switch (pid) {
    132 	case -1:
    133 		if (~flags & JOB_PTY) {
    134 			close(out[0]);
    135 			close(out[1]);
    136 		}
    137 		goto fail;
    138 	case 0:
    139 		proc_clear_signals(server_proc, 1);
    140 		sigprocmask(SIG_SETMASK, &oldset, NULL);
    141 
    142 		if (cwd != NULL) {
    143 			if (chdir(cwd) == 0)
    144 				environ_set(env, "PWD", 0, "%s", cwd);
    145 			else if ((home = find_home()) != NULL && chdir(home) == 0)
    146 				environ_set(env, "PWD", 0, "%s", home);
    147 			else if (chdir("/") == 0)
    148 				environ_set(env, "PWD", 0, "/");
    149 			else
    150 				fatal("chdir failed");
    151 		}
    152 
    153 		environ_push(env);
    154 		environ_free(env);
    155 
    156 		if (~flags & JOB_PTY) {
    157 			if (dup2(out[1], STDIN_FILENO) == -1)
    158 				fatal("dup2 failed");
    159 			do_close = do_close && out[1] != STDIN_FILENO;
    160 			if (dup2(out[1], STDOUT_FILENO) == -1)
    161 				fatal("dup2 failed");
    162 			do_close = do_close && out[1] != STDOUT_FILENO;
    163 			if (flags & JOB_SHOWSTDERR) {
    164 				if (dup2(out[1], STDERR_FILENO) == -1)
    165 					fatal("dup2 failed");
    166 				do_close = do_close && out[1] != STDERR_FILENO;
    167 			} else {
    168 				nullfd = open(_PATH_DEVNULL, O_RDWR);
    169 				if (nullfd == -1)
    170 					fatal("open failed");
    171 				if (dup2(nullfd, STDERR_FILENO) == -1)
    172 					fatal("dup2 failed");
    173 				if (nullfd != STDERR_FILENO)
    174 					close(nullfd);
    175 			}
    176 			if (do_close)
    177 				close(out[1]);
    178 			close(out[0]);
    179 		}
    180 		closefrom(STDERR_FILENO + 1);
    181 
    182 		if (cmd != NULL) {
    183 			if (flags & JOB_DEFAULTSHELL)
    184 				setenv("SHELL", shell, 1);
    185 			execl(shell, argv0, "-c", cmd, (char *)NULL);
    186 			fatal("execl failed");
    187 		} else {
    188 			argvp = cmd_copy_argv(argc, argv);
    189 			execvp(argvp[0], argvp);
    190 			fatal("execvp failed");
    191 		}
    192 	}
    193 
    194 	sigprocmask(SIG_SETMASK, &oldset, NULL);
    195 	environ_free(env);
    196 	free(argv0);
    197 
    198 	job = xcalloc(1, sizeof *job);
    199 	job->state = JOB_RUNNING;
    200 	job->flags = flags;
    201 
    202 	if (cmd != NULL)
    203 		job->cmd = xstrdup(cmd);
    204 	else
    205 		job->cmd = cmd_stringify_argv(argc, argv);
    206 	job->pid = pid;
    207 	if (flags & JOB_PTY)
    208 		strlcpy(job->tty, tty, sizeof job->tty);
    209 	job->status = 0;
    210 
    211 	LIST_INSERT_HEAD(&all_jobs, job, entry);
    212 
    213 	job->updatecb = updatecb;
    214 	job->completecb = completecb;
    215 	job->freecb = freecb;
    216 	job->data = data;
    217 
    218 	if (~flags & JOB_PTY) {
    219 		close(out[1]);
    220 		job->fd = out[0];
    221 	} else
    222 		job->fd = master;
    223 	setblocking(job->fd, 0);
    224 
    225 	job->event = bufferevent_new(job->fd, job_read_callback,
    226 	    job_write_callback, job_error_callback, job);
    227 	if (job->event == NULL)
    228 		fatalx("out of memory");
    229 	bufferevent_enable(job->event, EV_READ|EV_WRITE);
    230 
    231 	log_debug("run job %p: %s, pid %ld", job, job->cmd, (long)job->pid);
    232 	return (job);
    233 
    234 fail:
    235 	sigprocmask(SIG_SETMASK, &oldset, NULL);
    236 	environ_free(env);
    237 	free(argv0);
    238 	return (NULL);
    239 }
    240 
    241 /* Take job's file descriptor and free the job. */
    242 int
    243 job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen)
    244 {
    245 	int	fd = job->fd;
    246 
    247 	log_debug("transfer job %p: %s", job, job->cmd);
    248 
    249 	if (pid != NULL)
    250 		*pid = job->pid;
    251 	if (tty != NULL)
    252 		strlcpy(tty, job->tty, ttylen);
    253 
    254 	LIST_REMOVE(job, entry);
    255 	free(job->cmd);
    256 
    257 	if (job->freecb != NULL && job->data != NULL)
    258 		job->freecb(job->data);
    259 
    260 	if (job->event != NULL)
    261 		bufferevent_free(job->event);
    262 
    263 	free(job);
    264 	return (fd);
    265 }
    266 
    267 /* Kill and free an individual job. */
    268 void
    269 job_free(struct job *job)
    270 {
    271 	log_debug("free job %p: %s", job, job->cmd);
    272 
    273 	LIST_REMOVE(job, entry);
    274 	free(job->cmd);
    275 
    276 	if (job->freecb != NULL && job->data != NULL)
    277 		job->freecb(job->data);
    278 
    279 	if (job->pid != -1)
    280 		kill(job->pid, SIGTERM);
    281 	if (job->event != NULL)
    282 		bufferevent_free(job->event);
    283 	if (job->fd != -1)
    284 		close(job->fd);
    285 
    286 	free(job);
    287 }
    288 
    289 /* Resize job. */
    290 void
    291 job_resize(struct job *job, u_int sx, u_int sy)
    292 {
    293 	struct winsize	 ws;
    294 
    295 	if (job->fd == -1 || (~job->flags & JOB_PTY))
    296 		return;
    297 
    298 	log_debug("resize job %p: %ux%u", job, sx, sy);
    299 
    300 	memset(&ws, 0, sizeof ws);
    301 	ws.ws_col = sx;
    302 	ws.ws_row = sy;
    303 	if (ioctl(job->fd, TIOCSWINSZ, &ws) == -1)
    304 		fatal("ioctl failed");
    305 }
    306 
    307 /* Job buffer read callback. */
    308 static void
    309 job_read_callback(__unused struct bufferevent *bufev, void *data)
    310 {
    311 	struct job	*job = data;
    312 
    313 	if (job->updatecb != NULL)
    314 		job->updatecb(job);
    315 }
    316 
    317 /*
    318  * Job buffer write callback. Fired when the buffer falls below watermark
    319  * (default is empty). If all the data has been written, disable the write
    320  * event.
    321  */
    322 static void
    323 job_write_callback(__unused struct bufferevent *bufev, void *data)
    324 {
    325 	struct job	*job = data;
    326 	size_t		 len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event));
    327 
    328 	log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd,
    329 	    (long) job->pid, len);
    330 
    331 	if (len == 0 && (~job->flags & JOB_KEEPWRITE)) {
    332 		shutdown(job->fd, SHUT_WR);
    333 		bufferevent_disable(job->event, EV_WRITE);
    334 	}
    335 }
    336 
    337 /* Job buffer error callback. */
    338 static void
    339 job_error_callback(__unused struct bufferevent *bufev, __unused short events,
    340     void *data)
    341 {
    342 	struct job	*job = data;
    343 
    344 	log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid);
    345 
    346 	if (job->state == JOB_DEAD) {
    347 		if (job->completecb != NULL)
    348 			job->completecb(job);
    349 		job_free(job);
    350 	} else {
    351 		bufferevent_disable(job->event, EV_READ);
    352 		job->state = JOB_CLOSED;
    353 	}
    354 }
    355 
    356 /* Job died (waitpid() returned its pid). */
    357 void
    358 job_check_died(pid_t pid, int status)
    359 {
    360 	struct job	*job;
    361 
    362 	LIST_FOREACH(job, &all_jobs, entry) {
    363 		if (pid == job->pid)
    364 			break;
    365 	}
    366 	if (job == NULL)
    367 		return;
    368 	if (WIFSTOPPED(status)) {
    369 		if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
    370 			return;
    371 		killpg(job->pid, SIGCONT);
    372 		return;
    373 	}
    374 	log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid);
    375 
    376 	job->status = status;
    377 
    378 	if (job->state == JOB_CLOSED) {
    379 		if (job->completecb != NULL)
    380 			job->completecb(job);
    381 		job_free(job);
    382 	} else {
    383 		job->pid = -1;
    384 		job->state = JOB_DEAD;
    385 	}
    386 }
    387 
    388 /* Get job status. */
    389 int
    390 job_get_status(struct job *job)
    391 {
    392 	return (job->status);
    393 }
    394 
    395 /* Get job data. */
    396 void *
    397 job_get_data(struct job *job)
    398 {
    399 	return (job->data);
    400 }
    401 
    402 /* Get job event. */
    403 struct bufferevent *
    404 job_get_event(struct job *job)
    405 {
    406 	return (job->event);
    407 }
    408 
    409 /* Kill all jobs. */
    410 void
    411 job_kill_all(void)
    412 {
    413 	struct job	*job;
    414 
    415 	LIST_FOREACH(job, &all_jobs, entry) {
    416 		if (job->pid != -1)
    417 			kill(job->pid, SIGTERM);
    418 	}
    419 }
    420 
    421 /* Are any jobs still running? */
    422 int
    423 job_still_running(void)
    424 {
    425 	struct job	*job;
    426 
    427 	LIST_FOREACH(job, &all_jobs, entry) {
    428 		if ((~job->flags & JOB_NOWAIT) && job->state == JOB_RUNNING)
    429 			return (1);
    430 	}
    431 	return (0);
    432 }
    433 
    434 /* Print job summary. */
    435 void
    436 job_print_summary(struct cmdq_item *item, int blank)
    437 {
    438 	struct job	*job;
    439 	u_int		 n = 0;
    440 
    441 	LIST_FOREACH(job, &all_jobs, entry) {
    442 		if (blank) {
    443 			cmdq_print(item, "%s", "");
    444 			blank = 0;
    445 		}
    446 		cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]",
    447 		    n, job->cmd, job->fd, (long)job->pid, job->status);
    448 		n++;
    449 	}
    450 }
    451