xref: /netbsd-src/external/bsd/tmux/dist/job.c (revision 890b6d91a44b7fcb2dfbcbc1e93463086e462d2d)
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