aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/process.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zencore/process.cpp')
-rw-r--r--src/zencore/process.cpp237
1 files changed, 227 insertions, 10 deletions
diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp
index 29de107bd..8a91ab287 100644
--- a/src/zencore/process.cpp
+++ b/src/zencore/process.cpp
@@ -137,6 +137,144 @@ IsZombieProcess(int pid, std::error_code& OutEc)
}
#endif // ZEN_PLATFORM_MAC
+//////////////////////////////////////////////////////////////////////////
+// Pipe creation for child process stdout capture
+
+#if ZEN_PLATFORM_WINDOWS
+
+StdoutPipeHandles::~StdoutPipeHandles()
+{
+ Close();
+}
+
+StdoutPipeHandles::StdoutPipeHandles(StdoutPipeHandles&& Other) noexcept
+: ReadHandle(std::exchange(Other.ReadHandle, nullptr))
+, WriteHandle(std::exchange(Other.WriteHandle, nullptr))
+{
+}
+
+StdoutPipeHandles&
+StdoutPipeHandles::operator=(StdoutPipeHandles&& Other) noexcept
+{
+ if (this != &Other)
+ {
+ Close();
+ ReadHandle = std::exchange(Other.ReadHandle, nullptr);
+ WriteHandle = std::exchange(Other.WriteHandle, nullptr);
+ }
+ return *this;
+}
+
+void
+StdoutPipeHandles::CloseWriteEnd()
+{
+ if (WriteHandle)
+ {
+ CloseHandle(WriteHandle);
+ WriteHandle = nullptr;
+ }
+}
+
+void
+StdoutPipeHandles::Close()
+{
+ if (ReadHandle)
+ {
+ CloseHandle(ReadHandle);
+ ReadHandle = nullptr;
+ }
+ CloseWriteEnd();
+}
+
+bool
+CreateStdoutPipe(StdoutPipeHandles& OutPipe)
+{
+ SECURITY_ATTRIBUTES Sa;
+ Sa.nLength = sizeof(Sa);
+ Sa.lpSecurityDescriptor = nullptr;
+ Sa.bInheritHandle = TRUE;
+
+ HANDLE ReadHandle = nullptr;
+ HANDLE WriteHandle = nullptr;
+ if (!::CreatePipe(&ReadHandle, &WriteHandle, &Sa, 0))
+ {
+ return false;
+ }
+
+ // The read end should not be inherited by the child
+ SetHandleInformation(ReadHandle, HANDLE_FLAG_INHERIT, 0);
+
+ OutPipe.ReadHandle = ReadHandle;
+ OutPipe.WriteHandle = WriteHandle;
+ return true;
+}
+
+#else
+
+StdoutPipeHandles::~StdoutPipeHandles()
+{
+ Close();
+}
+
+StdoutPipeHandles::StdoutPipeHandles(StdoutPipeHandles&& Other) noexcept
+: ReadFd(std::exchange(Other.ReadFd, -1))
+, WriteFd(std::exchange(Other.WriteFd, -1))
+{
+}
+
+StdoutPipeHandles&
+StdoutPipeHandles::operator=(StdoutPipeHandles&& Other) noexcept
+{
+ if (this != &Other)
+ {
+ Close();
+ ReadFd = std::exchange(Other.ReadFd, -1);
+ WriteFd = std::exchange(Other.WriteFd, -1);
+ }
+ return *this;
+}
+
+void
+StdoutPipeHandles::CloseWriteEnd()
+{
+ if (WriteFd >= 0)
+ {
+ close(WriteFd);
+ WriteFd = -1;
+ }
+}
+
+void
+StdoutPipeHandles::Close()
+{
+ if (ReadFd >= 0)
+ {
+ close(ReadFd);
+ ReadFd = -1;
+ }
+ CloseWriteEnd();
+}
+
+bool
+CreateStdoutPipe(StdoutPipeHandles& OutPipe)
+{
+ int Fds[2];
+ if (pipe(Fds) != 0)
+ {
+ return false;
+ }
+ OutPipe.ReadFd = Fds[0];
+ OutPipe.WriteFd = Fds[1];
+
+ // Set close-on-exec on the read end so the child doesn't inherit it
+ fcntl(OutPipe.ReadFd, F_SETFD, FD_CLOEXEC);
+ return true;
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+
ProcessHandle::ProcessHandle() = default;
#if ZEN_PLATFORM_WINDOWS
@@ -309,6 +447,10 @@ ProcessHandle::Reset()
{
#if ZEN_PLATFORM_WINDOWS
CloseHandle(m_ProcessHandle);
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ // Reap the child if it has already exited to prevent zombies.
+ // If still running, it will be reparented to init on our exit.
+ waitpid(m_Pid, nullptr, WNOHANG);
#endif
m_ProcessHandle = nullptr;
m_Pid = 0;
@@ -350,17 +492,26 @@ ProcessHandle::Wait(int TimeoutMs, std::error_code& OutEc)
timespec SleepTime = {0, SleepMs * 1000 * 1000};
for (int SleepedTimeMS = 0;; SleepedTimeMS += SleepMs)
{
- int WaitState = 0;
- if (waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED) != -1)
+ int WaitState = 0;
+ pid_t WaitResult = waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED);
+ if (WaitResult > 0 && WIFEXITED(WaitState))
{
- if (WIFEXITED(WaitState))
- {
- m_ExitCode = WEXITSTATUS(WaitState);
- }
+ m_ExitCode = WEXITSTATUS(WaitState);
}
if (!IsProcessRunning(m_Pid, OutEc))
{
+ // Process is gone but waitpid(WNOHANG) may have missed the exit status
+ // due to a TOCTOU race (process became a zombie between waitpid and
+ // IsProcessRunning). Do a blocking reap now to capture the exit code.
+ if (WaitResult <= 0)
+ {
+ WaitState = 0;
+ if (waitpid(m_Pid, &WaitState, 0) > 0 && WIFEXITED(WaitState))
+ {
+ m_ExitCode = WEXITSTATUS(WaitState);
+ }
+ }
return true;
}
else if (OutEc)
@@ -381,6 +532,12 @@ ProcessHandle::Wait(int TimeoutMs, std::error_code& OutEc)
else if (IsZombieProcess(m_Pid, OutEc))
{
ZEN_INFO("Found process {} in zombie state, treating as not running", m_Pid);
+ // Reap the zombie to capture its exit code.
+ WaitState = 0;
+ if (waitpid(m_Pid, &WaitState, 0) > 0 && WIFEXITED(WaitState))
+ {
+ m_ExitCode = WEXITSTATUS(WaitState);
+ }
return true;
}
@@ -567,7 +724,40 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
ExtendableWideStringBuilder<256> CommandLineZ;
CommandLineZ << CommandLine;
- if (!Options.StdoutFile.empty())
+ bool DuplicatedStdErr = false;
+
+ if (Options.StdoutPipe != nullptr && Options.StdoutPipe->WriteHandle != nullptr)
+ {
+ StartupInfo.hStdInput = nullptr;
+ StartupInfo.hStdOutput = (HANDLE)Options.StdoutPipe->WriteHandle;
+
+ if (Options.StderrPipe != nullptr && Options.StderrPipe->WriteHandle != nullptr)
+ {
+ // Use separate pipe for stderr
+ StartupInfo.hStdError = (HANDLE)Options.StderrPipe->WriteHandle;
+ StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+ InheritHandles = true;
+ }
+ else
+ {
+ // Duplicate stdout handle for stderr (both go to same pipe)
+ const BOOL DupSuccess = DuplicateHandle(GetCurrentProcess(),
+ StartupInfo.hStdOutput,
+ GetCurrentProcess(),
+ &StartupInfo.hStdError,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS);
+
+ if (DupSuccess)
+ {
+ DuplicatedStdErr = true;
+ StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+ InheritHandles = true;
+ }
+ }
+ }
+ else if (!Options.StdoutFile.empty())
{
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof sa;
@@ -593,6 +783,7 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
if (Success)
{
+ DuplicatedStdErr = true;
StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
InheritHandles = true;
}
@@ -616,8 +807,16 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
if (StartupInfo.dwFlags & STARTF_USESTDHANDLES)
{
- CloseHandle(StartupInfo.hStdError);
- CloseHandle(StartupInfo.hStdOutput);
+ // Only close hStdError if we duplicated it (caller-owned pipe handles are not ours to close)
+ if (DuplicatedStdErr)
+ {
+ CloseHandle(StartupInfo.hStdError);
+ }
+ // Only close hStdOutput if it was a file handle we created (not a pipe handle owned by caller)
+ if (Options.StdoutPipe == nullptr || Options.StdoutPipe->WriteHandle == nullptr)
+ {
+ CloseHandle(StartupInfo.hStdOutput);
+ }
}
if (!Success)
@@ -826,7 +1025,25 @@ CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine
ZEN_UNUSED(Result);
}
- if (!Options.StdoutFile.empty())
+ if (Options.StdoutPipe != nullptr && Options.StdoutPipe->WriteFd >= 0)
+ {
+ dup2(Options.StdoutPipe->WriteFd, STDOUT_FILENO);
+
+ if (Options.StderrPipe != nullptr && Options.StderrPipe->WriteFd >= 0)
+ {
+ dup2(Options.StderrPipe->WriteFd, STDERR_FILENO);
+ close(Options.StderrPipe->WriteFd);
+ // StderrPipe ReadFd has FD_CLOEXEC so it's auto-closed on exec
+ }
+ else
+ {
+ dup2(Options.StdoutPipe->WriteFd, STDERR_FILENO);
+ }
+
+ close(Options.StdoutPipe->WriteFd);
+ // ReadFd has FD_CLOEXEC so it's auto-closed on exec
+ }
+ else if (!Options.StdoutFile.empty())
{
int Fd = open(Options.StdoutFile.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (Fd >= 0)