Coverage Report

Created: 2025-12-14 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/job.c
Line
Count
Source
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) {
143
0
      if (chdir(cwd) == 0)
144
0
        environ_set(env, "PWD", 0, "%s", cwd);
145
0
      else if ((home = find_home()) != NULL && chdir(home) == 0)
146
0
        environ_set(env, "PWD", 0, "%s", home);
147
0
      else if (chdir("/") == 0)
148
0
        environ_set(env, "PWD", 0, "/");
149
0
      else
150
0
        fatal("chdir failed");
151
0
    }
152
153
0
    environ_push(env);
154
0
    environ_free(env);
155
156
0
    if (~flags & JOB_PTY) {
157
0
      if (dup2(out[1], STDIN_FILENO) == -1)
158
0
        fatal("dup2 failed");
159
0
      do_close = do_close && out[1] != STDIN_FILENO;
160
0
      if (dup2(out[1], STDOUT_FILENO) == -1)
161
0
        fatal("dup2 failed");
162
0
      do_close = do_close && out[1] != STDOUT_FILENO;
163
0
      if (flags & JOB_SHOWSTDERR) {
164
0
        if (dup2(out[1], STDERR_FILENO) == -1)
165
0
          fatal("dup2 failed");
166
0
        do_close = do_close && out[1] != STDERR_FILENO;
167
0
      } else {
168
0
        nullfd = open(_PATH_DEVNULL, O_RDWR);
169
0
        if (nullfd == -1)
170
0
          fatal("open failed");
171
0
        if (dup2(nullfd, STDERR_FILENO) == -1)
172
0
          fatal("dup2 failed");
173
0
        if (nullfd != STDERR_FILENO)
174
0
          close(nullfd);
175
0
      }
176
0
      if (do_close)
177
0
        close(out[1]);
178
0
      close(out[0]);
179
0
    }
180
0
    closefrom(STDERR_FILENO + 1);
181
182
0
    if (cmd != NULL) {
183
0
      if (flags & JOB_DEFAULTSHELL)
184
0
        setenv("SHELL", shell, 1);
185
0
      execl(shell, argv0, "-c", cmd, (char *)NULL);
186
0
      fatal("execl failed");
187
0
    } else {
188
0
      argvp = cmd_copy_argv(argc, argv);
189
0
      execvp(argvp[0], argvp);
190
0
      fatal("execvp failed");
191
0
    }
192
0
  }
193
194
0
  sigprocmask(SIG_SETMASK, &oldset, NULL);
195
0
  environ_free(env);
196
0
  free(argv0);
197
198
0
  job = xcalloc(1, sizeof *job);
199
0
  job->state = JOB_RUNNING;
200
0
  job->flags = flags;
201
202
0
  if (cmd != NULL)
203
0
    job->cmd = xstrdup(cmd);
204
0
  else
205
0
    job->cmd = cmd_stringify_argv(argc, argv);
206
0
  job->pid = pid;
207
0
  if (flags & JOB_PTY)
208
0
    strlcpy(job->tty, tty, sizeof job->tty);
209
0
  job->status = 0;
210
211
0
  LIST_INSERT_HEAD(&all_jobs, job, entry);
212
213
0
  job->updatecb = updatecb;
214
0
  job->completecb = completecb;
215
0
  job->freecb = freecb;
216
0
  job->data = data;
217
218
0
  if (~flags & JOB_PTY) {
219
0
    close(out[1]);
220
0
    job->fd = out[0];
221
0
  } else
222
0
    job->fd = master;
223
0
  setblocking(job->fd, 0);
224
225
0
  job->event = bufferevent_new(job->fd, job_read_callback,
226
0
      job_write_callback, job_error_callback, job);
227
0
  if (job->event == NULL)
228
0
    fatalx("out of memory");
229
0
  bufferevent_enable(job->event, EV_READ|EV_WRITE);
230
231
0
  log_debug("run job %p: %s, pid %ld", job, job->cmd, (long)job->pid);
232
0
  return (job);
233
234
0
fail:
235
0
  sigprocmask(SIG_SETMASK, &oldset, NULL);
236
0
  environ_free(env);
237
0
  free(argv0);
238
0
  return (NULL);
