1    	// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2    	// vim: ts=8 sw=2 smarttab
3    	
4    	// Run a function in a forked child, with a timeout.
5    	
6    	#pragma once
7    	
8    	#include <functional>
9    	#include <iostream>
10   	#include <ostream>
11   	
12   	#include <signal.h>
13   	#include <sys/wait.h>
14   	#include <sys/types.h>
15   	
16   	#include "include/ceph_assert.h"
17   	#include "common/errno.h"
18   	
19   	static void _fork_function_dummy_sighandler(int sig) {}
20   	
21   	// Run a function post-fork, with a timeout.  Function can return
22   	// int8_t only due to unix exit code limitations.  Returns -ETIMEDOUT
23   	// if timeout is reached.
24   	
25   	static inline int fork_function(
26   	  int timeout,
27   	  std::ostream& errstr,
28   	  std::function<int8_t(void)> f)
29   	{
30   	  // first fork the forker.
31   	  pid_t forker_pid = fork();
32   	  if (forker_pid) {
33   	    // just wait
34   	    int status;
(6) Event example_checked: Example 3: "waitpid(forker_pid, &status, 0)" has its value checked in "waitpid(forker_pid, &status, 0) == -1".
Also see events: [check_return][example_checked][example_checked][example_assign][example_checked][example_assign][example_checked]
35   	    while (waitpid(forker_pid, &status, 0) == -1) {
36   	      ceph_assert(errno == EINTR);
37   	    }
38   	    if (WIFSIGNALED(status)) {
39   	      errstr << ": got signal: " << WTERMSIG(status) << "\n";
40   	      return 128 + WTERMSIG(status);
41   	    }
42   	    if (WIFEXITED(status)) {
43   	      int8_t r = WEXITSTATUS(status);
44   	      errstr << ": exit status: " << (int)r << "\n";
45   	      return r;
46   	    }
47   	    errstr << ": waitpid: unknown status returned\n";
48   	    return -1;
49   	  }
50   	
51   	  // we are forker (first child)
52   	
53   	  // close all fds
54   	  int maxfd = sysconf(_SC_OPEN_MAX);
55   	  if (maxfd == -1)
56   	    maxfd = 16384;
57   	  for (int fd = 0; fd <= maxfd; fd++) {
58   	    if (fd == STDIN_FILENO)
59   	      continue;
60   	    if (fd == STDOUT_FILENO)
61   	      continue;
62   	    if (fd == STDERR_FILENO)
63   	      continue;
64   	    ::close(fd);
65   	  }
66   	
67   	  sigset_t mask, oldmask;
68   	  int pid;
69   	
70   	  // Restore default action for SIGTERM in case the parent process decided
71   	  // to ignore it.
72   	  if (signal(SIGTERM, SIG_DFL) == SIG_ERR) {
73   	    std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
74   	    goto fail_exit;
75   	  }
76   	  // Because SIGCHLD is ignored by default, setup dummy handler for it,
77   	  // so we can mask it.
78   	  if (signal(SIGCHLD, _fork_function_dummy_sighandler) == SIG_ERR) {
79   	    std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
80   	    goto fail_exit;
81   	  }
82   	  // Setup timeout handler.
83   	  if (signal(SIGALRM, timeout_sighandler) == SIG_ERR) {
84   	    std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
85   	    goto fail_exit;
86   	  }
87   	  // Block interesting signals.
88   	  sigemptyset(&mask);
89   	  sigaddset(&mask, SIGINT);
90   	  sigaddset(&mask, SIGTERM);
91   	  sigaddset(&mask, SIGCHLD);
92   	  sigaddset(&mask, SIGALRM);
93   	  if (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1) {
94   	    std::cerr << ": sigprocmask failed: "
95   		      << cpp_strerror(errno) << "\n";
96   	    goto fail_exit;
97   	  }
98   	
99   	  pid = fork();
100  	
101  	  if (pid == -1) {
102  	    std::cerr << ": fork failed: " << cpp_strerror(errno) << "\n";
103  	    goto fail_exit;
104  	  }
105  	
106  	  if (pid == 0) { // we are second child
107  	    // Restore old sigmask.
108  	    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) {
109  	      std::cerr << ": sigprocmask failed: "
110  			<< cpp_strerror(errno) << "\n";
111  	      goto fail_exit;
112  	    }
113  	    (void)setpgid(0, 0); // Become process group leader.
114  	    int8_t r = f();
115  	    _exit((uint8_t)r);
116  	  }
117  	
118  	  // Parent
119  	  (void)alarm(timeout);
120  	
121  	  for (;;) {
122  	    int signo;
123  	    if (sigwait(&mask, &signo) == -1) {
124  	      std::cerr << ": sigwait failed: " << cpp_strerror(errno) << "\n";
125  	      goto fail_exit;
126  	    }
127  	    switch (signo) {
128  	    case SIGCHLD:
129  	      int status;
130  	      if (waitpid(pid, &status, WNOHANG) == -1) {
131  		std::cerr << ": waitpid failed: " << cpp_strerror(errno) << "\n";
132  		goto fail_exit;
133  	      }
134  	      if (WIFEXITED(status))
135  		_exit(WEXITSTATUS(status));
136  	      if (WIFSIGNALED(status))
137  		_exit(128 + WTERMSIG(status));
138  	      std::cerr << ": unknown status returned\n";
139  	      goto fail_exit;
140  	    case SIGINT:
141  	    case SIGTERM:
142  	      // Pass SIGINT and SIGTERM, which are usually used to terminate
143  	      // a process, to the child.
144  	      if (::kill(pid, signo) == -1) {
145  		std::cerr << ": kill failed: " << cpp_strerror(errno) << "\n";
146  		goto fail_exit;
147  	      }
148  	      continue;
149  	    case SIGALRM:
150  	      std::cerr << ": timed out (" << timeout << " sec)\n";
151  	      if (::killpg(pid, SIGKILL) == -1) {
152  		std::cerr << ": kill failed: " << cpp_strerror(errno) << "\n";
153  		goto fail_exit;
154  	      }
155  	      _exit(-ETIMEDOUT);
156  	    default:
157  	      std::cerr << ": sigwait: invalid signal: " << signo << "\n";
158  	      goto fail_exit;
159  	    }
160  	  }
161  	  return 0;
162  	fail_exit:
163  	  _exit(EXIT_FAILURE);
164  	}
165