summaryrefslogtreecommitdiffstats
path: root/src/common/Preforker.h
blob: d34179b40204a71099933702b8dbe9aaeb8908d2 (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
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#ifndef CEPH_COMMON_PREFORKER_H
#define CEPH_COMMON_PREFORKER_H

#include <signal.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sstream>

#include "common/errno.h"
#include "common/safe_io.h"
#include "include/ceph_assert.h"
#include "include/compat.h"
#include "include/sock_compat.h"

/**
 * pre-fork fork/daemonize helper class
 *
 * Hide the details of letting a process fork early, do a bunch of
 * initialization work that may spam stdout or exit with an error, and
 * then daemonize.  The exit() method will either exit directly (if we
 * haven't forked) or pass a message to the parent with the error if
 * we have.
 */
class Preforker {
  pid_t childpid;
  bool forked;
  int fd[2];  // parent's, child's

public:
  Preforker()
    : childpid(0),
      forked(false)
  {}

  int prefork(std::string &err) {
    ceph_assert(!forked);
    std::ostringstream oss;
    int r = socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, fd);
    if (r < 0) {
      int e = errno;
      oss << "[" << getpid() << "]: unable to create socketpair: " << cpp_strerror(e);
      err = oss.str();
      return (errno = e, -1);
    }

    struct sigaction sa;
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGHUP, &sa, nullptr) != 0) {
      int e = errno;
      oss << "[" << getpid() << "]: unable to ignore SIGHUP: " << cpp_strerror(e);
      err = oss.str();
      return (errno = e, -1);
    }

    forked = true;

    childpid = fork();
    if (childpid < 0) {
      int e = errno;
      oss << "[" << getpid() << "]: unable to fork: " << cpp_strerror(e);
      err = oss.str();
      return (errno = e, -1);
    }
    if (is_child()) {
      ::close(fd[0]);
    } else {
      ::close(fd[1]);
    }
    return 0;
  }

  int get_signal_fd() const {
    return forked ? fd[1] : 0;
  }

  bool is_child() {
    return childpid == 0;
  }

  bool is_parent() {
    return childpid != 0;
  }

  int parent_wait(std::string &err_msg) {
    ceph_assert(forked);

    int r = -1;
    std::ostringstream oss;
    int err = safe_read_exact(fd[0], &r, sizeof(r));
    if (err == 0 && r == -1) {
      // daemonize
      ::close(0);
      ::close(1);
      ::close(2);
    } else if (err) {
      oss << "[" << getpid() << "]: " << cpp_strerror(err);
    } else {
      // wait for child to exit
      int status;
      err = waitpid(childpid, &status, 0);
      if (err < 0) {
        oss << "[" << getpid() << "]" << " waitpid error: " << cpp_strerror(err);
      } else if (WIFSIGNALED(status)) {
        oss << "[" << getpid() << "]" << " exited with a signal";
      } else if (!WIFEXITED(status)) {
        oss << "[" << getpid() << "]" << " did not exit normally";
      } else {
        err = WEXITSTATUS(status);
        if (err != 0)
         oss << "[" << getpid() << "]" << " returned exit_status " << cpp_strerror(err);
      }
    }
    err_msg = oss.str();
    return err;
  }

  int signal_exit(int r) {
    if (forked) {
      /* If we get an error here, it's too late to do anything reasonable about it. */
      [[maybe_unused]] auto n = safe_write(fd[1], &r, sizeof(r));
    }
    return r;
  }
  void exit(int r) {
    if (is_child())
        signal_exit(r);
    ::exit(r);
  }

  void daemonize() {
    ceph_assert(forked);
    static int r = -1;
    int r2 = ::write(fd[1], &r, sizeof(r));
    r += r2;  // make the compiler shut up about the unused return code from ::write(2).
  }
  
};

#endif