File: | src/usr.bin/tmux/job.c |
Warning: | line 190, column 11 Assigned value is garbage or undefined |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: job.c,v 1.67 2022/02/01 12:05:42 nicm 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 | sigfillset(&set); | |||
96 | sigprocmask(SIG_BLOCK1, &set, &oldset); | |||
97 | ||||
98 | if (flags & JOB_PTY0x4) { | |||
99 | memset(&ws, 0, sizeof ws); | |||
100 | ws.ws_col = sx; | |||
101 | ws.ws_row = sy; | |||
102 | pid = fdforkpty(ptm_fd, &master, tty, NULL((void *)0), &ws); | |||
103 | } else { | |||
104 | if (socketpair(AF_UNIX1, SOCK_STREAM1, PF_UNSPEC0, out) != 0) | |||
105 | goto fail; | |||
106 | pid = fork(); | |||
107 | } | |||
108 | if (cmd == NULL((void *)0)) { | |||
109 | cmd_log_argv(argc, argv, "%s:", __func__); | |||
110 | log_debug("%s: cwd=%s", __func__, cwd == NULL((void *)0) ? "" : cwd); | |||
111 | } else { | |||
112 | log_debug("%s: cmd=%s, cwd=%s", __func__, cmd, | |||
113 | cwd == NULL((void *)0) ? "" : cwd); | |||
114 | } | |||
115 | ||||
116 | switch (pid) { | |||
117 | case -1: | |||
118 | if (~flags & JOB_PTY0x4) { | |||
119 | close(out[0]); | |||
120 | close(out[1]); | |||
121 | } | |||
122 | goto fail; | |||
123 | case 0: | |||
124 | proc_clear_signals(server_proc, 1); | |||
125 | sigprocmask(SIG_SETMASK3, &oldset, NULL((void *)0)); | |||
126 | ||||
127 | if ((cwd == NULL((void *)0) || chdir(cwd) != 0) && | |||
128 | ((home = find_home()) == NULL((void *)0) || chdir(home) != 0) && | |||
129 | chdir("/") != 0) | |||
130 | fatal("chdir failed"); | |||
131 | ||||
132 | environ_push(env); | |||
133 | environ_free(env); | |||
134 | ||||
135 | if (~flags & JOB_PTY0x4) { | |||
136 | if (dup2(out[1], STDIN_FILENO0) == -1) | |||
137 | fatal("dup2 failed"); | |||
138 | if (dup2(out[1], STDOUT_FILENO1) == -1) | |||
139 | fatal("dup2 failed"); | |||
140 | if (out[1] != STDIN_FILENO0 && out[1] != STDOUT_FILENO1) | |||
141 | close(out[1]); | |||
142 | close(out[0]); | |||
143 | ||||
144 | nullfd = open(_PATH_DEVNULL"/dev/null", O_RDWR0x0002); | |||
145 | if (nullfd == -1) | |||
146 | fatal("open failed"); | |||
147 | if (dup2(nullfd, STDERR_FILENO2) == -1) | |||
148 | fatal("dup2 failed"); | |||
149 | if (nullfd != STDERR_FILENO2) | |||
150 | close(nullfd); | |||
151 | } | |||
152 | closefrom(STDERR_FILENO2 + 1); | |||
153 | ||||
154 | if (cmd != NULL((void *)0)) { | |||
155 | execl(_PATH_BSHELL"/bin/sh", "sh", "-c", cmd, (char *) NULL((void *)0)); | |||
156 | fatal("execl failed"); | |||
157 | } else { | |||
158 | argvp = cmd_copy_argv(argc, argv); | |||
159 | execvp(argvp[0], argvp); | |||
160 | fatal("execvp failed"); | |||
161 | } | |||
162 | } | |||
163 | ||||
164 | sigprocmask(SIG_SETMASK3, &oldset, NULL((void *)0)); | |||
165 | environ_free(env); | |||
166 | ||||
167 | job = xmalloc(sizeof *job); | |||
168 | job->state = JOB_RUNNING; | |||
169 | job->flags = flags; | |||
170 | ||||
171 | if (cmd
| |||
172 | job->cmd = xstrdup(cmd); | |||
173 | else | |||
174 | job->cmd = cmd_stringify_argv(argc, argv); | |||
175 | job->pid = pid; | |||
176 | strlcpy(job->tty, tty, sizeof job->tty); | |||
177 | job->status = 0; | |||
178 | ||||
179 | 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); | |||
180 | ||||
181 | job->updatecb = updatecb; | |||
182 | job->completecb = completecb; | |||
183 | job->freecb = freecb; | |||
184 | job->data = data; | |||
185 | ||||
186 | if (~flags & JOB_PTY0x4) { | |||
187 | close(out[1]); | |||
188 | job->fd = out[0]; | |||
189 | } else | |||
190 | job->fd = master; | |||
| ||||
191 | setblocking(job->fd, 0); | |||
192 | ||||
193 | job->event = bufferevent_new(job->fd, job_read_callback, | |||
194 | job_write_callback, job_error_callback, job); | |||
195 | if (job->event == NULL((void *)0)) | |||
196 | fatalx("out of memory"); | |||
197 | bufferevent_enable(job->event, EV_READ0x02|EV_WRITE0x04); | |||
198 | ||||
199 | log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid); | |||
200 | return (job); | |||
201 | ||||
202 | fail: | |||
203 | sigprocmask(SIG_SETMASK3, &oldset, NULL((void *)0)); | |||
204 | environ_free(env); | |||
205 | return (NULL((void *)0)); | |||
206 | } | |||
207 | ||||
208 | /* Take job's file descriptor and free the job. */ | |||
209 | int | |||
210 | job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen) | |||
211 | { | |||
212 | int fd = job->fd; | |||
213 | ||||
214 | log_debug("transfer job %p: %s", job, job->cmd); | |||
215 | ||||
216 | if (pid != NULL((void *)0)) | |||
217 | *pid = job->pid; | |||
218 | if (tty != NULL((void *)0)) | |||
219 | strlcpy(tty, job->tty, ttylen); | |||
220 | ||||
221 | 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); | |||
222 | free(job->cmd); | |||
223 | ||||
224 | if (job->freecb != NULL((void *)0) && job->data != NULL((void *)0)) | |||
225 | job->freecb(job->data); | |||
226 | ||||
227 | if (job->event != NULL((void *)0)) | |||
228 | bufferevent_free(job->event); | |||
229 | ||||
230 | free(job); | |||
231 | return (fd); | |||
232 | } | |||
233 | ||||
234 | /* Kill and free an individual job. */ | |||
235 | void | |||
236 | job_free(struct job *job) | |||
237 | { | |||
238 | log_debug("free job %p: %s", job, job->cmd); | |||
239 | ||||
240 | 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); | |||
241 | free(job->cmd); | |||
242 | ||||
243 | if (job->freecb != NULL((void *)0) && job->data != NULL((void *)0)) | |||
244 | job->freecb(job->data); | |||
245 | ||||
246 | if (job->pid != -1) | |||
247 | kill(job->pid, SIGTERM15); | |||
248 | if (job->event != NULL((void *)0)) | |||
249 | bufferevent_free(job->event); | |||
250 | if (job->fd != -1) | |||
251 | close(job->fd); | |||
252 | ||||
253 | free(job); | |||
254 | } | |||
255 | ||||
256 | /* Resize job. */ | |||
257 | void | |||
258 | job_resize(struct job *job, u_int sx, u_int sy) | |||
259 | { | |||
260 | struct winsize ws; | |||
261 | ||||
262 | if (job->fd == -1 || (~job->flags & JOB_PTY0x4)) | |||
263 | return; | |||
264 | ||||
265 | log_debug("resize job %p: %ux%u", job, sx, sy); | |||
266 | ||||
267 | memset(&ws, 0, sizeof ws); | |||
268 | ws.ws_col = sx; | |||
269 | ws.ws_row = sy; | |||
270 | if (ioctl(job->fd, TIOCSWINSZ((unsigned long)0x80000000 | ((sizeof(struct winsize) & 0x1fff ) << 16) | ((('t')) << 8) | ((103))), &ws) == -1) | |||
271 | fatal("ioctl failed"); | |||
272 | } | |||
273 | ||||
274 | /* Job buffer read callback. */ | |||
275 | static void | |||
276 | job_read_callback(__unused__attribute__((__unused__)) struct bufferevent *bufev, void *data) | |||
277 | { | |||
278 | struct job *job = data; | |||
279 | ||||
280 | if (job->updatecb != NULL((void *)0)) | |||
281 | job->updatecb(job); | |||
282 | } | |||
283 | ||||
284 | /* | |||
285 | * Job buffer write callback. Fired when the buffer falls below watermark | |||
286 | * (default is empty). If all the data has been written, disable the write | |||
287 | * event. | |||
288 | */ | |||
289 | static void | |||
290 | job_write_callback(__unused__attribute__((__unused__)) struct bufferevent *bufev, void *data) | |||
291 | { | |||
292 | struct job *job = data; | |||
293 | size_t len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event))((job->event)->output)->off; | |||
294 | ||||
295 | log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd, | |||
296 | (long) job->pid, len); | |||
297 | ||||
298 | if (len == 0 && (~job->flags & JOB_KEEPWRITE0x2)) { | |||
299 | shutdown(job->fd, SHUT_WR1); | |||
300 | bufferevent_disable(job->event, EV_WRITE0x04); | |||
301 | } | |||
302 | } | |||
303 | ||||
304 | /* Job buffer error callback. */ | |||
305 | static void | |||
306 | job_error_callback(__unused__attribute__((__unused__)) struct bufferevent *bufev, __unused__attribute__((__unused__)) short events, | |||
307 | void *data) | |||
308 | { | |||
309 | struct job *job = data; | |||
310 | ||||
311 | log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid); | |||
312 | ||||
313 | if (job->state == JOB_DEAD) { | |||
314 | if (job->completecb != NULL((void *)0)) | |||
315 | job->completecb(job); | |||
316 | job_free(job); | |||
317 | } else { | |||
318 | bufferevent_disable(job->event, EV_READ0x02); | |||
319 | job->state = JOB_CLOSED; | |||
320 | } | |||
321 | } | |||
322 | ||||
323 | /* Job died (waitpid() returned its pid). */ | |||
324 | void | |||
325 | job_check_died(pid_t pid, int status) | |||
326 | { | |||
327 | struct job *job; | |||
328 | ||||
329 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
330 | if (pid == job->pid) | |||
331 | break; | |||
332 | } | |||
333 | if (job == NULL((void *)0)) | |||
334 | return; | |||
335 | if (WIFSTOPPED(status)(((status) & 0xff) == 0177)) { | |||
336 | if (WSTOPSIG(status)(int)(((unsigned)(status) >> 8) & 0xff) == SIGTTIN21 || WSTOPSIG(status)(int)(((unsigned)(status) >> 8) & 0xff) == SIGTTOU22) | |||
337 | return; | |||
338 | killpg(job->pid, SIGCONT19); | |||
339 | return; | |||
340 | } | |||
341 | log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); | |||
342 | ||||
343 | job->status = status; | |||
344 | ||||
345 | if (job->state == JOB_CLOSED) { | |||
346 | if (job->completecb != NULL((void *)0)) | |||
347 | job->completecb(job); | |||
348 | job_free(job); | |||
349 | } else { | |||
350 | job->pid = -1; | |||
351 | job->state = JOB_DEAD; | |||
352 | } | |||
353 | } | |||
354 | ||||
355 | /* Get job status. */ | |||
356 | int | |||
357 | job_get_status(struct job *job) | |||
358 | { | |||
359 | return (job->status); | |||
360 | } | |||
361 | ||||
362 | /* Get job data. */ | |||
363 | void * | |||
364 | job_get_data(struct job *job) | |||
365 | { | |||
366 | return (job->data); | |||
367 | } | |||
368 | ||||
369 | /* Get job event. */ | |||
370 | struct bufferevent * | |||
371 | job_get_event(struct job *job) | |||
372 | { | |||
373 | return (job->event); | |||
374 | } | |||
375 | ||||
376 | /* Kill all jobs. */ | |||
377 | void | |||
378 | job_kill_all(void) | |||
379 | { | |||
380 | struct job *job; | |||
381 | ||||
382 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
383 | if (job->pid != -1) | |||
384 | kill(job->pid, SIGTERM15); | |||
385 | } | |||
386 | } | |||
387 | ||||
388 | /* Are any jobs still running? */ | |||
389 | int | |||
390 | job_still_running(void) | |||
391 | { | |||
392 | struct job *job; | |||
393 | ||||
394 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
395 | if ((~job->flags & JOB_NOWAIT0x1) && job->state == JOB_RUNNING) | |||
396 | return (1); | |||
397 | } | |||
398 | return (0); | |||
399 | } | |||
400 | ||||
401 | /* Print job summary. */ | |||
402 | void | |||
403 | job_print_summary(struct cmdq_item *item, int blank) | |||
404 | { | |||
405 | struct job *job; | |||
406 | u_int n = 0; | |||
407 | ||||
408 | LIST_FOREACH(job, &all_jobs, entry)for((job) = ((&all_jobs)->lh_first); (job)!= ((void *) 0); (job) = ((job)->entry.le_next)) { | |||
409 | if (blank) { | |||
410 | cmdq_print(item, "%s", ""); | |||
411 | blank = 0; | |||
412 | } | |||
413 | cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]", | |||
414 | n, job->cmd, job->fd, (long)job->pid, job->status); | |||
415 | n++; | |||
416 | } | |||
417 | } |