File: | src/usr.bin/tmux/job.c |
Warning: | line 191, column 11 Assigned value is garbage or undefined |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: job.c,v 1.66 2021/10/24 21:24:17 deraadt Exp $ */ | |||
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/ioctl.h> | |||
21 | #include <sys/socket.h> | |||
22 | #include <sys/wait.h> | |||
23 | ||||
24 | #include <fcntl.h> | |||
25 | #include <paths.h> | |||
26 | #include <signal.h> | |||
27 | #include <stdlib.h> | |||
28 | #include <string.h> | |||
29 | #include <unistd.h> | |||
30 | #include <util.h> | |||
31 | ||||
32 | #include "tmux.h" | |||
33 | ||||
34 | /* | |||
35 | * Job scheduling. Run queued commands in the background and record their | |||
36 | * output. | |||
37 | */ | |||
38 | ||||
39 | static void job_read_callback(struct bufferevent *, void *); | |||
40 | static void job_write_callback(struct bufferevent *, void *); | |||
41 | static void job_error_callback(struct bufferevent *, short, void *); | |||
42 | ||||
43 | /* A single job. */ | |||
44 | struct job { | |||
45 | enum { | |||
46 | JOB_RUNNING, | |||
47 | JOB_DEAD, | |||
48 | JOB_CLOSED | |||
49 | } state; | |||
50 | ||||
51 | int flags; | |||
52 | ||||
53 | char *cmd; | |||
54 | pid_t pid; | |||
55 | char tty[TTY_NAME_MAX260]; | |||
56 | int status; | |||
57 | ||||
58 | int fd; | |||
59 | struct bufferevent *event; | |||
60 | ||||
61 | job_update_cb updatecb; | |||
62 | job_complete_cb completecb; | |||
63 | job_free_cb freecb; | |||
64 | void *data; | |||
65 | ||||
66 | LIST_ENTRY(job)struct { struct job *le_next; struct job **le_prev; } entry; | |||
67 | }; | |||
68 | ||||
69 | /* All jobs list. */ | |||
70 | static LIST_HEAD(joblist, job)struct joblist { struct job *lh_first; } all_jobs = LIST_HEAD_INITIALIZER(all_jobs){ ((void *)0) }; | |||
71 | ||||
72 | /* Start a job running. */ | |||
73 | struct job * | |||
74 | job_run(const char *cmd, int argc, char **argv, struct environ *e, struct session *s, | |||
75 | const char *cwd, job_update_cb updatecb, job_complete_cb completecb, | |||
76 | job_free_cb freecb, void *data, int flags, int sx, int sy) | |||
77 | { | |||
78 | struct job *job; | |||
79 | struct environ *env; | |||
80 | pid_t pid; | |||
81 | int nullfd, out[2], master; | |||
| ||||
82 | const char *home; | |||
83 | sigset_t set, oldset; | |||
84 | struct winsize ws; | |||
85 | char **argvp, tty[TTY_NAME_MAX260]; | |||
86 | ||||
87 | /* | |||
88 | * Do not set TERM during .tmux.conf, it is nice to be able to use | |||
89 | * if-shell to decide on default-terminal based on outside TERM. | |||
90 | */ | |||
91 | env = environ_for_session(s, !cfg_finished); | |||
92 | if (e != NULL((void *)0)) { | |||
93 | environ_copy(e, env); | |||
94 | } | |||
95 | ||||
96 | sigfillset(&set); | |||
97 | sigprocmask(SIG_BLOCK1, &set, &oldset); | |||
98 | ||||
99 | if (flags & JOB_PTY0x4) { | |||
100 | memset(&ws, 0, sizeof ws); | |||
101 | ws.ws_col = sx; | |||
102 | ws.ws_row = sy; | |||
103 | pid = fdforkpty(ptm_fd, &master, tty, NULL((void *)0), &ws); | |||
104 | } else { | |||
105 | if (socketpair(AF_UNIX1, SOCK_STREAM1, PF_UNSPEC0, out) != 0) | |||
106 | goto fail; | |||
107 | pid = fork(); | |||
108 | } | |||
109 | if (cmd == NULL((void *)0)) { | |||
110 | cmd_log_argv(argc, argv, "%s:", __func__); | |||
111 | log_debug("%s: cwd=%s", __func__, cwd == NULL((void *)0) ? "" : cwd); | |||
112 | } else { | |||
113 | log_debug("%s: cmd=%s, cwd=%s", __func__, cmd, | |||
114 | cwd == NULL((void *)0) ? "" : cwd); | |||
115 | } | |||
116 | ||||
117 | switch (pid) { | |||
118 | case -1: | |||
119 | if (~flags & JOB_PTY0x4) { | |||
120 | close(out[0]); | |||
121 | close(out[1]); | |||
122 | } | |||
123 | goto fail; | |||
124 | case 0: | |||
125 | proc_clear_signals(server_proc, 1); | |||
126 | sigprocmask(SIG_SETMASK3, &oldset, NULL((void *)0)); | |||
127 | ||||
128 | if ((cwd == NULL((void *)0) || chdir(cwd) != 0) && | |||
129 | ((home = find_home()) == NULL((void *)0) || chdir(home) != 0) && | |||
130 | chdir("/") != 0) | |||
131 | fatal("chdir failed"); | |||
132 | ||||
133 | environ_push(env); | |||
134 | environ_free(env); | |||
135 | ||||
136 | if (~flags & JOB_PTY0x4) { | |||
137 | if (dup2(out[1], STDIN_FILENO0) == -1) | |||
138 | fatal("dup2 failed"); | |||
139 | if (dup2(out[1], STDOUT_FILENO1) == -1) | |||
140 | fatal("dup2 failed"); | |||
141 | if (out[1] != STDIN_FILENO0 && out[1] != STDOUT_FILENO1) | |||
142 | close(out[1]); | |||
143 | close(out[0]); | |||
144 | ||||
145 | nullfd = open(_PATH_DEVNULL"/dev/null", O_RDWR0x0002); | |||
146 | if (nullfd == -1) | |||
147 | fatal("open failed"); | |||
148 | if (dup2(nullfd, STDERR_FILENO2) == -1) | |||
149 | fatal("dup2 failed"); | |||
150 | if (nullfd != STDERR_FILENO2) | |||
151 | close(nullfd); | |||
152 | } | |||
153 | closefrom(STDERR_FILENO2 + 1); | |||
154 | ||||
155 | if (cmd != NULL((void *)0)) { | |||
156 | execl(_PATH_BSHELL"/bin/sh", "sh", "-c", cmd, (char *) NULL((void *)0)); | |||
157 | fatal("execl failed"); | |||
158 | } else { | |||
159 | argvp = cmd_copy_argv(argc, argv); | |||
160 | execvp(argvp[0], argvp); | |||
161 | fatal("execvp failed"); | |||
162 | } | |||
163 | } | |||
164 | ||||
165 | sigprocmask(SIG_SETMASK3, &oldset, NULL((void *)0)); | |||
166 | environ_free(env); | |||
167 | ||||
168 | job = xmalloc(sizeof *job); | |||
169 | job->state = JOB_RUNNING; | |||
170 | job->flags = flags; | |||
171 | ||||
172 | if (cmd
| |||
173 | job->cmd = xstrdup(cmd); | |||
174 | else | |||
175 | job->cmd = cmd_stringify_argv(argc, argv); | |||
176 | job->pid = pid; | |||
177 | strlcpy(job->tty, tty, sizeof job->tty); | |||
178 | job->status = 0; | |||
179 | ||||
180 | LIST_INSERT_HEAD(&all_jobs, job, entry)do { if (((job)->entry.le_next = (&all_jobs)->lh_first ) != ((void *)0)) (&all_jobs)->lh_first->entry.le_prev = &(job)->entry.le_next; (&all_jobs)->lh_first = (job); (job)->entry.le_prev = &(&all_jobs)-> lh_first; } while (0); | |||
181 | ||||
182 | job->updatecb = updatecb; | |||
183 | job->completecb = completecb; | |||
184 | job->freecb = freecb; | |||
185 | job->data = data; | |||
186 | ||||
187 | if (~flags & JOB_PTY0x4) { | |||
188 | close(out[1]); | |||
189 | job->fd = out[0]; | |||
190 | } else | |||
191 | job->fd = master; | |||
| ||||
192 | setblocking(job->fd, 0); | |||
193 | ||||
194 | job->event = bufferevent_new(job->fd, job_read_callback, | |||
195 | job_write_callback, job_error_callback, job); | |||
196 | if (job->event == NULL((void *)0)) | |||
197 | fatalx("out of memory"); | |||
198 | bufferevent_enable(job->event, EV_READ0x02|EV_WRITE0x04); | |||
199 | ||||
200 | log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid); | |||
201 | return (job); | |||
202 | ||||
203 | fail: | |||
204 | sigprocmask(SIG_SETMASK3, &oldset, NULL((void *)0)); | |||
205 | environ_free(env); | |||
206 | return (NULL((void *)0)); | |||
207 | } | |||
208 | ||||
209 | /* Take job's file descriptor and free the job. */ | |||
210 | int | |||
211 | job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen) | |||
212 | { | |||
213 | int fd = job->fd; | |||
214 | ||||
215 | log_debug("transfer job %p: %s", job, job->cmd); | |||
216 | ||||
217 | if (pid != NULL((void *)0)) | |||
218 | *pid = job->pid; | |||
219 | if (tty != NULL((void *)0)) | |||
220 | strlcpy(tty, job->tty, ttylen); | |||
221 | ||||
222 | LIST_REMOVE(job, entry)do { if ((job)->entry.le_next != ((void *)0)) (job)->entry .le_next->entry.le_prev = (job)->entry.le_prev; *(job)-> entry.le_prev = (job)->entry.le_next; ; ; } while (0); | |||
223 | free(job->cmd); | |||
224 | ||||
225 | if (job->freecb != NULL((void *)0) && job->data != NULL((void *)0)) | |||
226 | job->freecb(job->data); | |||
227 | ||||
228 | if (job->event != NULL((void *)0)) | |||
229 | bufferevent_free(job->event); | |||
230 | ||||
231 | free(job); | |||
232 | return (fd); | |||
233 | } | |||
234 | ||||
235 | /* Kill and free an individual job. */ | |||
236 | void | |||
237 | job_free(struct job *job) | |||
238 | { | |||
239 | log_debug("free job %p: %s", job, job->cmd); | |||
240 | ||||
241 | LIST_REMOVE(job, entry)do { if ((job)->entry.le_next != ((void *)0)) (job)->entry .le_next->entry.le_prev = (job)->entry.le_prev; *(job)-> entry.le_prev = (job)->entry.le_next; ; ; } while (0); | |||
242 | free(job->cmd); | |||
243 | ||||
244 | if (job->freecb != NULL((void *)0) && job->data != NULL((void *)0)) | |||
245 | job->freecb(job->data); | |||
246 | ||||
247 | if (job->pid != -1) | |||
248 | kill(job->pid, SIGTERM15); | |||
249 | if (job->event != NULL((void *)0)) | |||
250 | bufferevent_free(job->event); | |||
251 | if (job->fd != -1) | |||
252 | close(job->fd); | |||
253 | ||||
254 | free(job); | |||
255 | } | |||
256 | ||||
257 | /* Resize job. */ | |||
258 | void | |||
259 | job_resize(struct job *job, u_int sx, u_int sy) | |||
260 | { | |||
261 | struct winsize ws; | |||
262 | ||||
263 | if (job->fd == -1 || (~job->flags & JOB_PTY0x4)) | |||
264 | return; | |||
265 | ||||
266 | log_debug("resize job %p: %ux%u", job, sx, sy); | |||
267 | ||||
268 | memset(&ws, 0, sizeof ws); | |||
269 | ws.ws_col = sx; | |||
270 | ws.ws_row = sy; | |||
271 | if (ioctl(job->fd, TIOCSWINSZ((unsigned long)0x80000000 | ((sizeof(struct winsize) & 0x1fff ) << 16) | ((('t')) << 8) | ((103))), &ws) == -1) | |||
272 | fatal("ioctl failed"); | |||
273 | } | |||
274 | ||||
275 | /* Job buffer read callback. */ | |||
276 | static void | |||
277 | job_read_callback(__unused__attribute__((__unused__)) struct bufferevent *bufev, void *data) | |||
278 | { | |||
279 | struct job *job = data; | |||
280 | ||||
281 | if (job->updatecb != NULL((void *)0)) | |||
282 | job->updatecb(job); | |||
283 | } | |||
284 | ||||
285 | /* | |||
286 | * Job buffer write callback. Fired when the buffer falls below watermark | |||
287 | * (default is empty). If all the data has been written, disable the write | |||
288 | * event. | |||
289 | */ | |||
290 | static void | |||
291 | job_write_callback(__unused__attribute__((__unused__)) struct bufferevent *bufev, void *data) | |||
292 | { | |||
293 | struct job *job = data; | |||
294 | size_t len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event))((job->event)->output)->off; | |||
295 | ||||
296 | log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd, | |||
297 | (long) job->pid, len); | |||
298 | ||||
299 | if (len == 0 && (~job->flags & JOB_KEEPWRITE0x2)) { | |||
300 | shutdown(job->fd, SHUT_WR1); | |||
301 | bufferevent_disable(job->event, EV_WRITE0x04); | |||
302 | } | |||
303 | } | |||
304 | ||||
305 | /* Job buffer error callback. */ | |||
306 | static void | |||
307 | job_error_callback(__unused__attribute__((__unused__)) struct bufferevent *bufev, __unused__attribute__((__unused__)) short events, | |||
308 | void *data) | |||
309 | { | |||
310 | struct job *job = data; | |||
311 | ||||
312 | log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid); | |||
313 | ||||
314 | if (job->state == JOB_DEAD) { | |||
315 | if (job->completecb != NULL((void *)0)) | |||
316 | job->completecb(job); | |||
317 | job_free(job); | |||
318 | } else { | |||
319 | bufferevent_disable(job->event, EV_READ0x02); | |||
320 | job->state = JOB_CLOSED; | |||
321 | } | |||
322 | } | |||
323 | ||||
324 | /* Job died (waitpid() returned its pid). */ | |||
325 | void | |||
326 | job_check_died(pid_t pid, int status) | |||
327 | { | |||
328 | struct job *job; | |||
329 | ||||
330 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
331 | if (pid == job->pid) | |||
332 | break; | |||
333 | } | |||
334 | if (job == NULL((void *)0)) | |||
335 | return; | |||
336 | if (WIFSTOPPED(status)(((status) & 0xff) == 0177)) { | |||
337 | if (WSTOPSIG(status)(int)(((unsigned)(status) >> 8) & 0xff) == SIGTTIN21 || WSTOPSIG(status)(int)(((unsigned)(status) >> 8) & 0xff) == SIGTTOU22) | |||
338 | return; | |||
339 | killpg(job->pid, SIGCONT19); | |||
340 | return; | |||
341 | } | |||
342 | log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); | |||
343 | ||||
344 | job->status = status; | |||
345 | ||||
346 | if (job->state == JOB_CLOSED) { | |||
347 | if (job->completecb != NULL((void *)0)) | |||
348 | job->completecb(job); | |||
349 | job_free(job); | |||
350 | } else { | |||
351 | job->pid = -1; | |||
352 | job->state = JOB_DEAD; | |||
353 | } | |||
354 | } | |||
355 | ||||
356 | /* Get job status. */ | |||
357 | int | |||
358 | job_get_status(struct job *job) | |||
359 | { | |||
360 | return (job->status); | |||
361 | } | |||
362 | ||||
363 | /* Get job data. */ | |||
364 | void * | |||
365 | job_get_data(struct job *job) | |||
366 | { | |||
367 | return (job->data); | |||
368 | } | |||
369 | ||||
370 | /* Get job event. */ | |||
371 | struct bufferevent * | |||
372 | job_get_event(struct job *job) | |||
373 | { | |||
374 | return (job->event); | |||
375 | } | |||
376 | ||||
377 | /* Kill all jobs. */ | |||
378 | void | |||
379 | job_kill_all(void) | |||
380 | { | |||
381 | struct job *job; | |||
382 | ||||
383 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
384 | if (job->pid != -1) | |||
385 | kill(job->pid, SIGTERM15); | |||
386 | } | |||
387 | } | |||
388 | ||||
389 | /* Are any jobs still running? */ | |||
390 | int | |||
391 | job_still_running(void) | |||
392 | { | |||
393 | struct job *job; | |||
394 | ||||
395 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
396 | if ((~job->flags & JOB_NOWAIT0x1) && job->state == JOB_RUNNING) | |||
397 | return (1); | |||
398 | } | |||
399 | return (0); | |||
400 | } | |||
401 | ||||
402 | /* Print job summary. */ | |||
403 | void | |||
404 | job_print_summary(struct cmdq_item *item, int blank) | |||
405 | { | |||
406 | struct job *job; | |||
407 | u_int n = 0; | |||
408 | ||||
409 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
410 | if (blank) { | |||
411 | cmdq_print(item, "%s", ""); | |||
412 | blank = 0; | |||
413 | } | |||
414 | cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]", | |||
415 | n, job->cmd, job->fd, (long)job->pid, job->status); | |||
416 | n++; | |||
417 | } | |||
418 | } |