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