Coverage Report

Created: 2025-08-24 06:20

/src/tor/src/lib/process/waitpid.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (c) 2003-2004, Roger Dingledine
2
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
3
 * Copyright (c) 2007-2025, The Tor Project, Inc. */
4
/* See LICENSE for licensing information */
5
6
/**
7
 * \file waitpid.c
8
 * \brief Convenience structures for handlers for handling waitpid().
9
 **/
10
11
#include "orconfig.h"
12
13
#ifndef _WIN32
14
15
#include "lib/process/waitpid.h"
16
#include "lib/log/log.h"
17
#include "lib/log/util_bug.h"
18
#include "lib/malloc/malloc.h"
19
#include "ext/ht.h"
20
21
#ifdef HAVE_SYS_WAIT_H
22
#include <sys/wait.h>
23
#endif
24
25
#include <string.h>
26
27
/* ================================================== */
28
/* Convenience structures for handlers for waitpid().
29
 *
30
 * The tor_process_monitor*() code above doesn't use them, since it is for
31
 * monitoring a non-child process.
32
 */
33
34
/** Mapping from a PID to a userfn/userdata pair. */
35
struct waitpid_callback_t {
36
  HT_ENTRY(waitpid_callback_t) node;
37
  pid_t pid;
38
39
  void (*userfn)(int, void *userdata);
40
  void *userdata;
41
42
  unsigned running;
43
};
44
45
static inline unsigned int
46
process_map_entry_hash_(const waitpid_callback_t *ent)
47
0
{
48
0
  return (unsigned) ent->pid;
49
0
}
50
51
static inline unsigned int
52
process_map_entries_eq_(const waitpid_callback_t *a,
53
                        const waitpid_callback_t *b)
54
0
{
55
0
  return a->pid == b->pid;
56
0
}
57
58
static HT_HEAD(process_map, waitpid_callback_t) process_map = HT_INITIALIZER();
59
60
HT_PROTOTYPE(process_map, waitpid_callback_t, node, process_map_entry_hash_,
61
             process_map_entries_eq_);
62
HT_GENERATE2(process_map, waitpid_callback_t, node, process_map_entry_hash_,
63
             process_map_entries_eq_, 0.6, tor_reallocarray_, tor_free_);
64
65
/**
66
 * Begin monitoring the child pid <b>pid</b> to see if we get a SIGCHLD for
67
 * it.  If we eventually do, call <b>fn</b>, passing it the exit status (as
68
 * yielded by waitpid) and the pointer <b>arg</b>.
69
 *
70
 * To cancel this, or clean up after it has triggered, call
71
 * clear_waitpid_callback().
72
 */
73
waitpid_callback_t *
74
set_waitpid_callback(pid_t pid, void (*fn)(int, void *), void *arg)
75
0
{
76
0
  waitpid_callback_t *old_ent;
77
0
  waitpid_callback_t *ent = tor_malloc_zero(sizeof(waitpid_callback_t));
78
0
  ent->pid = pid;
79
0
  ent->userfn = fn;
80
0
  ent->userdata = arg;
81
0
  ent->running = 1;
82
83
0
  old_ent = HT_REPLACE(process_map, &process_map, ent);
84
0
  if (old_ent) {
85
0
    log_warn(LD_BUG, "Replaced a waitpid monitor on pid %u. That should be "
86
0
             "impossible.", (unsigned) pid);
87
0
    old_ent->running = 0;
88
0
  }
89
90
0
  return ent;
91
0
}
92
93
/**
94
 * Cancel a waitpid_callback_t, or clean up after one has triggered. Releases
95
 * all storage held by <b>ent</b>.
96
 */
97
void
98
clear_waitpid_callback(waitpid_callback_t *ent)
99
0
{
100
0
  waitpid_callback_t *old_ent;
101
0
  if (ent == NULL)
102
0
    return;
103
104
0
  if (ent->running) {
105
0
    old_ent = HT_REMOVE(process_map, &process_map, ent);
106
0
    if (old_ent != ent) {
107
0
      log_warn(LD_BUG, "Couldn't remove waitpid monitor for pid %u.",
108
0
               (unsigned) ent->pid);
109
0
      return;
110
0
    }
111
0
  }
112
113
0
  tor_free(ent);
114
0
}
115
116
/** Helper: find the callback for <b>pid</b>; if there is one, run it,
117
 * reporting the exit status as <b>status</b>. */
118
static void
119
notify_waitpid_callback_by_pid(pid_t pid, int status)
120
0
{
121
0
  waitpid_callback_t search, *ent;
122
123
0
  search.pid = pid;
124
0
  ent = HT_REMOVE(process_map, &process_map, &search);
125
0
  if (!ent || !ent->running) {
126
0
    log_info(LD_GENERAL, "Child process %u has exited; no callback was "
127
0
             "registered", (unsigned)pid);
128
0
    return;
129
0
  }
130
131
0
  log_info(LD_GENERAL, "Child process %u has exited; running callback.",
132
0
           (unsigned)pid);
133
134
0
  ent->running = 0;
135
0
  ent->userfn(status, ent->userdata);
136
0
}
137
138
/** Use waitpid() to wait for all children that have exited, and invoke any
139
 * callbacks registered for them. */
140
void
141
notify_pending_waitpid_callbacks(void)
142
0
{
143
  /* I was going to call this function reap_zombie_children(), but
144
   * that makes it sound way more exciting than it really is. */
145
0
  pid_t child;
146
0
  int status = 0;
147
148
0
  while ((child = waitpid(-1, &status, WNOHANG)) > 0) {
149
0
    status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
150
0
    notify_waitpid_callback_by_pid(child, status);
151
0
    status = 0; /* should be needless */
152
0
  }
153
0
}
154
155
#endif /* !defined(_WIN32) */