1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2009 Nicholas Marriott <[email protected]> |
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 <signal.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | #include <unistd.h> |
27 | |
28 | #include "tmux.h" |
29 | |
30 | /* |
31 | * Job scheduling. Run queued commands in the background and record their |
32 | * output. |
33 | */ |
34 | |
35 | static void job_read_callback(struct bufferevent *, void *); |
36 | static void job_write_callback(struct bufferevent *, void *); |
37 | static void job_error_callback(struct bufferevent *, short, void *); |
38 | |
39 | /* A single job. */ |
40 | struct job { |
41 | enum { |
42 | JOB_RUNNING, |
43 | JOB_DEAD, |
44 | JOB_CLOSED |
45 | } state; |
46 | |
47 | int flags; |
48 | |
49 | char *cmd; |
50 | pid_t pid; |
51 | int status; |
52 | |
53 | int fd; |
54 | struct bufferevent *event; |
55 | |
56 | job_update_cb updatecb; |
57 | job_complete_cb completecb; |
58 | job_free_cb freecb; |
59 | void *data; |
60 | |
61 | LIST_ENTRY(job) entry; |
62 | }; |
63 | |
64 | /* All jobs list. */ |
65 | static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs); |
66 | |
67 | /* Start a job running, if it isn't already. */ |
68 | struct job * |
69 | job_run(const char *cmd, struct session *s, const char *cwd, |
70 | job_update_cb updatecb, job_complete_cb completecb, job_free_cb freecb, |
71 | void *data, int flags) |
72 | { |
73 | struct job *job; |
74 | struct environ *env; |
75 | pid_t pid; |
76 | int nullfd, out[2]; |
77 | const char *home; |
78 | sigset_t set, oldset; |
79 | |
80 | if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0) |
81 | return (NULL); |
82 | log_debug("%s: cmd=%s, cwd=%s" , __func__, cmd, cwd == NULL ? "" : cwd); |
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 | switch (pid = fork()) { |
93 | case -1: |
94 | sigprocmask(SIG_SETMASK, &oldset, NULL); |
95 | environ_free(env); |
96 | close(out[0]); |
97 | close(out[1]); |
98 | return (NULL); |
99 | case 0: |
100 | proc_clear_signals(server_proc, 1); |
101 | sigprocmask(SIG_SETMASK, &oldset, NULL); |
102 | |
103 | if (cwd == NULL || chdir(cwd) != 0) { |
104 | if ((home = find_home()) == NULL || chdir(home) != 0) |
105 | chdir("/" ); |
106 | } |
107 | |
108 | environ_push(env); |
109 | environ_free(env); |
110 | |
111 | if (dup2(out[1], STDIN_FILENO) == -1) |
112 | fatal("dup2 failed" ); |
113 | if (dup2(out[1], STDOUT_FILENO) == -1) |
114 | fatal("dup2 failed" ); |
115 | if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO) |
116 | close(out[1]); |
117 | close(out[0]); |
118 | |
119 | nullfd = open(_PATH_DEVNULL, O_RDWR, 0); |
120 | if (nullfd == -1) |
121 | fatal("open failed" ); |
122 | if (dup2(nullfd, STDERR_FILENO) == -1) |
123 | fatal("dup2 failed" ); |
124 | if (nullfd != STDERR_FILENO) |
125 | close(nullfd); |
126 | |
127 | closefrom(STDERR_FILENO + 1); |
128 | |
129 | execl(_PATH_BSHELL, "sh" , "-c" , cmd, (char *) NULL); |
130 | fatal("execl failed" ); |
131 | } |
132 | |
133 | sigprocmask(SIG_SETMASK, &oldset, NULL); |
134 | environ_free(env); |
135 | close(out[1]); |
136 | |
137 | job = xmalloc(sizeof *job); |
138 | job->state = JOB_RUNNING; |
139 | job->flags = flags; |
140 | |
141 | job->cmd = xstrdup(cmd); |
142 | job->pid = pid; |
143 | job->status = 0; |
144 | |
145 | LIST_INSERT_HEAD(&all_jobs, job, entry); |
146 | |
147 | job->updatecb = updatecb; |
148 | job->completecb = completecb; |
149 | job->freecb = freecb; |
150 | job->data = data; |
151 | |
152 | job->fd = out[0]; |
153 | setblocking(job->fd, 0); |
154 | |
155 | job->event = bufferevent_new(job->fd, job_read_callback, |
156 | job_write_callback, job_error_callback, job); |
157 | if (job->event == NULL) |
158 | fatalx("out of memory" ); |
159 | bufferevent_enable(job->event, EV_READ|EV_WRITE); |
160 | |
161 | log_debug("run job %p: %s, pid %ld" , job, job->cmd, (long) job->pid); |
162 | return (job); |
163 | } |
164 | |
165 | /* Kill and free an individual job. */ |
166 | void |
167 | job_free(struct job *job) |
168 | { |
169 | log_debug("free job %p: %s" , job, job->cmd); |
170 | |
171 | LIST_REMOVE(job, entry); |
172 | free(job->cmd); |
173 | |
174 | if (job->freecb != NULL && job->data != NULL) |
175 | job->freecb(job->data); |
176 | |
177 | if (job->pid != -1) |
178 | kill(job->pid, SIGTERM); |
179 | if (job->event != NULL) |
180 | bufferevent_free(job->event); |
181 | if (job->fd != -1) |
182 | close(job->fd); |
183 | |
184 | free(job); |
185 | } |
186 | |
187 | /* Job buffer read callback. */ |
188 | static void |
189 | job_read_callback(__unused struct bufferevent *bufev, void *data) |
190 | { |
191 | struct job *job = data; |
192 | |
193 | if (job->updatecb != NULL) |
194 | job->updatecb(job); |
195 | } |
196 | |
197 | /* |
198 | * Job buffer write callback. Fired when the buffer falls below watermark |
199 | * (default is empty). If all the data has been written, disable the write |
200 | * event. |
201 | */ |
202 | static void |
203 | job_write_callback(__unused struct bufferevent *bufev, void *data) |
204 | { |
205 | struct job *job = data; |
206 | size_t len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event)); |
207 | |
208 | log_debug("job write %p: %s, pid %ld, output left %zu" , job, job->cmd, |
209 | (long) job->pid, len); |
210 | |
211 | if (len == 0) { |
212 | shutdown(job->fd, SHUT_WR); |
213 | bufferevent_disable(job->event, EV_WRITE); |
214 | } |
215 | } |
216 | |
217 | /* Job buffer error callback. */ |
218 | static void |
219 | job_error_callback(__unused struct bufferevent *bufev, __unused short events, |
220 | void *data) |
221 | { |
222 | struct job *job = data; |
223 | |
224 | log_debug("job error %p: %s, pid %ld" , job, job->cmd, (long) job->pid); |
225 | |
226 | if (job->state == JOB_DEAD) { |
227 | if (job->completecb != NULL) |
228 | job->completecb(job); |
229 | job_free(job); |
230 | } else { |
231 | bufferevent_disable(job->event, EV_READ); |
232 | job->state = JOB_CLOSED; |
233 | } |
234 | } |
235 | |
236 | /* Job died (waitpid() returned its pid). */ |
237 | void |
238 | job_check_died(pid_t pid, int status) |
239 | { |
240 | struct job *job; |
241 | |
242 | LIST_FOREACH(job, &all_jobs, entry) { |
243 | if (pid == job->pid) |
244 | break; |
245 | } |
246 | if (job == NULL) |
247 | return; |
248 | log_debug("job died %p: %s, pid %ld" , job, job->cmd, (long) job->pid); |
249 | |
250 | job->status = status; |
251 | |
252 | if (job->state == JOB_CLOSED) { |
253 | if (job->completecb != NULL) |
254 | job->completecb(job); |
255 | job_free(job); |
256 | } else { |
257 | job->pid = -1; |
258 | job->state = JOB_DEAD; |
259 | } |
260 | } |
261 | |
262 | /* Get job status. */ |
263 | int |
264 | job_get_status(struct job *job) |
265 | { |
266 | return (job->status); |
267 | } |
268 | |
269 | /* Get job data. */ |
270 | void * |
271 | job_get_data(struct job *job) |
272 | { |
273 | return (job->data); |
274 | } |
275 | |
276 | /* Get job event. */ |
277 | struct bufferevent * |
278 | job_get_event(struct job *job) |
279 | { |
280 | return (job->event); |
281 | } |
282 | |
283 | /* Kill all jobs. */ |
284 | void |
285 | job_kill_all(void) |
286 | { |
287 | struct job *job; |
288 | |
289 | LIST_FOREACH(job, &all_jobs, entry) { |
290 | if (job->pid != -1) |
291 | kill(job->pid, SIGTERM); |
292 | } |
293 | } |
294 | |
295 | /* Are any jobs still running? */ |
296 | int |
297 | job_still_running(void) |
298 | { |
299 | struct job *job; |
300 | |
301 | LIST_FOREACH(job, &all_jobs, entry) { |
302 | if ((~job->flags & JOB_NOWAIT) && job->state == JOB_RUNNING) |
303 | return (1); |
304 | } |
305 | return (0); |
306 | } |
307 | |
308 | /* Print job summary. */ |
309 | void |
310 | job_print_summary(struct cmdq_item *item, int blank) |
311 | { |
312 | struct job *job; |
313 | u_int n = 0; |
314 | |
315 | LIST_FOREACH(job, &all_jobs, entry) { |
316 | if (blank) { |
317 | cmdq_print(item, "%s" , "" ); |
318 | blank = 0; |
319 | } |
320 | cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]" , |
321 | n, job->cmd, job->fd, (long)job->pid, job->status); |
322 | n++; |
323 | } |
324 | } |
325 | |