Apache Mesos
subprocess_posix.hpp
Go to the documentation of this file.
1 // Licensed under the Apache License, Version 2.0 (the "License");
2 // you may not use this file except in compliance with the License.
3 // You may obtain a copy of the License at
4 //
5 // http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License
12 
13 #ifndef __PROCESS_POSIX_SUBPROCESS_HPP__
14 #define __PROCESS_POSIX_SUBPROCESS_HPP__
15 
16 #ifdef __linux__
17 #include <sys/prctl.h>
18 #endif // __linux__
19 #include <sys/types.h>
20 
21 #include <string>
22 
23 #include <glog/logging.h>
24 
25 #include <process/subprocess.hpp>
26 
27 #include <stout/check.hpp>
28 #include <stout/error.hpp>
29 #include <stout/exit.hpp>
30 #include <stout/foreach.hpp>
31 #include <stout/hashset.hpp>
32 #include <stout/nothing.hpp>
33 #include <stout/lambda.hpp>
34 #include <stout/none.hpp>
35 #include <stout/option.hpp>
36 #include <stout/os.hpp>
37 #include <stout/try.hpp>
38 #include <stout/unreachable.hpp>
39 
40 #include <stout/os/close.hpp>
41 #include <stout/os/environment.hpp>
42 #include <stout/os/fcntl.hpp>
43 #include <stout/os/signals.hpp>
44 #include <stout/os/strerror.hpp>
45 
46 namespace process {
47 namespace internal {
48 
49 static void close(std::initializer_list<int_fd> fds);
50 
51 
52 static void close(
55  const Subprocess::IO::OutputFileDescriptors& stderrfds);
56 
57 
58 inline pid_t defaultClone(const lambda::function<int()>& func)
59 {
60  pid_t pid = ::fork();
61  if (pid == -1) {
62  return -1;
63  } else if (pid == 0) {
64  // Child.
65  ::exit(func());
66  UNREACHABLE();
67  } else {
68  // Parent.
69  return pid;
70  }
71 }
72 
73 
74 // This function will invoke `os::cloexec` on all specified file
75 // descriptors that are valid (i.e., not `None` and >= 0).
77  const InputFileDescriptors& stdinfds,
78  const OutputFileDescriptors& stdoutfds,
79  const OutputFileDescriptors& stderrfds)
80 {
81  hashset<int> fds = {
82  stdinfds.read,
83  stdinfds.write.getOrElse(-1),
84  stdoutfds.read.getOrElse(-1),
85  stdoutfds.write,
86  stderrfds.read.getOrElse(-1),
87  stderrfds.write
88  };
89 
90  foreach (int fd, fds) {
91  if (fd >= 0) {
93  if (cloexec.isError()) {
94  return Error(cloexec.error());
95  }
96  }
97  }
98 
99  return Nothing();
100 }
101 
102 
103 // The main entry of the child process.
104 //
105 // NOTE: This function has to be async signal safe.
106 inline int childMain(
107  const std::string& path,
108  char** argv,
109  char** envp,
110  const InputFileDescriptors& stdinfds,
111  const OutputFileDescriptors& stdoutfds,
112  const OutputFileDescriptors& stderrfds,
113  bool blocking,
114  int pipes[2],
115  const std::vector<Subprocess::ChildHook>& child_hooks)
116 {
117  // Close parent's end of the pipes.
118  if (stdinfds.write.isSome()) {
119  ::close(stdinfds.write.get());
120  }
121  if (stdoutfds.read.isSome()) {
122  ::close(stdoutfds.read.get());
123  }
124  if (stderrfds.read.isSome()) {
125  ::close(stderrfds.read.get());
126  }
127 
128  // Currently we will block the child's execution of the new process
129  // until all the parent hooks (if any) have executed.
130  if (blocking) {
131  ::close(pipes[1]);
132  }
133 
134  // Redirect I/O for stdin/stdout/stderr.
135  while (::dup2(stdinfds.read, STDIN_FILENO) == -1 && errno == EINTR);
136  while (::dup2(stdoutfds.write, STDOUT_FILENO) == -1 && errno == EINTR);
137  while (::dup2(stderrfds.write, STDERR_FILENO) == -1 && errno == EINTR);
138 
139  // Close the copies. We need to make sure that we do not close the
140  // file descriptor assigned to stdin/stdout/stderr in case the
141  // parent has closed stdin/stdout/stderr when calling this
142  // function (in that case, a dup'ed file descriptor may have the
143  // same file descriptor number as stdin/stdout/stderr).
144  //
145  // We also need to ensure that we don't "double close" any file
146  // descriptors in the case where one of stdinfds.read,
147  // stdoutfds.write, or stdoutfds.write are equal.
148  if (stdinfds.read != STDIN_FILENO &&
149  stdinfds.read != STDOUT_FILENO &&
150  stdinfds.read != STDERR_FILENO) {
151  ::close(stdinfds.read);
152  }
153  if (stdoutfds.write != STDIN_FILENO &&
154  stdoutfds.write != STDOUT_FILENO &&
155  stdoutfds.write != STDERR_FILENO &&
156  stdoutfds.write != stdinfds.read) {
157  ::close(stdoutfds.write);
158  }
159  if (stderrfds.write != STDIN_FILENO &&
160  stderrfds.write != STDOUT_FILENO &&
161  stderrfds.write != STDERR_FILENO &&
162  stderrfds.write != stdinfds.read &&
163  stderrfds.write != stdoutfds.write) {
164  ::close(stderrfds.write);
165  }
166 
167  if (blocking) {
168  // Do a blocking read on the pipe until the parent signals us to
169  // continue.
170  char dummy;
171  ssize_t length;
172  while ((length = ::read(pipes[0], &dummy, sizeof(dummy))) == -1 &&
173  errno == EINTR);
174 
175  if (length != sizeof(dummy)) {
176  ABORT("Failed to synchronize with parent");
177  }
178 
179  // Now close the pipe as we don't need it anymore.
180  ::close(pipes[0]);
181  }
182 
183  // Run the child hooks.
184  foreach (const Subprocess::ChildHook& hook, child_hooks) {
185  Try<Nothing> callback = hook();
186 
187  // If the callback failed, we should abort execution.
188  if (callback.isError()) {
189  ABORT("Failed to execute Subprocess::ChildHook: " + callback.error());
190  }
191  }
192 
193  os::execvpe(path.c_str(), argv, envp);
194 
195  SAFE_EXIT(
196  errno, "Failed to os::execvpe on path '%s': %d", path.c_str(), errno);
197 }
198 
199 
201  const std::string& path,
202  std::vector<std::string> argv,
203  const Option<std::map<std::string, std::string>>& environment,
204  const Option<lambda::function<
205  pid_t(const lambda::function<int()>&)>>& _clone,
206  const std::vector<Subprocess::ParentHook>& parent_hooks,
207  const std::vector<Subprocess::ChildHook>& child_hooks,
208  const InputFileDescriptors stdinfds,
209  const OutputFileDescriptors stdoutfds,
210  const OutputFileDescriptors stderrfds)
211 {
212  // The real arguments that will be passed to 'os::execvpe'. We need
213  // to construct them here before doing the clone as it might not be
214  // async signal safe to perform the memory allocation.
215  char** _argv = new char*[argv.size() + 1];
216  for (size_t i = 0; i < argv.size(); i++) {
217  _argv[i] = (char*) argv[i].c_str();
218  }
219  _argv[argv.size()] = nullptr;
220 
221  // Like above, we need to construct the environment that we'll pass
222  // to 'os::execvpe' as it might not be async-safe to perform the
223  // memory allocations.
224  char** envp = os::raw::environment();
225 
226  if (environment.isSome()) {
227  // NOTE: We add 1 to the size for a `nullptr` terminator.
228  envp = new char*[environment.get().size() + 1];
229 
230  size_t index = 0;
231  foreachpair (
232  const std::string& key,
233  const std::string& value, environment.get()) {
234  std::string entry = key + "=" + value;
235  envp[index] = new char[entry.size() + 1];
236  strncpy(envp[index], entry.c_str(), entry.size() + 1);
237  ++index;
238  }
239 
240  envp[index] = nullptr;
241  }
242 
243  // Determine the function to clone the child process. If the user
244  // does not specify the clone function, we will use the default.
245  lambda::function<pid_t(const lambda::function<int()>&)> clone =
246  (_clone.isSome() ? _clone.get() : defaultClone);
247 
248  // Currently we will block the child's execution of the new process
249  // until all the `parent_hooks` (if any) have executed.
250  std::array<int, 2> pipes;
251  const bool blocking = !parent_hooks.empty();
252 
253  if (blocking) {
254  // We assume this should not fail under reasonable conditions so we
255  // use CHECK.
257  CHECK_SOME(pipe);
258 
259  pipes = pipe.get();
260  }
261 
262  // Now, clone the child process.
263  pid_t pid = clone(lambda::bind(
264  &childMain,
265  path,
266  _argv,
267  envp,
268  stdinfds,
269  stdoutfds,
270  stderrfds,
271  blocking,
272  pipes.data(),
273  child_hooks));
274 
275  delete[] _argv;
276 
277  // Need to delete 'envp' if we had environment variables passed to
278  // us and we needed to allocate the space.
279  if (environment.isSome()) {
280  CHECK_NE(os::raw::environment(), envp);
281 
282  // We ignore the last 'envp' entry since it is nullptr.
283  for (size_t index = 0; index < environment->size(); index++) {
284  delete[] envp[index];
285  }
286 
287  delete[] envp;
288  }
289 
290  if (pid == -1) {
291  // Save the errno as 'close' below might overwrite it.
292  ErrnoError error("Failed to clone");
293  internal::close(stdinfds, stdoutfds, stderrfds);
294 
295  if (blocking) {
296  os::close(pipes[0]);
297  os::close(pipes[1]);
298  }
299 
300  return error;
301  }
302 
303  // Close the child-ends of the file descriptors that are created by
304  // this function.
305  internal::close({stdinfds.read, stdoutfds.write, stderrfds.write});
306 
307  if (blocking) {
308  os::close(pipes[0]);
309 
310  // Run the parent hooks.
311  foreach (const Subprocess::ParentHook& hook, parent_hooks) {
312  Try<Nothing> parentSetup = hook.parent_setup(pid);
313 
314  // If the hook callback fails, we shouldn't proceed with the
315  // execution and hence the child process should be killed.
316  if (parentSetup.isError()) {
317  LOG(WARNING)
318  << "Failed to execute Subprocess::ParentHook in parent for child '"
319  << pid << "': " << parentSetup.error();
320 
321  os::close(pipes[1]);
322 
323  // Ensure the child is killed.
324  ::kill(pid, SIGKILL);
325 
326  return Error(
327  "Failed to execute Subprocess::ParentHook in parent for child '" +
328  stringify(pid) + "': " + parentSetup.error());
329  }
330  }
331 
332  // Now that we've executed the parent hooks, we can signal the child to
333  // continue by writing to the pipe.
334  char dummy;
335  ssize_t length;
336  while ((length = ::write(pipes[1], &dummy, sizeof(dummy))) == -1 &&
337  errno == EINTR);
338 
339  os::close(pipes[1]);
340 
341  if (length != sizeof(dummy)) {
342  // Ensure the child is killed.
343  ::kill(pid, SIGKILL);
344 
345  return Error("Failed to synchronize child process");
346  }
347  }
348 
349  return pid;
350 }
351 
352 } // namespace internal {
353 } // namespace process {
354 
355 #endif // __PROCESS_POSIX_SUBPROCESS_HPP__
Try< Nothing > dup2(int oldFd, int newFd)
Definition: os.hpp:412
SSIZE_T ssize_t
Definition: windows.hpp:192
Definition: nothing.hpp:16
Definition: errorbase.hpp:35
Definition: option.hpp:28
Try< pid_t > clone(pid_t target, int nstypes, const lambda::function< int()> &f, int flags)
Performs an os::clone after entering a set of namespaces for the specified target process...
#define ABORT(...)
Definition: abort.hpp:40
Subprocess::IO::OutputFileDescriptors OutputFileDescriptors
Definition: subprocess.hpp:342
const mode_t SIGKILL
Definition: windows.hpp:348
A ChildHook can be passed to a subprocess call.
Definition: subprocess.hpp:189
Definition: try.hpp:34
Definition: hashset.hpp:53
int_fd read
Definition: subprocess.hpp:75
For output file descriptors a child writes to the write file descriptor and a parent may read from th...
Definition: subprocess.hpp:88
pid_t defaultClone(const lambda::function< int()> &func)
Definition: subprocess_posix.hpp:58
Definition: errorbase.hpp:49
#define STDERR_FILENO
Definition: windows.hpp:161
Try< Nothing > cloexec(const InputFileDescriptors &stdinfds, const OutputFileDescriptors &stdoutfds, const OutputFileDescriptors &stderrfds)
Definition: subprocess_posix.hpp:76
bool isSome() const
Definition: option.hpp:115
Subprocess::IO::InputFileDescriptors InputFileDescriptors
Definition: subprocess.hpp:341
Environment * environment
DWORD pid_t
Definition: windows.hpp:187
#define CHECK_SOME(expression)
Definition: check.hpp:44
#define STDOUT_FILENO
Definition: windows.hpp:160
int_fd write
Definition: subprocess.hpp:91
Try< Nothing > close(int fd)
Definition: close.hpp:24
Try< Nothing > cloexec(int fd)
Definition: fcntl.hpp:27
Try< std::array< int, 2 > > pipe()
Definition: pipe.hpp:26
const T & get() const &
Definition: option.hpp:118
#define foreachpair(KEY, VALUE, ELEMS)
Definition: foreach.hpp:51
For input file descriptors a child reads from the read file descriptor and a parent may write to the ...
Definition: subprocess.hpp:73
Try< pid_t > cloneChild(const std::string &path, std::vector< std::string > argv, const Option< std::map< std::string, std::string >> &environment, const Option< lambda::function< pid_t(const lambda::function< int()> &)>> &_clone, const std::vector< Subprocess::ParentHook > &parent_hooks, const std::vector< Subprocess::ChildHook > &child_hooks, const InputFileDescriptors stdinfds, const OutputFileDescriptors stdoutfds, const OutputFileDescriptors stderrfds)
Definition: subprocess_posix.hpp:200
#define STDIN_FILENO
Definition: windows.hpp:159
static Try error(const E &e)
Definition: try.hpp:42
#define UNREACHABLE()
Definition: unreachable.hpp:22
const lambda::function< Try< Nothing >pid_t)> parent_setup
The callback that must be specified for execution after the child has been cloned, but before it starts executing the new process.
Definition: subprocess.hpp:162
Result< Process > process(pid_t pid)
Definition: freebsd.hpp:30
bool isError() const
Definition: try.hpp:71
Result< Credentials > read(const Path &path)
Definition: credentials.hpp:35
std::string error(const std::string &msg, uint32_t code)
int execvpe(const std::string &command, const std::vector< std::string > &argv, const std::map< std::string, std::string > &envp)
Definition: shell.hpp:394
Protocol< WriteRequest, WriteResponse > write
Option< int_fd > read
Definition: subprocess.hpp:90
Try< Nothing > kill(const std::string &hierarchy, const std::string &cgroup, int signal)
Try< Nothing > bind(int_fd s, const Address &address)
Definition: network.hpp:46
Option< int_fd > write
Definition: subprocess.hpp:76
A hook can be passed to a subprocess call.
Definition: subprocess.hpp:151
std::string stringify(int flags)
T getOrElse(const T &_t) const
Definition: option.hpp:127
#define SAFE_EXIT(status, fmt,...)
Definition: exit.hpp:42
const T & get() const
Definition: try.hpp:73
int childMain(const std::string &path, char **argv, char **envp, const InputFileDescriptors &stdinfds, const OutputFileDescriptors &stdoutfds, const OutputFileDescriptors &stderrfds, bool blocking, int pipes[2], const std::vector< Subprocess::ChildHook > &child_hooks)
Definition: subprocess_posix.hpp:106
char ** environment()
Definition: environment.hpp:66