Coverage Report

Created: 2025-08-09 06:27

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