xref: /openbsd/usr.bin/tmux/job.c (revision 404b540a)
1 /* $OpenBSD: job.c,v 1.5 2009/10/11 08:58:05 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 
21 #include <fcntl.h>
22 #include <paths.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 
33 /* All jobs list. */
34 struct joblist	all_jobs = SLIST_HEAD_INITIALIZER(&all_jobs);
35 
36 RB_GENERATE(jobs, job, entry, job_cmp);
37 
38 int
39 job_cmp(struct job *job1, struct job *job2)
40 {
41 	return (strcmp(job1->cmd, job2->cmd));
42 }
43 
44 /* Initialise job tree. */
45 void
46 job_tree_init(struct jobs *jobs)
47 {
48 	RB_INIT(jobs);
49 }
50 
51 /* Count the number of jobs in a tree. */
52 u_int
53 job_tree_size(struct jobs *jobs)
54 {
55 	struct job	*job;
56 	u_int		 n;
57 
58 	n = 0;
59 	RB_FOREACH(job, jobs, jobs)
60 		n++;
61 	return (n);
62 }
63 
64 /* Destroy a job tree. */
65 void
66 job_tree_free(struct jobs *jobs)
67 {
68 	struct job	*job;
69 
70 	while (!RB_EMPTY(jobs)) {
71 		job = RB_ROOT(jobs);
72 		RB_REMOVE(jobs, jobs, job);
73 		job_free(job);
74 	}
75 }
76 
77 /* Find a job and return it. */
78 struct job *
79 job_get(struct jobs *jobs, const char *cmd)
80 {
81 	struct job	job;
82 
83 	job.cmd = (char *) cmd;
84 	return (RB_FIND(jobs, jobs, &job));
85 }
86 
87 /* Add a job. */
88 struct job *
89 job_add(struct jobs *jobs, struct client *c, const char *cmd,
90     void (*callbackfn)(struct job *), void (*freefn)(void *), void *data)
91 {
92 	struct job	*job;
93 
94 	job = xmalloc(sizeof *job);
95 	job->cmd = xstrdup(cmd);
96 	job->pid = -1;
97 
98 	job->client = c;
99 
100 	job->fd = -1;
101 	job->out = buffer_create(BUFSIZ);
102 
103 	job->callbackfn = callbackfn;
104 	job->freefn = freefn;
105 	job->data = data;
106 
107 	job->flags = JOB_DONE;
108 
109 	if (jobs != NULL)
110 		RB_INSERT(jobs, jobs, job);
111 	SLIST_INSERT_HEAD(&all_jobs, job, lentry);
112 
113 	return (job);
114 }
115 
116 /* Kill and free an individual job. */
117 void
118 job_free(struct job *job)
119 {
120 	job_kill(job);
121 
122 	SLIST_REMOVE(&all_jobs, job, job, lentry);
123 	xfree(job->cmd);
124 
125 	if (job->freefn != NULL && job->data != NULL)
126 		job->freefn(job->data);
127 
128 	if (job->fd != -1)
129 		close(job->fd);
130 	if (job->out != NULL)
131 		buffer_destroy(job->out);
132 
133 	xfree(job);
134 }
135 
136 /* Start a job running, if it isn't already. */
137 int
138 job_run(struct job *job)
139 {
140 	int	nullfd, out[2], mode;
141 
142 	if (!(job->flags & JOB_DONE))
143 		return (0);
144 	job->flags &= ~JOB_DONE;
145 
146 	if (pipe(out) != 0)
147 		return (-1);
148 
149 	switch (job->pid = fork()) {
150 	case -1:
151 		return (-1);
152 	case 0:		/* child */
153 		sigreset();
154 		/* XXX environ? */
155 
156 		nullfd = open(_PATH_DEVNULL, O_RDONLY, 0);
157 		if (nullfd < 0)
158 			fatal("open failed");
159 		if (dup2(nullfd, STDIN_FILENO) == -1)
160 			fatal("dup2 failed");
161 		if (dup2(nullfd, STDERR_FILENO) == -1)
162 			fatal("dup2 failed");
163 		if (nullfd != STDIN_FILENO && nullfd != STDERR_FILENO)
164 			close(nullfd);
165 
166 		close(out[1]);
167 		if (dup2(out[0], STDOUT_FILENO) == -1)
168 			fatal("dup2 failed");
169 		if (out[0] != STDOUT_FILENO)
170 			close(out[0]);
171 
172 		execl(_PATH_BSHELL, "sh", "-c", job->cmd, (char *) NULL);
173 		fatal("execl failed");
174 	default:	/* parent */
175 		close(out[0]);
176 
177 		job->fd = out[1];
178 		if ((mode = fcntl(job->fd, F_GETFL)) == -1)
179 			fatal("fcntl failed");
180 		if (fcntl(job->fd, F_SETFL, mode|O_NONBLOCK) == -1)
181 			fatal("fcntl failed");
182 		if (fcntl(job->fd, F_SETFD, FD_CLOEXEC) == -1)
183 			fatal("fcntl failed");
184 
185 		if (BUFFER_USED(job->out) != 0)
186 			buffer_remove(job->out, BUFFER_USED(job->out));
187 
188 		return (0);
189 	}
190 }
191 
192 /* Kill a job. */
193 void
194 job_kill(struct job *job)
195 {
196 	if (job->pid == -1)
197 		return;
198 	kill(job->pid, SIGTERM);
199 	job->pid = -1;
200 }
201