xref: /netbsd-src/external/bsd/tmux/dist/job.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
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/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 void	job_callback(struct bufferevent *, short, void *);
36 void	job_write_callback(struct bufferevent *, void *);
37 
38 /* All jobs list. */
39 struct joblist	all_jobs = LIST_HEAD_INITIALIZER(all_jobs);
40 
41 /* Start a job running, if it isn't already. */
42 struct job *
43 job_run(const char *cmd, struct session *s, const char *cwd,
44     void (*callbackfn)(struct job *), void (*freefn)(void *), void *data)
45 {
46 	struct job	*job;
47 	struct environ	*env;
48 	pid_t		 pid;
49 	int		 nullfd, out[2];
50 	const char	*home;
51 
52 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0)
53 		return (NULL);
54 
55 	env = environ_create();
56 	environ_copy(global_environ, env);
57 	if (s != NULL)
58 		environ_copy(s->environ, env);
59 	server_fill_environ(s, env);
60 
61 	switch (pid = fork()) {
62 	case -1:
63 		environ_free(env);
64 		close(out[0]);
65 		close(out[1]);
66 		return (NULL);
67 	case 0:		/* child */
68 		clear_signals(1);
69 
70 		if (cwd == NULL || chdir(cwd) != 0) {
71 			if ((home = find_home()) == NULL || chdir(home) != 0)
72 				chdir("/");
73 		}
74 
75 		environ_push(env);
76 		environ_free(env);
77 
78 		if (dup2(out[1], STDIN_FILENO) == -1)
79 			fatal("dup2 failed");
80 		if (dup2(out[1], STDOUT_FILENO) == -1)
81 			fatal("dup2 failed");
82 		if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO)
83 			close(out[1]);
84 		close(out[0]);
85 
86 		nullfd = open(_PATH_DEVNULL, O_RDWR, 0);
87 		if (nullfd < 0)
88 			fatal("open failed");
89 		if (dup2(nullfd, STDERR_FILENO) == -1)
90 			fatal("dup2 failed");
91 		if (nullfd != STDERR_FILENO)
92 			close(nullfd);
93 
94 		closefrom(STDERR_FILENO + 1);
95 
96 		execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL);
97 		fatal("execl failed");
98 	}
99 
100 	/* parent */
101 	environ_free(env);
102 	close(out[1]);
103 
104 	job = xmalloc(sizeof *job);
105 	job->state = JOB_RUNNING;
106 
107 	job->cmd = xstrdup(cmd);
108 	job->pid = pid;
109 	job->status = 0;
110 
111 	LIST_INSERT_HEAD(&all_jobs, job, lentry);
112 
113 	job->callbackfn = callbackfn;
114 	job->freefn = freefn;
115 	job->data = data;
116 
117 	job->fd = out[0];
118 	setblocking(job->fd, 0);
119 
120 	job->event = bufferevent_new(job->fd, NULL, job_write_callback,
121 	    job_callback, job);
122 	bufferevent_enable(job->event, EV_READ|EV_WRITE);
123 
124 	log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid);
125 	return (job);
126 }
127 
128 /* Kill and free an individual job. */
129 void
130 job_free(struct job *job)
131 {
132 	log_debug("free job %p: %s", job, job->cmd);
133 
134 	LIST_REMOVE(job, lentry);
135 	free(job->cmd);
136 
137 	if (job->freefn != NULL && job->data != NULL)
138 		job->freefn(job->data);
139 
140 	if (job->pid != -1)
141 		kill(job->pid, SIGTERM);
142 	if (job->event != NULL)
143 		bufferevent_free(job->event);
144 	if (job->fd != -1)
145 		close(job->fd);
146 
147 	free(job);
148 }
149 
150 /* Called when output buffer falls below low watermark (default is 0). */
151 void
152 job_write_callback(__unused struct bufferevent *bufev, void *data)
153 {
154 	struct job	*job = data;
155 	size_t		 len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event));
156 
157 	log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd,
158 	    (long) job->pid, len);
159 
160 	if (len == 0) {
161 		shutdown(job->fd, SHUT_WR);
162 		bufferevent_disable(job->event, EV_WRITE);
163 	}
164 }
165 
166 /* Job buffer error callback. */
167 void
168 job_callback(__unused struct bufferevent *bufev, __unused short events,
169     void *data)
170 {
171 	struct job	*job = data;
172 
173 	log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid);
174 
175 	if (job->state == JOB_DEAD) {
176 		if (job->callbackfn != NULL)
177 			job->callbackfn(job);
178 		job_free(job);
179 	} else {
180 		bufferevent_disable(job->event, EV_READ);
181 		job->state = JOB_CLOSED;
182 	}
183 }
184 
185 /* Job died (waitpid() returned its pid). */
186 void
187 job_died(struct job *job, int status)
188 {
189 	log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid);
190 
191 	job->status = status;
192 
193 	if (job->state == JOB_CLOSED) {
194 		if (job->callbackfn != NULL)
195 			job->callbackfn(job);
196 		job_free(job);
197 	} else {
198 		job->pid = -1;
199 		job->state = JOB_DEAD;
200 	}
201 }
202