aboutsummaryrefslogtreecommitdiff
path: root/src/common/util_process.c
blob: 8ccf7f38f93167491aa9e368dfe975ae834df5d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* Copyright (c) 2003-2004, Roger Dingledine
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 * Copyright (c) 2007-2013, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file util_process.c
 * \brief utility functions for launching processes and checking their
 *    status. These functions are kept separately from procmon so that they
 *    won't require linking against libevent.
 **/

#include "orconfig.h"

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#include "compat.h"
#include "util.h"
#include "torlog.h"
#include "util_process.h"
#include "ht.h"

/* ================================================== */
/* Convenience structures for handlers for waitpid().
 *
 * The tor_process_monitor*() code above doesn't use them, since it is for
 * monitoring a non-child process.
 */

#ifndef _WIN32

/** Mapping from a PID to a userfn/userdata pair. */
struct waitpid_callback_t {
  HT_ENTRY(waitpid_callback_t) node;
  pid_t pid;

  void (*userfn)(int, void *userdata);
  void *userdata;

  unsigned running;
};

static INLINE unsigned int
process_map_entry_hash_(const waitpid_callback_t *ent)
{
  return (unsigned) ent->pid;
}

static INLINE unsigned int
process_map_entries_eq_(const waitpid_callback_t *a, const waitpid_callback_t *b)
{
  return a->pid == b->pid;
}

static HT_HEAD(process_map, waitpid_callback_t) process_map = HT_INITIALIZER();

HT_PROTOTYPE(process_map, waitpid_callback_t, node, process_map_entry_hash_,
             process_map_entries_eq_);
HT_GENERATE(process_map, waitpid_callback_t, node, process_map_entry_hash_,
            process_map_entries_eq_, 0.6, malloc, realloc, free);

/**
 * Begin monitoring the child pid <b>pid</b> to see if we get a SIGCHLD for
 * it.  If we eventually do, call <b>fn</b>, passing it the exit status (as
 * yielded by waitpid) and the pointer <b>arg</b>.
 *
 * To cancel this, or clean up after it has triggered, call
 * clear_waitpid_callback().
 */
waitpid_callback_t *
set_waitpid_callback(pid_t pid, void (*fn)(int, void *), void *arg)
{
  waitpid_callback_t *old_ent;
  waitpid_callback_t *ent = tor_malloc_zero(sizeof(waitpid_callback_t));
  ent->pid = pid;
  ent->userfn = fn;
  ent->userdata = arg;
  ent->running = 1;

  old_ent = HT_REPLACE(process_map, &process_map, ent);
  if (old_ent) {
    log_warn(LD_BUG, "Replaced a waitpid monitor on pid %u. That should be "
             "impossible.", (unsigned) pid);
    old_ent->running = 0;
  }

  return ent;
}

/**
 * Cancel a waitpid_callback_t, or clean up after one has triggered. Releases
 * all storage held by <b>ent</b>.
 */
void
clear_waitpid_callback(waitpid_callback_t *ent)
{
  waitpid_callback_t *old_ent;
  if (ent == NULL)
    return;

  if (ent->running) {
    old_ent = HT_REMOVE(process_map, &process_map, ent);
    if (old_ent != ent) {
      log_warn(LD_BUG, "Couldn't remove waitpid monitor for pid %u.",
               (unsigned) ent->pid);
      return;
    }
  }

  tor_free(ent);
}

/** Helper: find the callack for <b>pid</b>; if there is one, run it,
 * reporting the exit status as <b>status</b>. */
static void
notify_waitpid_callback_by_pid(pid_t pid, int status)
{
  waitpid_callback_t search, *ent;

  search.pid = pid;
  ent = HT_REMOVE(process_map, &process_map, &search);
  if (!ent || !ent->running) {
    log_info(LD_GENERAL, "Child process %u has exited; no callback was "
             "registered", (unsigned)pid);
    return;
  }

  log_info(LD_GENERAL, "Child process %u has exited; running callback.",
           (unsigned)pid);

  ent->running = 0;
  ent->userfn(status, ent->userdata);
}

/** Use waitpid() to wait for all children that have exited, and invoke any
 * callbacks registered for them. */
void
notify_pending_waitpid_callbacks(void)
{
  /* I was going to call this function reap_zombie_children(), but
   * that makes it sound way more exciting than it really is. */
  pid_t child;
  int status = 0;

  while ((child = waitpid(-1, &status, WNOHANG)) > 0) {
    notify_waitpid_callback_by_pid(child, status);
    status = 0; /* should be needless */
  }
}

#endif