Raw reader: use posix_spawn instead of fork + exec

This commit switchexisd the Raw reader to use posix_spawn, instead of
the combination of fork + exec. This should be much more efficient, and
also makes the code smaller, and easier to read and understand.
This commit is contained in:
Johanna Amann 2023-08-03 14:00:38 +01:00
parent 646b301b65
commit e97f63dbbe

View file

@ -3,6 +3,9 @@
#include "zeek/input/readers/raw/Raw.h" #include "zeek/input/readers/raw/Raw.h"
#include <fcntl.h> #include <fcntl.h>
#ifndef _MSC_VER
#include <spawn.h>
#endif
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
@ -12,6 +15,8 @@
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
extern char** environ;
#include "zeek/input/readers/raw/Plugin.h" #include "zeek/input/readers/raw/Plugin.h"
#include "zeek/input/readers/raw/raw.bif.h" #include "zeek/input/readers/raw/raw.bif.h"
#include "zeek/threading/SerialTypes.h" #include "zeek/threading/SerialTypes.h"
@ -120,6 +125,10 @@ std::unique_lock<std::mutex> Raw::AcquireForkMutex()
bool Raw::Execute() bool Raw::Execute()
{ {
#ifdef _MSC_VER
// Executing applications is currently not supported on Windows
return false;
#else
// AFAICT, pipe/fork/exec should be thread-safe, but actually having // AFAICT, pipe/fork/exec should be thread-safe, but actually having
// multiple threads set up pipes and fork concurrently sometimes // multiple threads set up pipes and fork concurrently sometimes
// results in problems w/ a stdin pipe not ever getting an EOF even // results in problems w/ a stdin pipe not ever getting an EOF even
@ -136,75 +145,54 @@ bool Raw::Execute()
return false; return false;
} }
childpid = fork(); posix_spawnattr_t attrs;
if ( childpid < 0 ) posix_spawnattr_init(&attrs);
{
Error(Fmt("Could not create child process: %d", errno));
return false;
}
else if ( childpid == 0 ) short spawn_flags = 0;
{ // equivalent to setpgid(0,0) in the child
// we are the child. spawn_flags |= POSIX_SPAWN_SETPGROUP;
// Obtain a process group w/ child's PID. posix_spawn_file_actions_t actions;
if ( setpgid(0, 0) == -1 ) posix_spawn_file_actions_init(&actions);
_exit(251); posix_spawn_file_actions_addclose(&actions, pipes[stdout_in]);
posix_spawn_file_actions_adddup2(&actions, pipes[stdout_out], stdout_fileno);
posix_spawn_file_actions_addclose(&actions, pipes[stdout_out]);
posix_spawn_file_actions_addclose(&actions, pipes[stdin_out]);
posix_spawn_file_actions_adddup2(&actions, pipes[stdin_in], stdin_fileno);
posix_spawn_file_actions_addclose(&actions, pipes[stdin_in]);
posix_spawn_file_actions_addclose(&actions, pipes[stderr_in]);
posix_spawn_file_actions_adddup2(&actions, pipes[stderr_out], stderr_fileno);
posix_spawn_file_actions_addclose(&actions, pipes[stderr_out]);
close(pipes[stdout_in]); posix_spawnattr_setflags(&attrs, spawn_flags);
if ( dup2(pipes[stdout_out], stdout_fileno) == -1 )
_exit(252);
close(pipes[stdout_out]); // Signal mask is inherited, so reset any ignored
close(pipes[stdin_out]);
if ( stdin_towrite && dup2(pipes[stdin_in], stdin_fileno) == -1 )
_exit(253);
close(pipes[stdin_in]);
close(pipes[stderr_in]);
if ( use_stderr && dup2(pipes[stderr_out], stderr_fileno) == -1 )
_exit(254);
close(pipes[stderr_out]);
// Signal mask is inherited over fork-exec, so reset any ignored
// signals to default behavior and unblock any blocked signals. // signals to default behavior and unblock any blocked signals.
setsignal(SIGPIPE, SIG_DFL); // May be ignored when debugging scripts. // The old code in the child process calls SIG_UNBLOCK on a full mask,
// and set SIGPIPE to the default signal, ignoring anything else. New
// code replicates this behavior.
sigset_t mask; sigset_t mask;
sigfillset(&mask); sigemptyset(&mask);
// Assuming the fork-one model of pthreads' fork(), using sigprocmask() spawn_flags |= POSIX_SPAWN_SETSIGMASK;
// makes sense over pthread_sigmask() here. posix_spawnattr_setsigmask(&attrs, &mask);
sigprocmask(SIG_UNBLOCK, &mask, 0);
execl("/bin/sh", "sh", "-c", fname.c_str(), (char*)NULL); spawn_flags |= POSIX_SPAWN_SETSIGDEF;
fprintf(stderr, "Exec failed :(......\n"); sigset_t sigdefault;
_exit(255); sigemptyset(&sigdefault);
} sigaddset(&sigdefault, SIGPIPE);
else posix_spawnattr_setsigdefault(&attrs, &sigdefault);
const char* spawn_argv[] = {"sh", "-c", fname.c_str(), nullptr};
auto posix_spawn_res = posix_spawn(&childpid, "/bin/sh", &actions, &attrs,
const_cast<char**>(spawn_argv), environ);
posix_spawnattr_destroy(&attrs);
posix_spawn_file_actions_destroy(&actions);
if ( posix_spawn_res != 0 )
{ {
// we are the parent Error(Fmt("Could not spawn child process: %d", errno));
return false;
// Parent also sets child process group immediately to avoid a race.
if ( setpgid(childpid, childpid) == -1 )
{
if ( errno == EACCES )
// Child already did exec. That's fine since then it must have
// already done the setpgid() itself.
;
else if ( errno == ESRCH && kill(childpid, 0) == 0 )
// Sometimes (e.g. FreeBSD) this error is reported even though
// child exists, so do extra sanity check of whether it exists.
;
else
{
char buf[256];
util::zeek_strerror_r(errno, buf, sizeof(buf));
Warning(Fmt("Could not set child process group: %s", buf));
}
} }
lock.unlock(); lock.unlock();
@ -221,7 +209,7 @@ bool Raw::Execute()
if ( stdin_towrite ) if ( stdin_towrite )
{ {
// Ya, just always set this to nonblocking. we do not // Ya, just always set this to nonblocking. We do not
// want to block on a program receiving data. Note // want to block on a program receiving data. Note
// that there is a small gotcha with it. More data is // that there is a small gotcha with it. More data is
// queued when more data is read from the program // queued when more data is read from the program
@ -257,8 +245,7 @@ bool Raw::Execute()
if ( use_stderr ) if ( use_stderr )
{ {
stderrfile = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(pipes[stderr_in], "r"), stderrfile = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(pipes[stderr_in], "r"), fclose);
fclose);
if ( ! stderrfile ) if ( ! stderrfile )
{ {
@ -270,7 +257,7 @@ bool Raw::Execute()
} }
return true; return true;
} #endif
} }
bool Raw::OpenInput() bool Raw::OpenInput()