239
0
}
240
241
/* Take job's file descriptor and free the job. */
242
int
243
job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen)
244
0
{
245
0
  int fd = job->fd;
246
247
0
  log_debug("transfer job %p: %s", job, job->cmd);
248
249
0
  if (pid != NULL)
250
0
    *pid = job->pid;
251
0
  if (tty != NULL)
252
0
    strlcpy(tty, job->tty, ttylen);
253
254
0
  LIST_REMOVE(job, entry);
255
0
  free(job->cmd);
256
257
0
  if (job->freecb != NULL && job->data != NULL)
258
0
    job->freecb(job->data);
259
260
0
  if (job->event != NULL)
261
0
    bufferevent_free(job->event);
262
263
0
  free(job);
264
0
  return (fd);
265
0
}
266
267
/* Kill and free an individual job. */
268
void
269
job_free(struct job *job)
270
0
{
271
0
  log_debug("free job %p: %s", job, job->cmd);
272
273
0
  LIST_REMOVE(job, entry);
274
0
  free(job->cmd);
275
276
0
  if (job->freecb != NULL && job->data != NULL)
277
0
    job->freecb(job->data);
278
279
0
  if (job->pid != -1)
280
0
    kill(job->pid, SIGTERM);
281
0
  if (job->event != NULL)
282
0
    bufferevent_free(job->event);
283
0
  if (job->fd != -1)
284
0
    close(job->fd);
285
286
0
  free(job);
287
0
}
288
289
/* Resize job. */
290
void
291
job_resize(struct job *job, u_int sx, u_int sy)
292
0
{
293
0
  struct winsize   ws;
294
295
0
  if (job->fd == -1 || (~job->flags & JOB_PTY))
296
0
    return;
297
298
0
  log_debug("resize job %p: %ux%u", job, sx, sy);
299
300
0
  memset(&ws, 0, sizeof ws);
301
0
  ws.ws_col = sx;
302
0
  ws.ws_row = sy;
303
0
  if (ioctl(job->fd, TIOCSWINSZ, &ws) == -1)
304
0
    fatal("ioctl failed");
305
0
}
306
307
/* Job buffer read callback. */
308
static void
309
job_read_callback(__unused struct bufferevent *bufev, void *data)
310
0
{
311
0
  struct job  *job = data;
312
313
0
  if (job->updatecb != NULL)
314
0
    job->updatecb(job);
315
0
}
316
317
/*
318
 * Job buffer write callback. Fired when the buffer falls below watermark
319
 * (default is empty). If all the data has been written, disable the write
320
 * event.
321
 */
322
static void
323
job_write_callback(__unused struct bufferevent *bufev, void *data)
324
0
{
325
0
  struct job  *job = data;
326
0
  size_t     len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event));
327
328
0
  log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd,
329
0
      (long) job->pid, len);
330
331
0
  if (len == 0 && (~job->flags & JOB_KEEPWRITE)) {
332
0
    shutdown(job->fd, SHUT_WR);
333
0
    bufferevent_disable(job->event, EV_WRITE);
334
0
  }
335
0
}
336
337
/* Job buffer error callback. */
338
static void
339
job_error_callback(__unused struct bufferevent *bufev, __unused short events,
340
    void *data)
341
0
{
342
0
  struct job  *job = data;
343
344
0
  log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid);
345
346
0
  if (job->state == JOB_DEAD) {
347
0
    if (job->completecb != NULL)
348
0
      job->completecb(job);
349
0
    job_free(job);
350
0
  } else {
351
0
    bufferevent_disable(job->event, EV_READ);
352
0
    job->state = JOB_CLOSED;
353
0
  }
354
0
}
355
356
/* Job died (waitpid() returned its pid). */
357
void
358
job_check_died(pid_t pid, int status)
359
0
{
360
0
  struct job  *job;
361
362
0
  LIST_FOREACH(job, &all_jobs, entry) {
363
0
    if (pid == job->pid)
364
0
      break;
365
0
  }
366
0
  if (job == NULL)
367
0
    return;
368
0
  if (WIFSTOPPED(status)) {
369
0
    if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
370
0
      return;
371
0
    killpg(job->pid, SIGCONT);
372
0
    return;
373
0
  }
374
0
  log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid);
375
376
0
  job->status = status;
377
378
0
  if (job->state == JOB_CLOSED) {
379
0
    if (job->completecb != NULL)
380
0
      job->completecb(job);
381
0
    job_free(job);
382
0
  } else {
383
0
    job->pid = -1;
384
0
    job->state = JOB_DEAD;
385
0
  }
386
0
}
387
388
/* Get job status. */
389
int
390
job_get_status(struct job *job)
391
0
{
392
0
  return (job->status);
393
0
}
394
395
/* Get job data. */
396
void *
397
job_get_data(struct job *job)
398
0
{
399
0
  return (job->data);
400
0
}
401
402
/* Get job event. */
403
struct bufferevent *
404
job_get_event(struct job *job)
405
0
{
406
0
  return (job->event);
407
0
}
408
409
/* Kill all jobs. */
410
void
411
job_kill_all(void)
412
0
{
413
0
  struct job  *job;
414
415
0
  LIST_FOREACH(job, &all_jobs, entry) {
416
0
    if (job->pid != -1)
417
0
      kill(job->pid, SIGTERM);
418
0
  }
419
0
}
420
421
/* Are any jobs still running? */
422
int
423
job_still_running(void)
424
0
{
425
0
  struct job  *job;
426
427
0
  LIST_FOREACH(job, &all_jobs, entry) {
428
0
    if ((~job->flags & JOB_NOWAIT) && job->state == JOB_RUNNING)
429
0
      return (1);
430
0
  }
431
0
  return (0);
432
0
}
433
434
/* Print job summary. */
435
void
436
job_print_summary(struct cmdq_item *item, int blank)
437
0
{
438
0
  struct job  *job;
439
0
  u_int    n = 0;
440
441
0
  LIST_FOREACH(job, &all_jobs, entry) {
442
0
    if (blank) {
443
0
      cmdq_print(item, "%s", "");
444
0
      blank = 0;
445
0
    }
446
0
    cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]",
447
0
        n, job->cmd, job->fd, (long)job->pid, job->status);
448
0
    n++;
449
0
  }
450
0
}