1b0d29bc4SBrooks Davis // Copyright 2015 The Kyua Authors.
2b0d29bc4SBrooks Davis // All rights reserved.
3b0d29bc4SBrooks Davis //
4b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6b0d29bc4SBrooks Davis // met:
7b0d29bc4SBrooks Davis //
8b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer.
10b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer in the
12b0d29bc4SBrooks Davis // documentation and/or other materials provided with the distribution.
13b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14b0d29bc4SBrooks Davis // may be used to endorse or promote products derived from this software
15b0d29bc4SBrooks Davis // without specific prior written permission.
16b0d29bc4SBrooks Davis //
17b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28b0d29bc4SBrooks Davis
29b0d29bc4SBrooks Davis #include "utils/process/executor.ipp"
30b0d29bc4SBrooks Davis
31b0d29bc4SBrooks Davis #if defined(HAVE_CONFIG_H)
32b0d29bc4SBrooks Davis #include "config.h"
33b0d29bc4SBrooks Davis #endif
34b0d29bc4SBrooks Davis
35b0d29bc4SBrooks Davis extern "C" {
36b0d29bc4SBrooks Davis #include <sys/types.h>
37b0d29bc4SBrooks Davis #include <sys/wait.h>
38b0d29bc4SBrooks Davis
39b0d29bc4SBrooks Davis #include <signal.h>
40b0d29bc4SBrooks Davis }
41b0d29bc4SBrooks Davis
426b13d60bSMuhammad Moinur Rahman #include <forward_list>
43b0d29bc4SBrooks Davis #include <fstream>
44b0d29bc4SBrooks Davis #include <map>
45b0d29bc4SBrooks Davis #include <memory>
46b0d29bc4SBrooks Davis #include <stdexcept>
476b13d60bSMuhammad Moinur Rahman #include <utility>
48b0d29bc4SBrooks Davis
49b0d29bc4SBrooks Davis #include "utils/datetime.hpp"
50b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
51b0d29bc4SBrooks Davis #include "utils/fs/auto_cleaners.hpp"
52b0d29bc4SBrooks Davis #include "utils/fs/exceptions.hpp"
53b0d29bc4SBrooks Davis #include "utils/fs/operations.hpp"
54b0d29bc4SBrooks Davis #include "utils/fs/path.hpp"
55b0d29bc4SBrooks Davis #include "utils/logging/macros.hpp"
56b0d29bc4SBrooks Davis #include "utils/logging/operations.hpp"
57b0d29bc4SBrooks Davis #include "utils/noncopyable.hpp"
58b0d29bc4SBrooks Davis #include "utils/optional.ipp"
59b0d29bc4SBrooks Davis #include "utils/passwd.hpp"
60b0d29bc4SBrooks Davis #include "utils/process/child.ipp"
61b0d29bc4SBrooks Davis #include "utils/process/deadline_killer.hpp"
62b0d29bc4SBrooks Davis #include "utils/process/isolation.hpp"
63b0d29bc4SBrooks Davis #include "utils/process/operations.hpp"
64b0d29bc4SBrooks Davis #include "utils/process/status.hpp"
65b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
66b0d29bc4SBrooks Davis #include "utils/signals/interrupts.hpp"
67b0d29bc4SBrooks Davis #include "utils/signals/timer.hpp"
68b0d29bc4SBrooks Davis
69b0d29bc4SBrooks Davis namespace datetime = utils::datetime;
70b0d29bc4SBrooks Davis namespace executor = utils::process::executor;
71b0d29bc4SBrooks Davis namespace fs = utils::fs;
72b0d29bc4SBrooks Davis namespace logging = utils::logging;
73b0d29bc4SBrooks Davis namespace passwd = utils::passwd;
74b0d29bc4SBrooks Davis namespace process = utils::process;
75b0d29bc4SBrooks Davis namespace signals = utils::signals;
76b0d29bc4SBrooks Davis
77b0d29bc4SBrooks Davis using utils::none;
78b0d29bc4SBrooks Davis using utils::optional;
79b0d29bc4SBrooks Davis
80b0d29bc4SBrooks Davis
81b0d29bc4SBrooks Davis namespace {
82b0d29bc4SBrooks Davis
83b0d29bc4SBrooks Davis
84b0d29bc4SBrooks Davis /// Template for temporary directories created by the executor.
85b0d29bc4SBrooks Davis static const char* work_directory_template = PACKAGE_TARNAME ".XXXXXX";
86b0d29bc4SBrooks Davis
87b0d29bc4SBrooks Davis
88b0d29bc4SBrooks Davis /// Mapping of active subprocess PIDs to their execution data.
89b0d29bc4SBrooks Davis typedef std::map< int, executor::exec_handle > exec_handles_map;
90b0d29bc4SBrooks Davis
91b0d29bc4SBrooks Davis
92b0d29bc4SBrooks Davis } // anonymous namespace
93b0d29bc4SBrooks Davis
94b0d29bc4SBrooks Davis
95b0d29bc4SBrooks Davis /// Basename of the file containing the stdout of the subprocess.
96b0d29bc4SBrooks Davis const char* utils::process::executor::detail::stdout_name = "stdout.txt";
97b0d29bc4SBrooks Davis
98b0d29bc4SBrooks Davis
99b0d29bc4SBrooks Davis /// Basename of the file containing the stderr of the subprocess.
100b0d29bc4SBrooks Davis const char* utils::process::executor::detail::stderr_name = "stderr.txt";
101b0d29bc4SBrooks Davis
102b0d29bc4SBrooks Davis
103b0d29bc4SBrooks Davis /// Basename of the subdirectory in which the subprocess is actually executed.
104b0d29bc4SBrooks Davis ///
105b0d29bc4SBrooks Davis /// This is a subdirectory of the "unique work directory" generated for the
106b0d29bc4SBrooks Davis /// subprocess so that our code can create control files on disk and not
107b0d29bc4SBrooks Davis /// get them clobbered by the subprocess's activity.
108b0d29bc4SBrooks Davis const char* utils::process::executor::detail::work_subdir = "work";
109b0d29bc4SBrooks Davis
110b0d29bc4SBrooks Davis
111b0d29bc4SBrooks Davis /// Prepares a subprocess to run a user-provided hook in a controlled manner.
112b0d29bc4SBrooks Davis ///
113b0d29bc4SBrooks Davis /// \param unprivileged_user User to switch to if not none.
114b0d29bc4SBrooks Davis /// \param control_directory Path to the subprocess-specific control directory.
115b0d29bc4SBrooks Davis /// \param work_directory Path to the subprocess-specific work directory.
116b0d29bc4SBrooks Davis void
setup_child(const optional<passwd::user> unprivileged_user,const fs::path & control_directory,const fs::path & work_directory)117b0d29bc4SBrooks Davis utils::process::executor::detail::setup_child(
118b0d29bc4SBrooks Davis const optional< passwd::user > unprivileged_user,
119b0d29bc4SBrooks Davis const fs::path& control_directory,
120b0d29bc4SBrooks Davis const fs::path& work_directory)
121b0d29bc4SBrooks Davis {
122b0d29bc4SBrooks Davis logging::set_inmemory();
123b0d29bc4SBrooks Davis process::isolate_path(unprivileged_user, control_directory);
124b0d29bc4SBrooks Davis process::isolate_child(unprivileged_user, work_directory);
125b0d29bc4SBrooks Davis }
126b0d29bc4SBrooks Davis
127b0d29bc4SBrooks Davis
1286b13d60bSMuhammad Moinur Rahman /// Internal implementation for the exec_handle class.
129b0d29bc4SBrooks Davis struct utils::process::executor::exec_handle::impl : utils::noncopyable {
130b0d29bc4SBrooks Davis /// PID of the process being run.
131b0d29bc4SBrooks Davis int pid;
132b0d29bc4SBrooks Davis
133b0d29bc4SBrooks Davis /// Path to the subprocess-specific work directory.
134b0d29bc4SBrooks Davis fs::path control_directory;
135b0d29bc4SBrooks Davis
136b0d29bc4SBrooks Davis /// Path to the subprocess's stdout file.
137b0d29bc4SBrooks Davis const fs::path stdout_file;
138b0d29bc4SBrooks Davis
139b0d29bc4SBrooks Davis /// Path to the subprocess's stderr file.
140b0d29bc4SBrooks Davis const fs::path stderr_file;
141b0d29bc4SBrooks Davis
142b0d29bc4SBrooks Davis /// Start time.
143b0d29bc4SBrooks Davis datetime::timestamp start_time;
144b0d29bc4SBrooks Davis
145b0d29bc4SBrooks Davis /// User the subprocess is running as if different than the current one.
146b0d29bc4SBrooks Davis const optional< passwd::user > unprivileged_user;
147b0d29bc4SBrooks Davis
148b0d29bc4SBrooks Davis /// Timer to kill the subprocess on activation.
149b0d29bc4SBrooks Davis process::deadline_killer timer;
150b0d29bc4SBrooks Davis
151b0d29bc4SBrooks Davis /// Number of owners of the on-disk state.
152b0d29bc4SBrooks Davis executor::detail::refcnt_t state_owners;
153b0d29bc4SBrooks Davis
154b0d29bc4SBrooks Davis /// Constructor.
155b0d29bc4SBrooks Davis ///
156b0d29bc4SBrooks Davis /// \param pid_ PID of the forked process.
157b0d29bc4SBrooks Davis /// \param control_directory_ Path to the subprocess-specific work
158b0d29bc4SBrooks Davis /// directory.
159b0d29bc4SBrooks Davis /// \param stdout_file_ Path to the subprocess's stdout file.
160b0d29bc4SBrooks Davis /// \param stderr_file_ Path to the subprocess's stderr file.
161b0d29bc4SBrooks Davis /// \param start_time_ Timestamp of when this object was constructed.
162b0d29bc4SBrooks Davis /// \param timeout Maximum amount of time the subprocess can run for.
163b0d29bc4SBrooks Davis /// \param unprivileged_user_ User the subprocess is running as if
164b0d29bc4SBrooks Davis /// different than the current one.
165b0d29bc4SBrooks Davis /// \param [in,out] state_owners_ Number of owners of the on-disk state.
166b0d29bc4SBrooks Davis /// For first-time processes, this should be a new counter set to 0;
167b0d29bc4SBrooks Davis /// for followup processes, this should point to the same counter used
168b0d29bc4SBrooks Davis /// by the preceding process.
implutils::process::executor::exec_handle::impl169b0d29bc4SBrooks Davis impl(const int pid_,
170b0d29bc4SBrooks Davis const fs::path& control_directory_,
171b0d29bc4SBrooks Davis const fs::path& stdout_file_,
172b0d29bc4SBrooks Davis const fs::path& stderr_file_,
173b0d29bc4SBrooks Davis const datetime::timestamp& start_time_,
174b0d29bc4SBrooks Davis const datetime::delta& timeout,
175b0d29bc4SBrooks Davis const optional< passwd::user > unprivileged_user_,
176b0d29bc4SBrooks Davis executor::detail::refcnt_t state_owners_) :
177b0d29bc4SBrooks Davis pid(pid_),
178b0d29bc4SBrooks Davis control_directory(control_directory_),
179b0d29bc4SBrooks Davis stdout_file(stdout_file_),
180b0d29bc4SBrooks Davis stderr_file(stderr_file_),
181b0d29bc4SBrooks Davis start_time(start_time_),
182b0d29bc4SBrooks Davis unprivileged_user(unprivileged_user_),
183b0d29bc4SBrooks Davis timer(timeout, pid_),
184b0d29bc4SBrooks Davis state_owners(state_owners_)
185b0d29bc4SBrooks Davis {
186b0d29bc4SBrooks Davis (*state_owners)++;
187b0d29bc4SBrooks Davis POST(*state_owners > 0);
188b0d29bc4SBrooks Davis }
189b0d29bc4SBrooks Davis };
190b0d29bc4SBrooks Davis
191b0d29bc4SBrooks Davis
192b0d29bc4SBrooks Davis /// Constructor.
193b0d29bc4SBrooks Davis ///
194b0d29bc4SBrooks Davis /// \param pimpl Constructed internal implementation.
exec_handle(std::shared_ptr<impl> pimpl)195b0d29bc4SBrooks Davis executor::exec_handle::exec_handle(std::shared_ptr< impl > pimpl) :
196b0d29bc4SBrooks Davis _pimpl(pimpl)
197b0d29bc4SBrooks Davis {
198b0d29bc4SBrooks Davis }
199b0d29bc4SBrooks Davis
200b0d29bc4SBrooks Davis
201b0d29bc4SBrooks Davis /// Destructor.
~exec_handle(void)202b0d29bc4SBrooks Davis executor::exec_handle::~exec_handle(void)
203b0d29bc4SBrooks Davis {
204b0d29bc4SBrooks Davis }
205b0d29bc4SBrooks Davis
206b0d29bc4SBrooks Davis
207b0d29bc4SBrooks Davis /// Returns the PID of the process being run.
208b0d29bc4SBrooks Davis ///
209b0d29bc4SBrooks Davis /// \return A PID.
210b0d29bc4SBrooks Davis int
pid(void) const211b0d29bc4SBrooks Davis executor::exec_handle::pid(void) const
212b0d29bc4SBrooks Davis {
213b0d29bc4SBrooks Davis return _pimpl->pid;
214b0d29bc4SBrooks Davis }
215b0d29bc4SBrooks Davis
216b0d29bc4SBrooks Davis
217b0d29bc4SBrooks Davis /// Returns the path to the subprocess-specific control directory.
218b0d29bc4SBrooks Davis ///
219b0d29bc4SBrooks Davis /// This is where the executor may store control files.
220b0d29bc4SBrooks Davis ///
221b0d29bc4SBrooks Davis /// \return The path to a directory that exists until cleanup() is called.
222b0d29bc4SBrooks Davis fs::path
control_directory(void) const223b0d29bc4SBrooks Davis executor::exec_handle::control_directory(void) const
224b0d29bc4SBrooks Davis {
225b0d29bc4SBrooks Davis return _pimpl->control_directory;
226b0d29bc4SBrooks Davis }
227b0d29bc4SBrooks Davis
228b0d29bc4SBrooks Davis
229b0d29bc4SBrooks Davis /// Returns the path to the subprocess-specific work directory.
230b0d29bc4SBrooks Davis ///
231b0d29bc4SBrooks Davis /// This is guaranteed to be clear of files created by the executor.
232b0d29bc4SBrooks Davis ///
233b0d29bc4SBrooks Davis /// \return The path to a directory that exists until cleanup() is called.
234b0d29bc4SBrooks Davis fs::path
work_directory(void) const235b0d29bc4SBrooks Davis executor::exec_handle::work_directory(void) const
236b0d29bc4SBrooks Davis {
237b0d29bc4SBrooks Davis return _pimpl->control_directory / detail::work_subdir;
238b0d29bc4SBrooks Davis }
239b0d29bc4SBrooks Davis
240b0d29bc4SBrooks Davis
241b0d29bc4SBrooks Davis /// Returns the path to the subprocess's stdout file.
242b0d29bc4SBrooks Davis ///
243b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called.
244b0d29bc4SBrooks Davis const fs::path&
stdout_file(void) const245b0d29bc4SBrooks Davis executor::exec_handle::stdout_file(void) const
246b0d29bc4SBrooks Davis {
247b0d29bc4SBrooks Davis return _pimpl->stdout_file;
248b0d29bc4SBrooks Davis }
249b0d29bc4SBrooks Davis
250b0d29bc4SBrooks Davis
251b0d29bc4SBrooks Davis /// Returns the path to the subprocess's stderr file.
252b0d29bc4SBrooks Davis ///
253b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called.
254b0d29bc4SBrooks Davis const fs::path&
stderr_file(void) const255b0d29bc4SBrooks Davis executor::exec_handle::stderr_file(void) const
256b0d29bc4SBrooks Davis {
257b0d29bc4SBrooks Davis return _pimpl->stderr_file;
258b0d29bc4SBrooks Davis }
259b0d29bc4SBrooks Davis
260b0d29bc4SBrooks Davis
261b0d29bc4SBrooks Davis /// Internal implementation for the exit_handle class.
262b0d29bc4SBrooks Davis struct utils::process::executor::exit_handle::impl : utils::noncopyable {
263b0d29bc4SBrooks Davis /// Original PID of the terminated subprocess.
264b0d29bc4SBrooks Davis ///
265b0d29bc4SBrooks Davis /// Note that this PID is no longer valid and cannot be used on system
266b0d29bc4SBrooks Davis /// tables!
267b0d29bc4SBrooks Davis const int original_pid;
268b0d29bc4SBrooks Davis
269b0d29bc4SBrooks Davis /// Termination status of the subprocess, or none if it timed out.
270b0d29bc4SBrooks Davis const optional< process::status > status;
271b0d29bc4SBrooks Davis
272b0d29bc4SBrooks Davis /// The user the process ran as, if different than the current one.
273b0d29bc4SBrooks Davis const optional< passwd::user > unprivileged_user;
274b0d29bc4SBrooks Davis
275b0d29bc4SBrooks Davis /// Timestamp of when the subprocess was spawned.
276b0d29bc4SBrooks Davis const datetime::timestamp start_time;
277b0d29bc4SBrooks Davis
278b0d29bc4SBrooks Davis /// Timestamp of when wait() or wait_any() returned this object.
279b0d29bc4SBrooks Davis const datetime::timestamp end_time;
280b0d29bc4SBrooks Davis
281b0d29bc4SBrooks Davis /// Path to the subprocess-specific work directory.
282b0d29bc4SBrooks Davis const fs::path control_directory;
283b0d29bc4SBrooks Davis
284b0d29bc4SBrooks Davis /// Path to the subprocess's stdout file.
285b0d29bc4SBrooks Davis const fs::path stdout_file;
286b0d29bc4SBrooks Davis
287b0d29bc4SBrooks Davis /// Path to the subprocess's stderr file.
288b0d29bc4SBrooks Davis const fs::path stderr_file;
289b0d29bc4SBrooks Davis
290b0d29bc4SBrooks Davis /// Number of owners of the on-disk state.
291b0d29bc4SBrooks Davis ///
292b0d29bc4SBrooks Davis /// This will be 1 if this exit_handle is the last holder of the on-disk
293b0d29bc4SBrooks Davis /// state, in which case cleanup() invocations will wipe the disk state.
294b0d29bc4SBrooks Davis /// For all other cases, this will hold a higher value.
295b0d29bc4SBrooks Davis detail::refcnt_t state_owners;
296b0d29bc4SBrooks Davis
297b0d29bc4SBrooks Davis /// Mutable pointer to the corresponding executor state.
298b0d29bc4SBrooks Davis ///
299b0d29bc4SBrooks Davis /// This object references a member of the executor_handle that yielded this
300b0d29bc4SBrooks Davis /// exit_handle instance. We need this direct access to clean up after
301b0d29bc4SBrooks Davis /// ourselves when the handle is destroyed.
302b0d29bc4SBrooks Davis exec_handles_map& all_exec_handles;
303b0d29bc4SBrooks Davis
304b0d29bc4SBrooks Davis /// Whether the subprocess state has been cleaned yet or not.
305b0d29bc4SBrooks Davis ///
306b0d29bc4SBrooks Davis /// Used to keep track of explicit calls to the public cleanup().
307b0d29bc4SBrooks Davis bool cleaned;
308b0d29bc4SBrooks Davis
309b0d29bc4SBrooks Davis /// Constructor.
310b0d29bc4SBrooks Davis ///
311b0d29bc4SBrooks Davis /// \param original_pid_ Original PID of the terminated subprocess.
312b0d29bc4SBrooks Davis /// \param status_ Termination status of the subprocess, or none if
313b0d29bc4SBrooks Davis /// timed out.
314b0d29bc4SBrooks Davis /// \param unprivileged_user_ The user the process ran as, if different than
315b0d29bc4SBrooks Davis /// the current one.
316b0d29bc4SBrooks Davis /// \param start_time_ Timestamp of when the subprocess was spawned.
317b0d29bc4SBrooks Davis /// \param end_time_ Timestamp of when wait() or wait_any() returned this
318b0d29bc4SBrooks Davis /// object.
319b0d29bc4SBrooks Davis /// \param control_directory_ Path to the subprocess-specific work
320b0d29bc4SBrooks Davis /// directory.
321b0d29bc4SBrooks Davis /// \param stdout_file_ Path to the subprocess's stdout file.
322b0d29bc4SBrooks Davis /// \param stderr_file_ Path to the subprocess's stderr file.
323b0d29bc4SBrooks Davis /// \param [in,out] state_owners_ Number of owners of the on-disk state.
324b0d29bc4SBrooks Davis /// \param [in,out] all_exec_handles_ Global object keeping track of all
325b0d29bc4SBrooks Davis /// active executions for an executor. This is a pointer to a member of
326b0d29bc4SBrooks Davis /// the executor_handle object.
implutils::process::executor::exit_handle::impl327b0d29bc4SBrooks Davis impl(const int original_pid_,
328b0d29bc4SBrooks Davis const optional< process::status > status_,
329b0d29bc4SBrooks Davis const optional< passwd::user > unprivileged_user_,
330b0d29bc4SBrooks Davis const datetime::timestamp& start_time_,
331b0d29bc4SBrooks Davis const datetime::timestamp& end_time_,
332b0d29bc4SBrooks Davis const fs::path& control_directory_,
333b0d29bc4SBrooks Davis const fs::path& stdout_file_,
334b0d29bc4SBrooks Davis const fs::path& stderr_file_,
335b0d29bc4SBrooks Davis detail::refcnt_t state_owners_,
336b0d29bc4SBrooks Davis exec_handles_map& all_exec_handles_) :
337b0d29bc4SBrooks Davis original_pid(original_pid_), status(status_),
338b0d29bc4SBrooks Davis unprivileged_user(unprivileged_user_),
339b0d29bc4SBrooks Davis start_time(start_time_), end_time(end_time_),
340b0d29bc4SBrooks Davis control_directory(control_directory_),
341b0d29bc4SBrooks Davis stdout_file(stdout_file_), stderr_file(stderr_file_),
342b0d29bc4SBrooks Davis state_owners(state_owners_),
343b0d29bc4SBrooks Davis all_exec_handles(all_exec_handles_), cleaned(false)
344b0d29bc4SBrooks Davis {
345b0d29bc4SBrooks Davis }
346b0d29bc4SBrooks Davis
347b0d29bc4SBrooks Davis /// Destructor.
~implutils::process::executor::exit_handle::impl348b0d29bc4SBrooks Davis ~impl(void)
349b0d29bc4SBrooks Davis {
350b0d29bc4SBrooks Davis if (!cleaned) {
351b0d29bc4SBrooks Davis LW(F("Implicitly cleaning up exit_handle for exec_handle %s; "
352b0d29bc4SBrooks Davis "ignoring errors!") % original_pid);
353b0d29bc4SBrooks Davis try {
354b0d29bc4SBrooks Davis cleanup();
355b0d29bc4SBrooks Davis } catch (const std::runtime_error& error) {
356b0d29bc4SBrooks Davis LE(F("Subprocess cleanup failed: %s") % error.what());
357b0d29bc4SBrooks Davis }
358b0d29bc4SBrooks Davis }
359b0d29bc4SBrooks Davis }
360b0d29bc4SBrooks Davis
361b0d29bc4SBrooks Davis /// Cleans up the subprocess on-disk state.
362b0d29bc4SBrooks Davis ///
363b0d29bc4SBrooks Davis /// \throw engine::error If the cleanup fails, especially due to the
364b0d29bc4SBrooks Davis /// inability to remove the work directory.
365b0d29bc4SBrooks Davis void
cleanuputils::process::executor::exit_handle::impl366b0d29bc4SBrooks Davis cleanup(void)
367b0d29bc4SBrooks Davis {
368b0d29bc4SBrooks Davis PRE(*state_owners > 0);
369b0d29bc4SBrooks Davis if (*state_owners == 1) {
370b0d29bc4SBrooks Davis LI(F("Cleaning up exit_handle for exec_handle %s") % original_pid);
371b0d29bc4SBrooks Davis fs::rm_r(control_directory);
372b0d29bc4SBrooks Davis } else {
373b0d29bc4SBrooks Davis LI(F("Not cleaning up exit_handle for exec_handle %s; "
374b0d29bc4SBrooks Davis "%s owners left") % original_pid % (*state_owners - 1));
375b0d29bc4SBrooks Davis }
376b0d29bc4SBrooks Davis // We must decrease our reference only after we have successfully
377b0d29bc4SBrooks Davis // cleaned up the control directory. Otherwise, the rm_r call would
378b0d29bc4SBrooks Davis // throw an exception, which would in turn invoke the implicit cleanup
379b0d29bc4SBrooks Davis // from the destructor, which would make us crash due to an invalid
380b0d29bc4SBrooks Davis // reference count.
381b0d29bc4SBrooks Davis (*state_owners)--;
382b0d29bc4SBrooks Davis // Marking this object as clean here, even if we did not do actually the
383b0d29bc4SBrooks Davis // cleaning above, is fine (albeit a bit confusing). Note that "another
384b0d29bc4SBrooks Davis // owner" refers to a handle for a different PID, so that handle will be
385b0d29bc4SBrooks Davis // the one issuing the cleanup.
386b0d29bc4SBrooks Davis all_exec_handles.erase(original_pid);
387b0d29bc4SBrooks Davis cleaned = true;
388b0d29bc4SBrooks Davis }
389b0d29bc4SBrooks Davis };
390b0d29bc4SBrooks Davis
391b0d29bc4SBrooks Davis
392b0d29bc4SBrooks Davis /// Constructor.
393b0d29bc4SBrooks Davis ///
394b0d29bc4SBrooks Davis /// \param pimpl Constructed internal implementation.
exit_handle(std::shared_ptr<impl> pimpl)395b0d29bc4SBrooks Davis executor::exit_handle::exit_handle(std::shared_ptr< impl > pimpl) :
396b0d29bc4SBrooks Davis _pimpl(pimpl)
397b0d29bc4SBrooks Davis {
398b0d29bc4SBrooks Davis }
399b0d29bc4SBrooks Davis
400b0d29bc4SBrooks Davis
401b0d29bc4SBrooks Davis /// Destructor.
~exit_handle(void)402b0d29bc4SBrooks Davis executor::exit_handle::~exit_handle(void)
403b0d29bc4SBrooks Davis {
404b0d29bc4SBrooks Davis }
405b0d29bc4SBrooks Davis
406b0d29bc4SBrooks Davis
407b0d29bc4SBrooks Davis /// Cleans up the subprocess status.
408b0d29bc4SBrooks Davis ///
409b0d29bc4SBrooks Davis /// This function should be called explicitly as it provides the means to
410b0d29bc4SBrooks Davis /// control any exceptions raised during cleanup. Do not rely on the destructor
411b0d29bc4SBrooks Davis /// to clean things up.
412b0d29bc4SBrooks Davis ///
413b0d29bc4SBrooks Davis /// \throw engine::error If the cleanup fails, especially due to the inability
414b0d29bc4SBrooks Davis /// to remove the work directory.
415b0d29bc4SBrooks Davis void
cleanup(void)416b0d29bc4SBrooks Davis executor::exit_handle::cleanup(void)
417b0d29bc4SBrooks Davis {
418b0d29bc4SBrooks Davis PRE(!_pimpl->cleaned);
419b0d29bc4SBrooks Davis _pimpl->cleanup();
420b0d29bc4SBrooks Davis POST(_pimpl->cleaned);
421b0d29bc4SBrooks Davis }
422b0d29bc4SBrooks Davis
423b0d29bc4SBrooks Davis
424b0d29bc4SBrooks Davis /// Gets the current number of owners of the on-disk data.
425b0d29bc4SBrooks Davis ///
426b0d29bc4SBrooks Davis /// \return A shared reference counter. Even though this function is marked as
427b0d29bc4SBrooks Davis /// const, the return value is intentionally mutable because we need to update
428b0d29bc4SBrooks Davis /// reference counts from different but related processes. This is why this
429b0d29bc4SBrooks Davis /// method is not public.
430b0d29bc4SBrooks Davis std::shared_ptr< std::size_t >
state_owners(void) const431b0d29bc4SBrooks Davis executor::exit_handle::state_owners(void) const
432b0d29bc4SBrooks Davis {
433b0d29bc4SBrooks Davis return _pimpl->state_owners;
434b0d29bc4SBrooks Davis }
435b0d29bc4SBrooks Davis
436b0d29bc4SBrooks Davis
437b0d29bc4SBrooks Davis /// Returns the original PID corresponding to the terminated subprocess.
438b0d29bc4SBrooks Davis ///
439b0d29bc4SBrooks Davis /// \return An exec_handle.
440b0d29bc4SBrooks Davis int
original_pid(void) const441b0d29bc4SBrooks Davis executor::exit_handle::original_pid(void) const
442b0d29bc4SBrooks Davis {
443b0d29bc4SBrooks Davis return _pimpl->original_pid;
444b0d29bc4SBrooks Davis }
445b0d29bc4SBrooks Davis
446b0d29bc4SBrooks Davis
447b0d29bc4SBrooks Davis /// Returns the process termination status of the subprocess.
448b0d29bc4SBrooks Davis ///
449b0d29bc4SBrooks Davis /// \return A process termination status, or none if the subprocess timed out.
450b0d29bc4SBrooks Davis const optional< process::status >&
status(void) const451b0d29bc4SBrooks Davis executor::exit_handle::status(void) const
452b0d29bc4SBrooks Davis {
453b0d29bc4SBrooks Davis return _pimpl->status;
454b0d29bc4SBrooks Davis }
455b0d29bc4SBrooks Davis
456b0d29bc4SBrooks Davis
457b0d29bc4SBrooks Davis /// Returns the user the process ran as if different than the current one.
458b0d29bc4SBrooks Davis ///
459b0d29bc4SBrooks Davis /// \return None if the credentials of the process were the same as the current
460b0d29bc4SBrooks Davis /// one, or else a user.
461b0d29bc4SBrooks Davis const optional< passwd::user >&
unprivileged_user(void) const462b0d29bc4SBrooks Davis executor::exit_handle::unprivileged_user(void) const
463b0d29bc4SBrooks Davis {
464b0d29bc4SBrooks Davis return _pimpl->unprivileged_user;
465b0d29bc4SBrooks Davis }
466b0d29bc4SBrooks Davis
467b0d29bc4SBrooks Davis
468b0d29bc4SBrooks Davis /// Returns the timestamp of when the subprocess was spawned.
469b0d29bc4SBrooks Davis ///
470b0d29bc4SBrooks Davis /// \return A timestamp.
471b0d29bc4SBrooks Davis const datetime::timestamp&
start_time(void) const472b0d29bc4SBrooks Davis executor::exit_handle::start_time(void) const
473b0d29bc4SBrooks Davis {
474b0d29bc4SBrooks Davis return _pimpl->start_time;
475b0d29bc4SBrooks Davis }
476b0d29bc4SBrooks Davis
477b0d29bc4SBrooks Davis
478b0d29bc4SBrooks Davis /// Returns the timestamp of when wait() or wait_any() returned this object.
479b0d29bc4SBrooks Davis ///
480b0d29bc4SBrooks Davis /// \return A timestamp.
481b0d29bc4SBrooks Davis const datetime::timestamp&
end_time(void) const482b0d29bc4SBrooks Davis executor::exit_handle::end_time(void) const
483b0d29bc4SBrooks Davis {
484b0d29bc4SBrooks Davis return _pimpl->end_time;
485b0d29bc4SBrooks Davis }
486b0d29bc4SBrooks Davis
487b0d29bc4SBrooks Davis
488b0d29bc4SBrooks Davis /// Returns the path to the subprocess-specific control directory.
489b0d29bc4SBrooks Davis ///
490b0d29bc4SBrooks Davis /// This is where the executor may store control files.
491b0d29bc4SBrooks Davis ///
492b0d29bc4SBrooks Davis /// \return The path to a directory that exists until cleanup() is called.
493b0d29bc4SBrooks Davis fs::path
control_directory(void) const494b0d29bc4SBrooks Davis executor::exit_handle::control_directory(void) const
495b0d29bc4SBrooks Davis {
496b0d29bc4SBrooks Davis return _pimpl->control_directory;
497b0d29bc4SBrooks Davis }
498b0d29bc4SBrooks Davis
499b0d29bc4SBrooks Davis
500b0d29bc4SBrooks Davis /// Returns the path to the subprocess-specific work directory.
501b0d29bc4SBrooks Davis ///
502b0d29bc4SBrooks Davis /// This is guaranteed to be clear of files created by the executor.
503b0d29bc4SBrooks Davis ///
504b0d29bc4SBrooks Davis /// \return The path to a directory that exists until cleanup() is called.
505b0d29bc4SBrooks Davis fs::path
work_directory(void) const506b0d29bc4SBrooks Davis executor::exit_handle::work_directory(void) const
507b0d29bc4SBrooks Davis {
508b0d29bc4SBrooks Davis return _pimpl->control_directory / detail::work_subdir;
509b0d29bc4SBrooks Davis }
510b0d29bc4SBrooks Davis
511b0d29bc4SBrooks Davis
512b0d29bc4SBrooks Davis /// Returns the path to the subprocess's stdout file.
513b0d29bc4SBrooks Davis ///
514b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called.
515b0d29bc4SBrooks Davis const fs::path&
stdout_file(void) const516b0d29bc4SBrooks Davis executor::exit_handle::stdout_file(void) const
517b0d29bc4SBrooks Davis {
518b0d29bc4SBrooks Davis return _pimpl->stdout_file;
519b0d29bc4SBrooks Davis }
520b0d29bc4SBrooks Davis
521b0d29bc4SBrooks Davis
522b0d29bc4SBrooks Davis /// Returns the path to the subprocess's stderr file.
523b0d29bc4SBrooks Davis ///
524b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called.
525b0d29bc4SBrooks Davis const fs::path&
stderr_file(void) const526b0d29bc4SBrooks Davis executor::exit_handle::stderr_file(void) const
527b0d29bc4SBrooks Davis {
528b0d29bc4SBrooks Davis return _pimpl->stderr_file;
529b0d29bc4SBrooks Davis }
530b0d29bc4SBrooks Davis
531b0d29bc4SBrooks Davis
532b0d29bc4SBrooks Davis /// Internal implementation for the executor_handle.
533b0d29bc4SBrooks Davis ///
534b0d29bc4SBrooks Davis /// Because the executor is a singleton, these essentially is a container for
535b0d29bc4SBrooks Davis /// global variables.
536b0d29bc4SBrooks Davis struct utils::process::executor::executor_handle::impl : utils::noncopyable {
537b0d29bc4SBrooks Davis /// Numeric counter of executed subprocesses.
538b0d29bc4SBrooks Davis ///
539b0d29bc4SBrooks Davis /// This is used to generate a unique identifier for each subprocess as an
540b0d29bc4SBrooks Davis /// easy mechanism to discern their unique work directories.
541b0d29bc4SBrooks Davis size_t last_subprocess;
542b0d29bc4SBrooks Davis
543b0d29bc4SBrooks Davis /// Interrupts handler.
544b392a90bSJohn Baldwin std::unique_ptr< signals::interrupts_handler > interrupts_handler;
545b0d29bc4SBrooks Davis
546b0d29bc4SBrooks Davis /// Root work directory for all executed subprocesses.
547b392a90bSJohn Baldwin std::unique_ptr< fs::auto_directory > root_work_directory;
548b0d29bc4SBrooks Davis
549b0d29bc4SBrooks Davis /// Mapping of PIDs to the data required at run time.
550b0d29bc4SBrooks Davis exec_handles_map all_exec_handles;
551b0d29bc4SBrooks Davis
5526b13d60bSMuhammad Moinur Rahman /// Former members of all_exec_handles removed due to PID reuse.
5536b13d60bSMuhammad Moinur Rahman std::forward_list<exec_handle> stale_exec_handles;
5546b13d60bSMuhammad Moinur Rahman
555b0d29bc4SBrooks Davis /// Whether the executor state has been cleaned yet or not.
556b0d29bc4SBrooks Davis ///
557b0d29bc4SBrooks Davis /// Used to keep track of explicit calls to the public cleanup().
558b0d29bc4SBrooks Davis bool cleaned;
559b0d29bc4SBrooks Davis
560b0d29bc4SBrooks Davis /// Constructor.
implutils::process::executor::executor_handle::impl561b0d29bc4SBrooks Davis impl(void) :
562b0d29bc4SBrooks Davis last_subprocess(0),
563b0d29bc4SBrooks Davis interrupts_handler(new signals::interrupts_handler()),
564b0d29bc4SBrooks Davis root_work_directory(new fs::auto_directory(
565b0d29bc4SBrooks Davis fs::auto_directory::mkdtemp_public(work_directory_template))),
5666b13d60bSMuhammad Moinur Rahman all_exec_handles(),
5676b13d60bSMuhammad Moinur Rahman stale_exec_handles(),
568b0d29bc4SBrooks Davis cleaned(false)
569b0d29bc4SBrooks Davis {
570b0d29bc4SBrooks Davis }
571b0d29bc4SBrooks Davis
572b0d29bc4SBrooks Davis /// Destructor.
~implutils::process::executor::executor_handle::impl573b0d29bc4SBrooks Davis ~impl(void)
574b0d29bc4SBrooks Davis {
575b0d29bc4SBrooks Davis if (!cleaned) {
576b0d29bc4SBrooks Davis LW("Implicitly cleaning up executor; ignoring errors!");
577b0d29bc4SBrooks Davis try {
578b0d29bc4SBrooks Davis cleanup();
579b0d29bc4SBrooks Davis cleaned = true;
580b0d29bc4SBrooks Davis } catch (const std::runtime_error& error) {
581b0d29bc4SBrooks Davis LE(F("Executor global cleanup failed: %s") % error.what());
582b0d29bc4SBrooks Davis }
583b0d29bc4SBrooks Davis }
584b0d29bc4SBrooks Davis }
585b0d29bc4SBrooks Davis
586b0d29bc4SBrooks Davis /// Cleans up the executor state.
587b0d29bc4SBrooks Davis void
cleanuputils::process::executor::executor_handle::impl588b0d29bc4SBrooks Davis cleanup(void)
589b0d29bc4SBrooks Davis {
590b0d29bc4SBrooks Davis PRE(!cleaned);
591b0d29bc4SBrooks Davis
592b0d29bc4SBrooks Davis for (exec_handles_map::const_iterator iter = all_exec_handles.begin();
593b0d29bc4SBrooks Davis iter != all_exec_handles.end(); ++iter) {
594b0d29bc4SBrooks Davis const int& pid = (*iter).first;
595b0d29bc4SBrooks Davis const exec_handle& data = (*iter).second;
596b0d29bc4SBrooks Davis
597b0d29bc4SBrooks Davis process::terminate_group(pid);
598b0d29bc4SBrooks Davis int status;
599b0d29bc4SBrooks Davis if (::waitpid(pid, &status, 0) == -1) {
600b0d29bc4SBrooks Davis // Should not happen.
601b0d29bc4SBrooks Davis LW(F("Failed to wait for PID %s") % pid);
602b0d29bc4SBrooks Davis }
603b0d29bc4SBrooks Davis
604b0d29bc4SBrooks Davis try {
605b0d29bc4SBrooks Davis fs::rm_r(data.control_directory());
606b0d29bc4SBrooks Davis } catch (const fs::error& e) {
607b0d29bc4SBrooks Davis LE(F("Failed to clean up subprocess work directory %s: %s") %
608b0d29bc4SBrooks Davis data.control_directory() % e.what());
609b0d29bc4SBrooks Davis }
610b0d29bc4SBrooks Davis }
611b0d29bc4SBrooks Davis all_exec_handles.clear();
612b0d29bc4SBrooks Davis
6136b13d60bSMuhammad Moinur Rahman for (auto iter : stale_exec_handles) {
6146b13d60bSMuhammad Moinur Rahman // The process already exited, so no need to kill and wait.
6156b13d60bSMuhammad Moinur Rahman try {
6166b13d60bSMuhammad Moinur Rahman fs::rm_r(iter.control_directory());
6176b13d60bSMuhammad Moinur Rahman } catch (const fs::error& e) {
6186b13d60bSMuhammad Moinur Rahman LE(F("Failed to clean up stale subprocess work directory "
6196b13d60bSMuhammad Moinur Rahman "%s: %s") % iter.control_directory() % e.what());
6206b13d60bSMuhammad Moinur Rahman }
6216b13d60bSMuhammad Moinur Rahman }
6226b13d60bSMuhammad Moinur Rahman stale_exec_handles.clear();
6236b13d60bSMuhammad Moinur Rahman
624b0d29bc4SBrooks Davis try {
625b0d29bc4SBrooks Davis // The following only causes the work directory to be deleted, not
626b0d29bc4SBrooks Davis // any of its contents, so we expect this to always succeed. This
627b0d29bc4SBrooks Davis // *should* be sufficient because, in the loop above, we have
628b0d29bc4SBrooks Davis // individually wiped the subdirectories of any still-unclean
629b0d29bc4SBrooks Davis // subprocesses.
630b0d29bc4SBrooks Davis root_work_directory->cleanup();
631b0d29bc4SBrooks Davis } catch (const fs::error& e) {
6326b13d60bSMuhammad Moinur Rahman LE(F("Failed to clean up executor work directory %s: %s; "
6336b13d60bSMuhammad Moinur Rahman "this could be an internal error or a buggy test") %
6346b13d60bSMuhammad Moinur Rahman root_work_directory->directory() % e.what());
635b0d29bc4SBrooks Davis }
636*d61c8bcaSJohn Baldwin root_work_directory.reset();
637b0d29bc4SBrooks Davis
638b0d29bc4SBrooks Davis interrupts_handler->unprogram();
639*d61c8bcaSJohn Baldwin interrupts_handler.reset();
640b0d29bc4SBrooks Davis }
641b0d29bc4SBrooks Davis
642b0d29bc4SBrooks Davis /// Common code to run after any of the wait calls.
643b0d29bc4SBrooks Davis ///
644b0d29bc4SBrooks Davis /// \param original_pid The PID of the terminated subprocess.
645b0d29bc4SBrooks Davis /// \param status The exit status of the terminated subprocess.
646b0d29bc4SBrooks Davis ///
647b0d29bc4SBrooks Davis /// \return A pointer to an object describing the waited-for subprocess.
648b0d29bc4SBrooks Davis executor::exit_handle
post_waitutils::process::executor::executor_handle::impl649b0d29bc4SBrooks Davis post_wait(const int original_pid, const process::status& status)
650b0d29bc4SBrooks Davis {
651b0d29bc4SBrooks Davis PRE(original_pid == status.dead_pid());
652b0d29bc4SBrooks Davis LI(F("Waited for subprocess with exec_handle %s") % original_pid);
653b0d29bc4SBrooks Davis
654b0d29bc4SBrooks Davis process::terminate_group(status.dead_pid());
655b0d29bc4SBrooks Davis
656b0d29bc4SBrooks Davis const exec_handles_map::iterator iter = all_exec_handles.find(
657b0d29bc4SBrooks Davis original_pid);
658b0d29bc4SBrooks Davis exec_handle& data = (*iter).second;
659b0d29bc4SBrooks Davis data._pimpl->timer.unprogram();
660b0d29bc4SBrooks Davis
661b0d29bc4SBrooks Davis // It is tempting to assert here (and old code did) that, if the timer
662b0d29bc4SBrooks Davis // has fired, the process has been forcibly killed by us. This is not
663b0d29bc4SBrooks Davis // always the case though: for short-lived processes and with very short
664b0d29bc4SBrooks Davis // timeouts (think 1ms), it is possible for scheduling decisions to
665b0d29bc4SBrooks Davis // allow the subprocess to finish while at the same time cause the timer
666b0d29bc4SBrooks Davis // to fire. So we do not assert this any longer and just rely on the
667b0d29bc4SBrooks Davis // timer expiration to check if the process timed out or not. If the
668b0d29bc4SBrooks Davis // process did finish but the timer expired... oh well, we do not detect
669b0d29bc4SBrooks Davis // this correctly but we don't care because this should not really
670b0d29bc4SBrooks Davis // happen.
671b0d29bc4SBrooks Davis
672b0d29bc4SBrooks Davis if (!fs::exists(data.stdout_file())) {
673b0d29bc4SBrooks Davis std::ofstream new_stdout(data.stdout_file().c_str());
674b0d29bc4SBrooks Davis }
675b0d29bc4SBrooks Davis if (!fs::exists(data.stderr_file())) {
676b0d29bc4SBrooks Davis std::ofstream new_stderr(data.stderr_file().c_str());
677b0d29bc4SBrooks Davis }
678b0d29bc4SBrooks Davis
679b0d29bc4SBrooks Davis return exit_handle(std::shared_ptr< exit_handle::impl >(
680b0d29bc4SBrooks Davis new exit_handle::impl(
681b0d29bc4SBrooks Davis data.pid(),
682b0d29bc4SBrooks Davis data._pimpl->timer.fired() ?
683b0d29bc4SBrooks Davis none : utils::make_optional(status),
684b0d29bc4SBrooks Davis data._pimpl->unprivileged_user,
685b0d29bc4SBrooks Davis data._pimpl->start_time, datetime::timestamp::now(),
686b0d29bc4SBrooks Davis data.control_directory(),
687b0d29bc4SBrooks Davis data.stdout_file(),
688b0d29bc4SBrooks Davis data.stderr_file(),
689b0d29bc4SBrooks Davis data._pimpl->state_owners,
690b0d29bc4SBrooks Davis all_exec_handles)));
691b0d29bc4SBrooks Davis }
692257e70f1SIgor Ostapenko
693257e70f1SIgor Ostapenko executor::exit_handle
reaputils::process::executor::executor_handle::impl694257e70f1SIgor Ostapenko reap(const pid_t original_pid)
695257e70f1SIgor Ostapenko {
696257e70f1SIgor Ostapenko const exec_handles_map::iterator iter = all_exec_handles.find(
697257e70f1SIgor Ostapenko original_pid);
698257e70f1SIgor Ostapenko exec_handle& data = (*iter).second;
699257e70f1SIgor Ostapenko data._pimpl->timer.unprogram();
700257e70f1SIgor Ostapenko
701257e70f1SIgor Ostapenko if (!fs::exists(data.stdout_file())) {
702257e70f1SIgor Ostapenko std::ofstream new_stdout(data.stdout_file().c_str());
703257e70f1SIgor Ostapenko }
704257e70f1SIgor Ostapenko if (!fs::exists(data.stderr_file())) {
705257e70f1SIgor Ostapenko std::ofstream new_stderr(data.stderr_file().c_str());
706257e70f1SIgor Ostapenko }
707257e70f1SIgor Ostapenko
708257e70f1SIgor Ostapenko return exit_handle(std::shared_ptr< exit_handle::impl >(
709257e70f1SIgor Ostapenko new exit_handle::impl(
710257e70f1SIgor Ostapenko data.pid(),
711257e70f1SIgor Ostapenko none,
712257e70f1SIgor Ostapenko data._pimpl->unprivileged_user,
713257e70f1SIgor Ostapenko data._pimpl->start_time, datetime::timestamp::now(),
714257e70f1SIgor Ostapenko data.control_directory(),
715257e70f1SIgor Ostapenko data.stdout_file(),
716257e70f1SIgor Ostapenko data.stderr_file(),
717257e70f1SIgor Ostapenko data._pimpl->state_owners,
718257e70f1SIgor Ostapenko all_exec_handles)));
719257e70f1SIgor Ostapenko }
720b0d29bc4SBrooks Davis };
721b0d29bc4SBrooks Davis
722b0d29bc4SBrooks Davis
723b0d29bc4SBrooks Davis /// Constructor.
executor_handle(void)724b0d29bc4SBrooks Davis executor::executor_handle::executor_handle(void) throw() : _pimpl(new impl())
725b0d29bc4SBrooks Davis {
726b0d29bc4SBrooks Davis }
727b0d29bc4SBrooks Davis
728b0d29bc4SBrooks Davis
729b0d29bc4SBrooks Davis /// Destructor.
~executor_handle(void)730b0d29bc4SBrooks Davis executor::executor_handle::~executor_handle(void)
731b0d29bc4SBrooks Davis {
732b0d29bc4SBrooks Davis }
733b0d29bc4SBrooks Davis
734b0d29bc4SBrooks Davis
735b0d29bc4SBrooks Davis /// Queries the path to the root of the work directory for all subprocesses.
736b0d29bc4SBrooks Davis ///
737b0d29bc4SBrooks Davis /// \return A path.
738b0d29bc4SBrooks Davis const fs::path&
root_work_directory(void) const739b0d29bc4SBrooks Davis executor::executor_handle::root_work_directory(void) const
740b0d29bc4SBrooks Davis {
741b0d29bc4SBrooks Davis return _pimpl->root_work_directory->directory();
742b0d29bc4SBrooks Davis }
743b0d29bc4SBrooks Davis
744b0d29bc4SBrooks Davis
745b0d29bc4SBrooks Davis /// Cleans up the executor state.
746b0d29bc4SBrooks Davis ///
747b0d29bc4SBrooks Davis /// This function should be called explicitly as it provides the means to
748b0d29bc4SBrooks Davis /// control any exceptions raised during cleanup. Do not rely on the destructor
749b0d29bc4SBrooks Davis /// to clean things up.
750b0d29bc4SBrooks Davis ///
751b0d29bc4SBrooks Davis /// \throw engine::error If there are problems cleaning up the executor.
752b0d29bc4SBrooks Davis void
cleanup(void)753b0d29bc4SBrooks Davis executor::executor_handle::cleanup(void)
754b0d29bc4SBrooks Davis {
755b0d29bc4SBrooks Davis PRE(!_pimpl->cleaned);
756b0d29bc4SBrooks Davis _pimpl->cleanup();
757b0d29bc4SBrooks Davis _pimpl->cleaned = true;
758b0d29bc4SBrooks Davis }
759b0d29bc4SBrooks Davis
760b0d29bc4SBrooks Davis
761b0d29bc4SBrooks Davis /// Initializes the executor.
762b0d29bc4SBrooks Davis ///
763b0d29bc4SBrooks Davis /// \pre This function can only be called if there is no other executor_handle
764b0d29bc4SBrooks Davis /// object alive.
765b0d29bc4SBrooks Davis ///
766b0d29bc4SBrooks Davis /// \return A handle to the operations of the executor.
767b0d29bc4SBrooks Davis executor::executor_handle
setup(void)768b0d29bc4SBrooks Davis executor::setup(void)
769b0d29bc4SBrooks Davis {
770b0d29bc4SBrooks Davis return executor_handle();
771b0d29bc4SBrooks Davis }
772b0d29bc4SBrooks Davis
773b0d29bc4SBrooks Davis
774b0d29bc4SBrooks Davis /// Pre-helper for the spawn() method.
775b0d29bc4SBrooks Davis ///
776b0d29bc4SBrooks Davis /// \return The created control directory for the subprocess.
777b0d29bc4SBrooks Davis fs::path
spawn_pre(void)778b0d29bc4SBrooks Davis executor::executor_handle::spawn_pre(void)
779b0d29bc4SBrooks Davis {
780b0d29bc4SBrooks Davis signals::check_interrupt();
781b0d29bc4SBrooks Davis
782b0d29bc4SBrooks Davis ++_pimpl->last_subprocess;
783b0d29bc4SBrooks Davis
784b0d29bc4SBrooks Davis const fs::path control_directory =
785b0d29bc4SBrooks Davis _pimpl->root_work_directory->directory() /
786b0d29bc4SBrooks Davis (F("%s") % _pimpl->last_subprocess);
787b0d29bc4SBrooks Davis fs::mkdir_p(control_directory / detail::work_subdir, 0755);
788b0d29bc4SBrooks Davis
789b0d29bc4SBrooks Davis return control_directory;
790b0d29bc4SBrooks Davis }
791b0d29bc4SBrooks Davis
792b0d29bc4SBrooks Davis
793b0d29bc4SBrooks Davis /// Post-helper for the spawn() method.
794b0d29bc4SBrooks Davis ///
795b0d29bc4SBrooks Davis /// \param control_directory Control directory as returned by spawn_pre().
796b0d29bc4SBrooks Davis /// \param stdout_file Path to the subprocess' stdout.
797b0d29bc4SBrooks Davis /// \param stderr_file Path to the subprocess' stderr.
798b0d29bc4SBrooks Davis /// \param timeout Maximum amount of time the subprocess can run for.
799b0d29bc4SBrooks Davis /// \param unprivileged_user If not none, user to switch to before execution.
800b0d29bc4SBrooks Davis /// \param child The process created by spawn().
801b0d29bc4SBrooks Davis ///
802b0d29bc4SBrooks Davis /// \return The execution handle of the started subprocess.
803b0d29bc4SBrooks Davis executor::exec_handle
spawn_post(const fs::path & control_directory,const fs::path & stdout_file,const fs::path & stderr_file,const datetime::delta & timeout,const optional<passwd::user> unprivileged_user,std::unique_ptr<process::child> child)804b0d29bc4SBrooks Davis executor::executor_handle::spawn_post(
805b0d29bc4SBrooks Davis const fs::path& control_directory,
806b0d29bc4SBrooks Davis const fs::path& stdout_file,
807b0d29bc4SBrooks Davis const fs::path& stderr_file,
808b0d29bc4SBrooks Davis const datetime::delta& timeout,
809b0d29bc4SBrooks Davis const optional< passwd::user > unprivileged_user,
810b392a90bSJohn Baldwin std::unique_ptr< process::child > child)
811b0d29bc4SBrooks Davis {
812b0d29bc4SBrooks Davis const exec_handle handle(std::shared_ptr< exec_handle::impl >(
813b0d29bc4SBrooks Davis new exec_handle::impl(
814b0d29bc4SBrooks Davis child->pid(),
815b0d29bc4SBrooks Davis control_directory,
816b0d29bc4SBrooks Davis stdout_file,
817b0d29bc4SBrooks Davis stderr_file,
818b0d29bc4SBrooks Davis datetime::timestamp::now(),
819b0d29bc4SBrooks Davis timeout,
820b0d29bc4SBrooks Davis unprivileged_user,
821b0d29bc4SBrooks Davis detail::refcnt_t(new detail::refcnt_t::element_type(0)))));
8226b13d60bSMuhammad Moinur Rahman const auto value = exec_handles_map::value_type(handle.pid(), handle);
8236b13d60bSMuhammad Moinur Rahman auto insert_pair = _pimpl->all_exec_handles.insert(value);
8246b13d60bSMuhammad Moinur Rahman if (!insert_pair.second) {
8256b13d60bSMuhammad Moinur Rahman LI(F("PID %s already in all_exec_handles") % handle.pid());
8266b13d60bSMuhammad Moinur Rahman _pimpl->stale_exec_handles.push_front(insert_pair.first->second);
8276b13d60bSMuhammad Moinur Rahman _pimpl->all_exec_handles.erase(insert_pair.first);
8286b13d60bSMuhammad Moinur Rahman insert_pair = _pimpl->all_exec_handles.insert(value);
8296b13d60bSMuhammad Moinur Rahman INV_MSG(insert_pair.second, F("PID %s still in all_exec_handles") %
8306b13d60bSMuhammad Moinur Rahman handle.pid());
8316b13d60bSMuhammad Moinur Rahman }
832b0d29bc4SBrooks Davis LI(F("Spawned subprocess with exec_handle %s") % handle.pid());
833b0d29bc4SBrooks Davis return handle;
834b0d29bc4SBrooks Davis }
835b0d29bc4SBrooks Davis
836b0d29bc4SBrooks Davis
837b0d29bc4SBrooks Davis /// Pre-helper for the spawn_followup() method.
838b0d29bc4SBrooks Davis void
spawn_followup_pre(void)839b0d29bc4SBrooks Davis executor::executor_handle::spawn_followup_pre(void)
840b0d29bc4SBrooks Davis {
841b0d29bc4SBrooks Davis signals::check_interrupt();
842b0d29bc4SBrooks Davis }
843b0d29bc4SBrooks Davis
844b0d29bc4SBrooks Davis
845b0d29bc4SBrooks Davis /// Post-helper for the spawn_followup() method.
846b0d29bc4SBrooks Davis ///
847b0d29bc4SBrooks Davis /// \param base Exit handle of the subprocess to use as context.
848b0d29bc4SBrooks Davis /// \param timeout Maximum amount of time the subprocess can run for.
849b0d29bc4SBrooks Davis /// \param child The process created by spawn_followup().
850b0d29bc4SBrooks Davis ///
851b0d29bc4SBrooks Davis /// \return The execution handle of the started subprocess.
852b0d29bc4SBrooks Davis executor::exec_handle
spawn_followup_post(const exit_handle & base,const datetime::delta & timeout,std::unique_ptr<process::child> child)853b0d29bc4SBrooks Davis executor::executor_handle::spawn_followup_post(
854b0d29bc4SBrooks Davis const exit_handle& base,
855b0d29bc4SBrooks Davis const datetime::delta& timeout,
856b392a90bSJohn Baldwin std::unique_ptr< process::child > child)
857b0d29bc4SBrooks Davis {
858b0d29bc4SBrooks Davis INV(*base.state_owners() > 0);
859b0d29bc4SBrooks Davis const exec_handle handle(std::shared_ptr< exec_handle::impl >(
860b0d29bc4SBrooks Davis new exec_handle::impl(
861b0d29bc4SBrooks Davis child->pid(),
862b0d29bc4SBrooks Davis base.control_directory(),
863b0d29bc4SBrooks Davis base.stdout_file(),
864b0d29bc4SBrooks Davis base.stderr_file(),
865b0d29bc4SBrooks Davis datetime::timestamp::now(),
866b0d29bc4SBrooks Davis timeout,
867b0d29bc4SBrooks Davis base.unprivileged_user(),
868b0d29bc4SBrooks Davis base.state_owners())));
8696b13d60bSMuhammad Moinur Rahman const auto value = exec_handles_map::value_type(handle.pid(), handle);
8706b13d60bSMuhammad Moinur Rahman auto insert_pair = _pimpl->all_exec_handles.insert(value);
8716b13d60bSMuhammad Moinur Rahman if (!insert_pair.second) {
8726b13d60bSMuhammad Moinur Rahman LI(F("PID %s already in all_exec_handles") % handle.pid());
8736b13d60bSMuhammad Moinur Rahman _pimpl->stale_exec_handles.push_front(insert_pair.first->second);
8746b13d60bSMuhammad Moinur Rahman _pimpl->all_exec_handles.erase(insert_pair.first);
8756b13d60bSMuhammad Moinur Rahman insert_pair = _pimpl->all_exec_handles.insert(value);
8766b13d60bSMuhammad Moinur Rahman INV_MSG(insert_pair.second, F("PID %s still in all_exec_handles") %
8776b13d60bSMuhammad Moinur Rahman handle.pid());
8786b13d60bSMuhammad Moinur Rahman }
879b0d29bc4SBrooks Davis LI(F("Spawned subprocess with exec_handle %s") % handle.pid());
880b0d29bc4SBrooks Davis return handle;
881b0d29bc4SBrooks Davis }
882b0d29bc4SBrooks Davis
883b0d29bc4SBrooks Davis
884b0d29bc4SBrooks Davis /// Waits for completion of any forked process.
885b0d29bc4SBrooks Davis ///
886b0d29bc4SBrooks Davis /// \param exec_handle The handle of the process to wait for.
887b0d29bc4SBrooks Davis ///
888b0d29bc4SBrooks Davis /// \return A pointer to an object describing the waited-for subprocess.
889b0d29bc4SBrooks Davis executor::exit_handle
wait(const exec_handle exec_handle)890b0d29bc4SBrooks Davis executor::executor_handle::wait(const exec_handle exec_handle)
891b0d29bc4SBrooks Davis {
892b0d29bc4SBrooks Davis signals::check_interrupt();
893b0d29bc4SBrooks Davis const process::status status = process::wait(exec_handle.pid());
894b0d29bc4SBrooks Davis return _pimpl->post_wait(exec_handle.pid(), status);
895b0d29bc4SBrooks Davis }
896b0d29bc4SBrooks Davis
897b0d29bc4SBrooks Davis
898b0d29bc4SBrooks Davis /// Waits for completion of any forked process.
899b0d29bc4SBrooks Davis ///
900b0d29bc4SBrooks Davis /// \return A pointer to an object describing the waited-for subprocess.
901b0d29bc4SBrooks Davis executor::exit_handle
wait_any(void)902b0d29bc4SBrooks Davis executor::executor_handle::wait_any(void)
903b0d29bc4SBrooks Davis {
904b0d29bc4SBrooks Davis signals::check_interrupt();
905b0d29bc4SBrooks Davis const process::status status = process::wait_any();
906b0d29bc4SBrooks Davis return _pimpl->post_wait(status.dead_pid(), status);
907b0d29bc4SBrooks Davis }
908b0d29bc4SBrooks Davis
909b0d29bc4SBrooks Davis
910257e70f1SIgor Ostapenko /// Forms exit_handle for the given PID subprocess.
911257e70f1SIgor Ostapenko ///
912257e70f1SIgor Ostapenko /// Can be used in the cases when we want to do cleanup(s) of a killed test
913257e70f1SIgor Ostapenko /// subprocess, but we do not have exit handle as we usually do after normal
914257e70f1SIgor Ostapenko /// wait mechanism.
915257e70f1SIgor Ostapenko ///
916257e70f1SIgor Ostapenko /// \return A pointer to an object describing the subprocess.
917257e70f1SIgor Ostapenko executor::exit_handle
reap(const int pid)918257e70f1SIgor Ostapenko executor::executor_handle::reap(const int pid)
919257e70f1SIgor Ostapenko {
920257e70f1SIgor Ostapenko return _pimpl->reap(pid);
921257e70f1SIgor Ostapenko }
922257e70f1SIgor Ostapenko
923257e70f1SIgor Ostapenko
924b0d29bc4SBrooks Davis /// Checks if an interrupt has fired.
925b0d29bc4SBrooks Davis ///
926b0d29bc4SBrooks Davis /// Calls to this function should be sprinkled in strategic places through the
927b0d29bc4SBrooks Davis /// code protected by an interrupts_handler object.
928b0d29bc4SBrooks Davis ///
929b0d29bc4SBrooks Davis /// This is just a wrapper over signals::check_interrupt() to avoid leaking this
930b0d29bc4SBrooks Davis /// dependency to the caller.
931b0d29bc4SBrooks Davis ///
932b0d29bc4SBrooks Davis /// \throw signals::interrupted_error If there has been an interrupt.
933b0d29bc4SBrooks Davis void
check_interrupt(void) const934b0d29bc4SBrooks Davis executor::executor_handle::check_interrupt(void) const
935b0d29bc4SBrooks Davis {
936b0d29bc4SBrooks Davis signals::check_interrupt();
937b0d29bc4SBrooks Davis }
938