xref: /openbsd-src/usr.bin/tmux/job.c (revision de8cc8edbc71bd3e3bc7fbffa27ba0e564c37d8b)
1 /* $OpenBSD: job.c,v 1.59 2021/02/19 09:09:16 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/ioctl.h>
21 #include <sys/socket.h>
22 #include <sys/wait.h>
23 
24 #include <fcntl.h>
25 #include <paths.h>
26 #include <signal.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <util.h>
31 
32 #include "tmux.h"
33 
34 /*
35  * Job scheduling. Run queued commands in the background and record their
36  * output.
37  */
38 
39 static void	job_read_callback(struct bufferevent *, void *);
40 static void	job_write_callback(struct bufferevent *, void *);
41 static void	job_error_callback(struct bufferevent *, short, void *);
42 
43 /* A single job. */
44 struct job {
45 	enum {
46 		JOB_RUNNING,
47 		JOB_DEAD,
48 		JOB_CLOSED
49 	} state;
50 
51 	int			 flags;
52 
53 	char			*cmd;
54 	pid_t			 pid;
55 	int			 status;
56 
57 	int			 fd;
58 	struct bufferevent	*event;
59 
60 	job_update_cb		 updatecb;
61 	job_complete_cb		 completecb;
62 	job_free_cb		 freecb;
63 	void			*data;
64 
65 	LIST_ENTRY(job)		 entry;
66 };
67 
68 /* All jobs list. */
69 static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs);
70 
71 /* Start a job running, if it isn't already. */
72 struct job *
73 job_run(const char *cmd, struct session *s, const char *cwd,
74     job_update_cb updatecb, job_complete_cb completecb, job_free_cb freecb,
75     void *data, int flags, int sx, int sy)
76 {
77 	struct job	*job;
78 	struct environ	*env;
79 	pid_t		 pid;
80 	int		 nullfd, out[2], master;
81 	const char	*home;
82 	sigset_t	 set, oldset;
83 	struct winsize	 ws;
84 
85 	/*
86 	 * Do not set TERM during .tmux.conf, it is nice to be able to use
87 	 * if-shell to decide on default-terminal based on outside TERM.
88 	 */
89 	env = environ_for_session(s, !cfg_finished);
90 
91 	sigfillset(&set);
92 	sigprocmask(SIG_BLOCK, &set, &oldset);
93 
94 	if (flags & JOB_PTY) {
95 		memset(&ws, 0, sizeof ws);
96 		ws.ws_col = sx;
97 		ws.ws_row = sy;
98 		pid = fdforkpty(ptm_fd, &master, NULL, NULL, &ws);
99 	} else {
100 		if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0)
101 			goto fail;
102 		pid = fork();
103 	}
104 	log_debug("%s: cmd=%s, cwd=%s", __func__, cmd, cwd == NULL ? "" : cwd);
105 
106 	switch (pid) {
107 	case -1:
108 		if (~flags & JOB_PTY) {
109 			close(out[0]);
110 			close(out[1]);
111 		}
112 		goto fail;
113 	case 0:
114 		proc_clear_signals(server_proc, 1);
115 		sigprocmask(SIG_SETMASK, &oldset, NULL);
116 
117 		if ((cwd == NULL || chdir(cwd) != 0) &&
118 		    ((home = find_home()) == NULL || chdir(home) != 0) &&
119 		    chdir("/") != 0)
120 			fatal("chdir failed");
121 
122 		environ_push(env);
123 		environ_free(env);
124 
125 		if (~flags & JOB_PTY) {
126 			if (dup2(out[1], STDIN_FILENO) == -1)
127 				fatal("dup2 failed");
128 			if (dup2(out[1], STDOUT_FILENO) == -1)
129 				fatal("dup2 failed");
130 			if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO)
131 				close(out[1]);
132 			close(out[0]);
133 
134 			nullfd = open(_PATH_DEVNULL, O_RDWR, 0);
135 			if (nullfd == -1)
136 				fatal("open failed");
137 			if (dup2(nullfd, STDERR_FILENO) == -1)
138 				fatal("dup2 failed");
139 			if (nullfd != STDERR_FILENO)
140 				close(nullfd);
141 		}
142 		closefrom(STDERR_FILENO + 1);
143 
144 		execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL);
145 		fatal("execl failed");
146 	}
147 
148 	sigprocmask(SIG_SETMASK, &oldset, NULL);
149 	environ_free(env);
150 
151 	job = xmalloc(sizeof *job);
152 	job->state = JOB_RUNNING;
153 	job->flags = flags;
154 
155 	job->cmd = xstrdup(cmd);
156 	job->pid = pid;
157 	job->status = 0;
158 
159 	LIST_INSERT_HEAD(&all_jobs, job, entry);
160 
161 	job->updatecb = updatecb;
162 	job->completecb = completecb;
163 	job->freecb = freecb;
164 	job->data = data;
165 
166 	if (~flags & JOB_PTY) {
167 		close(out[1]);
168 		job->fd = out[0];
169 	} else
170 		job->fd = master;
171 	setblocking(job->fd, 0);
172 
173 	job->event = bufferevent_new(job->fd, job_read_callback,
174 	    job_write_callback, job_error_callback, job);
175 	if (job->event == NULL)
176 		fatalx("out of memory");
177 	bufferevent_enable(job->event, EV_READ|EV_WRITE);
178 
179 	log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid);
180 	return (job);
181 
182 fail:
183 	sigprocmask(SIG_SETMASK, &oldset, NULL);
184 	environ_free(env);
185 	return (NULL);
186 }
187 
188 /* Kill and free an individual job. */
189 void
190 job_free(struct job *job)
191 {
192 	log_debug("free job %p: %s", job, job->cmd);
193 
194 	LIST_REMOVE(job, entry);
195 	free(job->cmd);
196 
197 	if (job->freecb != NULL && job->data != NULL)
198 		job->freecb(job->data);
199 
200 	if (job->pid != -1)
201 		kill(job->pid, SIGTERM);
202 	if (job->event != NULL)
203 		bufferevent_free(job->event);
204 	if (job->fd != -1)
205 		close(job->fd);
206 
207 	free(job);
208 }
209 
210 /* Resize job. */
211 void
212 job_resize(struct job *job, u_int sx, u_int sy)
213 {
214 	struct winsize	 ws;
215 
216 	if (job->fd == -1 || (~job->flags & JOB_PTY))
217 		return;
218 
219 	log_debug("resize job %p: %ux%u", job, sx, sy);
220 
221 	memset(&ws, 0, sizeof ws);
222 	ws.ws_col = sx;
223 	ws.ws_row = sy;
224 	if (ioctl(job->fd, TIOCSWINSZ, &ws) == -1)
225 		fatal("ioctl failed");
226 }
227 
228 /* Job buffer read callback. */
229 static void
230 job_read_callback(__unused struct bufferevent *bufev, void *data)
231 {
232 	struct job	*job = data;
233 
234 	if (job->updatecb != NULL)
235 		job->updatecb(job);
236 }
237 
238 /*
239  * Job buffer write callback. Fired when the buffer falls below watermark
240  * (default is empty). If all the data has been written, disable the write
241  * event.
242  */
243 static void
244 job_write_callback(__unused struct bufferevent *bufev, void *data)
245 {
246 	struct job	*job = data;
247 	size_t		 len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event));
248 
249 	log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd,
250 	    (long) job->pid, len);
251 
252 	if (len == 0 && (~job->flags & JOB_KEEPWRITE)) {
253 		shutdown(job->fd, SHUT_WR);
254 		bufferevent_disable(job->event, EV_WRITE);
255 	}
256 }
257 
258 /* Job buffer error callback. */
259 static void
260 job_error_callback(__unused struct bufferevent *bufev, __unused short events,
261     void *data)
262 {
263 	struct job	*job = data;
264 
265 	log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid);
266 
267 	if (job->state == JOB_DEAD) {
268 		if (job->completecb != NULL)
269 			job->completecb(job);
270 		job_free(job);
271 	} else {
272 		bufferevent_disable(job->event, EV_READ);
273 		job->state = JOB_CLOSED;
274 	}
275 }
276 
277 /* Job died (waitpid() returned its pid). */
278 void
279 job_check_died(pid_t pid, int status)
280 {
281 	struct job	*job;
282 
283 	LIST_FOREACH(job, &all_jobs, entry) {
284 		if (pid == job->pid)
285 			break;
286 	}
287 	if (job == NULL)
288 		return;
289 	if (WIFSTOPPED(status)) {
290 		if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
291 			return;
292 		killpg(job->pid, SIGCONT);
293 		return;
294 	}
295 	log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid);
296 
297 	job->status = status;
298 
299 	if (job->state == JOB_CLOSED) {
300 		if (job->completecb != NULL)
301 			job->completecb(job);
302 		job_free(job);
303 	} else {
304 		job->pid = -1;
305 		job->state = JOB_DEAD;
306 	}
307 }
308 
309 /* Get job status. */
310 int
311 job_get_status(struct job *job)
312 {
313 	return (job->status);
314 }
315 
316 /* Get job data. */
317 void *
318 job_get_data(struct job *job)
319 {
320 	return (job->data);
321 }
322 
323 /* Get job event. */
324 struct bufferevent *
325 job_get_event(struct job *job)
326 {
327 	return (job->event);
328 }
329 
330 /* Kill all jobs. */
331 void
332 job_kill_all(void)
333 {
334 	struct job	*job;
335 
336 	LIST_FOREACH(job, &all_jobs, entry) {
337 		if (job->pid != -1)
338 			kill(job->pid, SIGTERM);
339 	}
340 }
341 
342 /* Are any jobs still running? */
343 int
344 job_still_running(void)
345 {
346 	struct job	*job;
347 
348 	LIST_FOREACH(job, &all_jobs, entry) {
349 		if ((~job->flags & JOB_NOWAIT) && job->state == JOB_RUNNING)
350 			return (1);
351 	}
352 	return (0);
353 }
354 
355 /* Print job summary. */
356 void
357 job_print_summary(struct cmdq_item *item, int blank)
358 {
359 	struct job	*job;
360 	u_int		 n = 0;
361 
362 	LIST_FOREACH(job, &all_jobs, entry) {
363 		if (blank) {
364 			cmdq_print(item, "%s", "");
365 			blank = 0;
366 		}
367 		cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]",
368 		    n, job->cmd, job->fd, (long)job->pid, job->status);
369 		n++;
370 	}
371 }
372