xref: /openbsd/usr.bin/tmux/job.c (revision 3d8817e4)
1 /* $OpenBSD: job.c,v 1.25 2011/01/26 01:54:56 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>
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 <string.h>
25 #include <unistd.h>
26 
27 #include "tmux.h"
28 
29 /*
30  * Job scheduling. Run queued commands in the background and record their
31  * output.
32  */
33 
34 void	job_callback(struct bufferevent *, short, void *);
35 
36 /* All jobs list. */
37 struct joblist	all_jobs = LIST_HEAD_INITIALIZER(all_jobs);
38 
39 /* Start a job running, if it isn't already. */
40 struct job *
41 job_run(const char *cmd,
42     void (*callbackfn)(struct job *), void (*freefn)(void *), void *data)
43 {
44 	struct job	*job;
45 	struct environ	 env;
46 	pid_t		 pid;
47 	int		 nullfd, out[2];
48 
49 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0)
50 		return (NULL);
51 
52 	environ_init(&env);
53 	environ_copy(&global_environ, &env);
54 	server_fill_environ(NULL, &env);
55 
56 	switch (pid = fork()) {
57 	case -1:
58 		environ_free(&env);
59 		return (NULL);
60 	case 0:		/* child */
61 		clear_signals(1);
62 
63 		environ_push(&env);
64 		environ_free(&env);
65 
66 		if (dup2(out[1], STDOUT_FILENO) == -1)
67 			fatal("dup2 failed");
68 		if (out[1] != STDOUT_FILENO)
69 			close(out[1]);
70 		close(out[0]);
71 
72 		nullfd = open(_PATH_DEVNULL, O_RDWR, 0);
73 		if (nullfd < 0)
74 			fatal("open failed");
75 		if (dup2(nullfd, STDIN_FILENO) == -1)
76 			fatal("dup2 failed");
77 		if (dup2(nullfd, STDERR_FILENO) == -1)
78 			fatal("dup2 failed");
79 		if (nullfd != STDIN_FILENO && nullfd != STDERR_FILENO)
80 			close(nullfd);
81 
82 		closefrom(STDERR_FILENO + 1);
83 
84 		execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL);
85 		fatal("execl failed");
86 	}
87 
88 	/* parent */
89 	environ_free(&env);
90 	close(out[1]);
91 
92 	job = xmalloc(sizeof *job);
93 	job->cmd = xstrdup(cmd);
94 	job->pid = pid;
95 	job->status = 0;
96 
97 	LIST_INSERT_HEAD(&all_jobs, job, lentry);
98 
99 	job->callbackfn = callbackfn;
100 	job->freefn = freefn;
101 	job->data = data;
102 
103 	job->fd = out[0];
104 	setblocking(job->fd, 0);
105 
106 	job->event = bufferevent_new(job->fd, NULL, NULL, job_callback, job);
107 	bufferevent_enable(job->event, EV_READ);
108 
109 	log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid);
110 	return (job);
111 }
112 
113 /* Kill and free an individual job. */
114 void
115 job_free(struct job *job)
116 {
117 	log_debug("free job %p: %s", job, job->cmd);
118 
119 	LIST_REMOVE(job, lentry);
120 	xfree(job->cmd);
121 
122 	if (job->freefn != NULL && job->data != NULL)
123 		job->freefn(job->data);
124 
125 	if (job->pid != -1)
126 		kill(job->pid, SIGTERM);
127 	if (job->fd != -1)
128 		close(job->fd);
129 	if (job->event != NULL)
130 		bufferevent_free(job->event);
131 
132 	xfree(job);
133 }
134 
135 /* Job buffer error callback. */
136 /* ARGSUSED */
137 void
138 job_callback(unused struct bufferevent *bufev, unused short events, void *data)
139 {
140 	struct job	*job = data;
141 
142 	log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid);
143 
144 	if (job->pid == -1) {
145 		if (job->callbackfn != NULL)
146 			job->callbackfn(job);
147 		job_free(job);
148 	} else {
149 		bufferevent_disable(job->event, EV_READ);
150 		close(job->fd);
151 		job->fd = -1;
152 	}
153 }
154 
155 /* Job died (waitpid() returned its pid). */
156 void
157 job_died(struct job *job, int status)
158 {
159 	log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid);
160 
161 	job->status = status;
162 
163 	if (job->fd == -1) {
164 		if (job->callbackfn != NULL)
165 			job->callbackfn(job);
166 		job_free(job);
167 	} else
168 		job->pid = -1;
169 }
170