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