|
16 | 16 |
|
17 | 17 | #include <errno.h> |
18 | 18 | #include <fcntl.h> |
| 19 | +#include <spawn.h> |
19 | 20 | #include <sys/poll.h> |
20 | 21 | #include <sys/stat.h> |
21 | 22 | #include <sys/types.h> |
| 23 | +#include <sys/wait.h> |
22 | 24 | #include <unistd.h> |
23 | 25 | #ifdef __APPLE__ |
24 | 26 | #include <inttypes.h> |
|
30 | 32 | #include <cstdlib> |
31 | 33 | #include <filesystem> // NOLINT |
32 | 34 | #include <fstream> |
| 35 | +#include <optional> |
33 | 36 | #include <string> |
34 | 37 | #include <string_view> |
35 | 38 | #include <system_error> // NOLINT |
|
55 | 58 | #include "./centipede/util.h" |
56 | 59 | #include "./common/logging.h" |
57 | 60 |
|
| 61 | +#if !defined(_MSC_VER) |
| 62 | +// Needed to pass the current environment to posix_spawn, which needs an |
| 63 | +// explicit envp without an option to inherit implicitly. |
| 64 | +extern char **environ; |
| 65 | +#endif |
| 66 | + |
58 | 67 | namespace fuzztest::internal { |
59 | 68 | namespace { |
60 | 69 |
|
@@ -141,8 +150,16 @@ struct Command::ForkServerProps { |
141 | 150 | // the deleter is instantiated, the special member functions must be defined |
142 | 151 | // out-of-line here, now that ForkServerProps is complete (that's by-the-book |
143 | 152 | // PIMPL). |
144 | | -Command::Command(Command &&other) noexcept = default; |
145 | | -Command::~Command() = default; |
| 153 | +Command::~Command() { |
| 154 | + if (is_executing()) { |
| 155 | + LOG(WARNING) |
| 156 | + << "Destructing Command object for " << path() << " with " |
| 157 | + << (fork_server_ ? absl::StrCat("fork server PID ", fork_server_->pid_) |
| 158 | + : absl::StrCat("PID ", pid_)) |
| 159 | + << " still running. Requesting it to stop without waiting for it..."; |
| 160 | + RequestStop(); |
| 161 | + } |
| 162 | +} |
146 | 163 |
|
147 | 164 | Command::Command(std::string_view path, Options options) |
148 | 165 | : path_(path), options_(std::move(options)) {} |
@@ -308,88 +325,101 @@ absl::Status Command::VerifyForkServerIsHealthy() { |
308 | 325 | return absl::OkStatus(); |
309 | 326 | } |
310 | 327 |
|
311 | | -int Command::Execute() { |
| 328 | +bool Command::ExecuteAsync() { |
| 329 | + CHECK(!is_executing()); |
312 | 330 | VLOG(1) << "Executing command '" << command_line_ << "'..."; |
313 | 331 |
|
314 | | - int exit_code = EXIT_SUCCESS; |
315 | | - |
316 | 332 | if (fork_server_ != nullptr) { |
317 | | - VLOG(1) << "Sending execution request to fork server: " |
318 | | - << VV(options_.timeout); |
| 333 | + VLOG(1) << "Sending execution request to fork server"; |
319 | 334 |
|
320 | 335 | if (const auto status = VerifyForkServerIsHealthy(); !status.ok()) { |
321 | 336 | LogProblemInfo(absl::StrCat("Fork server should be running, but isn't: ", |
322 | 337 | status.message())); |
323 | | - return EXIT_FAILURE; |
| 338 | + return false; |
324 | 339 | } |
325 | 340 |
|
326 | 341 | // Wake up the fork server. |
327 | 342 | char x = ' '; |
328 | 343 | CHECK_EQ(1, write(fork_server_->pipe_[0], &x, 1)); |
| 344 | + } else { |
| 345 | + CHECK_EQ(pid_, -1); |
| 346 | + std::vector<std::string> argv_strs = {"/bin/sh", "-c", command_line_}; |
| 347 | + std::vector<char *> argv; |
| 348 | + argv.reserve(argv_strs.size() + 1); |
| 349 | + for (auto &argv_str : argv_strs) { |
| 350 | + argv.push_back(argv_str.data()); |
| 351 | + } |
| 352 | + argv.push_back(nullptr); |
| 353 | + CHECK_EQ(posix_spawn(&pid_, argv[0], /*file_actions=*/nullptr, |
| 354 | + /*attrp=*/nullptr, argv.data(), environ), |
| 355 | + 0); |
| 356 | + } |
| 357 | + |
| 358 | + is_executing_ = true; |
| 359 | + return true; |
| 360 | +} |
329 | 361 |
|
| 362 | +std::optional<int> Command::Wait(absl::Time deadline) { |
| 363 | + CHECK(is_executing()); |
| 364 | + int exit_code = EXIT_SUCCESS; |
| 365 | + |
| 366 | + if (fork_server_ != nullptr) { |
330 | 367 | // The fork server forks, the child is running. Block until some readable |
331 | 368 | // data appears in the pipe (that is, after the fork server writes the |
332 | 369 | // execution result to it). |
333 | 370 | struct pollfd poll_fd = {}; |
334 | 371 | int poll_ret = -1; |
335 | | - auto poll_deadline = absl::Now() + options_.timeout; |
336 | | - bool sigterm_sent = false; |
337 | | - bool try_again = false; |
338 | 372 | do { |
339 | | - try_again = false; |
340 | 373 | // NOTE: `poll_fd` has to be reset every time. |
341 | 374 | poll_fd = { |
342 | 375 | /*fd=*/fork_server_->pipe_[1], // The file descriptor to wait for. |
343 | 376 | /*events=*/POLLIN, // Wait until `fd` gets readable data. |
344 | 377 | }; |
345 | 378 | const int poll_timeout_ms = static_cast<int>(absl::ToInt64Milliseconds( |
346 | | - std::max(poll_deadline - absl::Now(), absl::Milliseconds(1)))); |
| 379 | + std::max(deadline - absl::Now(), absl::Milliseconds(1)))); |
347 | 380 | poll_ret = poll(&poll_fd, 1, poll_timeout_ms); |
348 | 381 | // The `poll()` syscall can get interrupted: it sets errno==EINTR in that |
349 | 382 | // case. We should tolerate that. |
350 | | - if (poll_ret < 0 && errno == EINTR) { |
351 | | - try_again = true; |
352 | | - continue; |
353 | | - } |
354 | | - if (poll_ret == 0 && !sigterm_sent) { |
355 | | - LogProblemInfo( |
356 | | - absl::StrCat("Timeout while waiting for fork server: timeout is ", |
357 | | - absl::FormatDuration(options_.timeout))); |
358 | | - CHECK_NE(fork_server_->pid_, -1); |
359 | | - LOG(INFO) << "Sending SIGTERM to the fork server PID " |
360 | | - << fork_server_->pid_ << " and waiting for 60s"; |
361 | | - kill(fork_server_->pid_, SIGTERM); |
362 | | - sigterm_sent = true; |
363 | | - poll_deadline += absl::Seconds(60); |
364 | | - try_again = true; |
365 | | - continue; |
366 | | - } |
367 | | - } while (try_again); |
368 | | - |
| 383 | + } while (poll_ret < 0 && errno == EINTR); |
369 | 384 | if (poll_ret != 1 || (poll_fd.revents & POLLIN) == 0) { |
370 | 385 | // The fork server errored out or timed out, or some other error occurred, |
371 | 386 | // e.g. the syscall was interrupted. |
372 | 387 | if (poll_ret == 0) { |
373 | | - CHECK(sigterm_sent); |
374 | | - LogProblemInfo( |
375 | | - "Fork server did not respond within 60s after SIGTERM was sent"); |
376 | | - // TODO: xinhaoyuan - the right thing to do is to either properly |
377 | | - // recover or request early exit. |
| 388 | + LogProblemInfo(absl::StrCat( |
| 389 | + "Timeout while waiting for fork server: deadline is ", deadline)); |
378 | 390 | } else { |
379 | 391 | LogProblemInfo(absl::StrCat( |
380 | 392 | "Error while waiting for fork server: poll() returned ", poll_ret)); |
381 | 393 | } |
382 | | - return EXIT_FAILURE; |
| 394 | + return std::nullopt; |
383 | 395 | } |
384 | 396 |
|
385 | 397 | // The fork server wrote the execution result to the pipe: read it. |
386 | 398 | CHECK_EQ(sizeof(exit_code), |
387 | 399 | read(fork_server_->pipe_[1], &exit_code, sizeof(exit_code))); |
388 | 400 | } else { |
389 | | - VLOG(1) << "Fork server disabled - executing command directly"; |
390 | | - // No fork server, use system(). |
391 | | - exit_code = system(command_line_.c_str()); |
| 401 | + CHECK_NE(pid_, -1); |
| 402 | + while (true) { |
| 403 | + const pid_t r = waitpid(pid_, &exit_code, WNOHANG); |
| 404 | + CHECK_NE(r, -1); |
| 405 | + if (r == pid_ && (WIFEXITED(exit_code) || WIFSIGNALED(exit_code))) break; |
| 406 | + CHECK_EQ(r, 0); |
| 407 | + const auto timeout = deadline - absl::Now(); |
| 408 | + if (timeout > absl::ZeroDuration()) { |
| 409 | + const auto duration = std::clamp<useconds_t>( |
| 410 | + absl::ToInt64Microseconds(timeout), 0, 100000); |
| 411 | + usleep(duration); // NOLINT: early return on SIGCHLD is desired. |
| 412 | + continue; |
| 413 | + } else { |
| 414 | + LogProblemInfo(absl::StrCat( |
| 415 | + "Timeout while waiting for the command process: deadline is ", |
| 416 | + deadline)); |
| 417 | + return std::nullopt; |
| 418 | + } |
| 419 | + } |
| 420 | + pid_ = -1; |
392 | 421 | } |
| 422 | + is_executing_ = false; |
393 | 423 |
|
394 | 424 | // When the command is actually a wrapper shell launching the binary(-es) |
395 | 425 | // (e.g. a Docker container), the shell will preserve a normal exit code |
@@ -444,6 +474,17 @@ int Command::Execute() { |
444 | 474 | return exit_code; |
445 | 475 | } |
446 | 476 |
|
| 477 | +void Command::RequestStop() { |
| 478 | + CHECK(is_executing()); |
| 479 | + if (fork_server_) { |
| 480 | + CHECK_NE(fork_server_->pid_, -1); |
| 481 | + kill(fork_server_->pid_, SIGTERM); |
| 482 | + return; |
| 483 | + } |
| 484 | + CHECK_NE(pid_, -1); |
| 485 | + kill(pid_, SIGTERM); |
| 486 | +} |
| 487 | + |
447 | 488 | std::string Command::ReadRedirectedStdout() const { |
448 | 489 | std::string ret; |
449 | 490 | if (!options_.stdout_file.empty()) { |
|
0 commit comments