tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

waitpid.c (4145B)


      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 {
     48  return (unsigned) ent->pid;
     49 }
     50 
     51 static inline unsigned int
     52 process_map_entries_eq_(const waitpid_callback_t *a,
     53                        const waitpid_callback_t *b)
     54 {
     55  return a->pid == b->pid;
     56 }
     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 {
     76  waitpid_callback_t *old_ent;
     77  waitpid_callback_t *ent = tor_malloc_zero(sizeof(waitpid_callback_t));
     78  ent->pid = pid;
     79  ent->userfn = fn;
     80  ent->userdata = arg;
     81  ent->running = 1;
     82 
     83  old_ent = HT_REPLACE(process_map, &process_map, ent);
     84  if (old_ent) {
     85    log_warn(LD_BUG, "Replaced a waitpid monitor on pid %u. That should be "
     86             "impossible.", (unsigned) pid);
     87    old_ent->running = 0;
     88  }
     89 
     90  return ent;
     91 }
     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 {
    100  waitpid_callback_t *old_ent;
    101  if (ent == NULL)
    102    return;
    103 
    104  if (ent->running) {
    105    old_ent = HT_REMOVE(process_map, &process_map, ent);
    106    if (old_ent != ent) {
    107      log_warn(LD_BUG, "Couldn't remove waitpid monitor for pid %u.",
    108               (unsigned) ent->pid);
    109      return;
    110    }
    111  }
    112 
    113  tor_free(ent);
    114 }
    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 {
    121  waitpid_callback_t search, *ent;
    122 
    123  search.pid = pid;
    124  ent = HT_REMOVE(process_map, &process_map, &search);
    125  if (!ent || !ent->running) {
    126    log_info(LD_GENERAL, "Child process %u has exited; no callback was "
    127             "registered", (unsigned)pid);
    128    return;
    129  }
    130 
    131  log_info(LD_GENERAL, "Child process %u has exited; running callback.",
    132           (unsigned)pid);
    133 
    134  ent->running = 0;
    135  ent->userfn(status, ent->userdata);
    136 }
    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 {
    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  pid_t child;
    146  int status = 0;
    147 
    148  while ((child = waitpid(-1, &status, WNOHANG)) > 0) {
    149    status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
    150    notify_waitpid_callback_by_pid(child, status);
    151    status = 0; /* should be needless */
    152  }
    153 }
    154 
    155 #endif /* !defined(_WIN32) */