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