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
35static void job_read_callback(struct bufferevent *, void *);
36static void job_write_callback(struct bufferevent *, void *);
37static void job_error_callback(struct bufferevent *, short, void *);
38
39/* A single job. */
40struct 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. */
65static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs);
66
67/* Start a job running, if it isn't already. */
68struct job *
69job_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. */
166void
167job_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. */
188static void
189job_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 */
202static void
203job_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. */
218static void
219job_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). */
237void
238job_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. */
263int
264job_get_status(struct job *job)
265{
266 return (job->status);
267}
268
269/* Get job data. */
270void *
271job_get_data(struct job *job)
272{
273 return (job->data);
274}
275
276/* Get job event. */
277struct bufferevent *
278job_get_event(struct job *job)
279{
280 return (job->event);
281}
282
283/* Kill all jobs. */
284void
285job_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? */
296int
297job_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. */
309void
310job_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