From 0e4fbca3bb676b23c3989d2c18b05d756c6104c1 Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Thu, 18 Jun 2026 21:16:23 -0500 Subject: [PATCH] feat(process): add managed process control --- CMakeLists.txt | 1 + include/izo/Process.hpp | 59 +++++++++ src/platform_linux.cpp | 268 ++++++++++++++++++++++++++++++++++++--- src/platform_windows.cpp | 255 ++++++++++++++++++++++++++++++++++++- src/process.cpp | 15 +++ 5 files changed, 571 insertions(+), 27 deletions(-) create mode 100644 src/process.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6302169..ad9c25c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(izo src/command_line.cpp src/dialogs.cpp src/directory_watcher.cpp + src/process.cpp src/time.cpp $<$:src/interaction_windows.cpp> $<$:src/platform_windows.cpp> diff --git a/include/izo/Process.hpp b/include/izo/Process.hpp index 2e21856..4a00eeb 100644 --- a/include/izo/Process.hpp +++ b/include/izo/Process.hpp @@ -2,7 +2,11 @@ #include #include +#include +#include #include +#include +#include #include namespace izo { @@ -12,6 +16,10 @@ struct ProcessOptions { std::vector arguments; std::filesystem::path workingDirectory; bool detached = false; + bool captureOutput = false; + bool pipeStdin = false; + // Used by RunProcess. A negative value waits indefinitely. + std::int64_t timeoutMs = -1; }; struct ProcessResult { @@ -23,4 +31,55 @@ struct ProcessResult { ProcessResult LaunchProcess(const ProcessOptions& options); +class Process { +public: + Process() = default; + ~Process(); + Process(Process&&) noexcept = default; + Process& operator=(Process&& other) noexcept; + Process(const Process&) = delete; + Process& operator=(const Process&) = delete; + + explicit operator bool() const noexcept { return state_ != nullptr; } + std::uint32_t Id() const noexcept; + bool IsRunning() const; + // Returns false if the timeout expires or this Process is invalid. + bool Wait(std::int64_t timeoutMs = -1, std::string* errorMessage = nullptr); + // Forcefully terminates the child process. + bool Terminate(std::string* errorMessage = nullptr); + std::optional ExitCode() const; + + bool WriteStdin(std::string_view data, std::string* errorMessage = nullptr); + bool CloseStdin(std::string* errorMessage = nullptr); + // Captured output is complete after Wait succeeds. + std::string Stdout() const; + std::string Stderr() const; + +private: + struct State; + explicit Process(std::shared_ptr state) : state_(std::move(state)) {} + std::shared_ptr state_; + + friend Process StartProcess(const ProcessOptions&, std::string*); +}; + +Process StartProcess(const ProcessOptions& options, std::string* errorMessage = nullptr); + +struct RunProcessResult { + bool started = false; + bool timedOut = false; + int exitCode = -1; + std::string stdoutText; + std::string stderrText; + std::string errorMessage; + + explicit operator bool() const noexcept { return started && !timedOut && exitCode == 0; } +}; + +RunProcessResult RunProcess(const ProcessOptions& options); + +bool IsProcessRunning(std::uint32_t processId) noexcept; +std::uint32_t GetCurrentProcessId() noexcept; +std::uint32_t GetParentProcessId() noexcept; + } // namespace izo diff --git a/src/platform_linux.cpp b/src/platform_linux.cpp index 94aa0b8..c157d84 100644 --- a/src/platform_linux.cpp +++ b/src/platform_linux.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #ifdef __linux__ @@ -17,6 +18,8 @@ #include #include +#include +#include #include #include #include @@ -103,6 +106,21 @@ std::string read_first_line(const std::filesystem::path& path) { } // namespace +struct Process::State { + ~State() { if (stdinWrite >= 0) close(stdinWrite); } + + pid_t processId = -1; + int stdinWrite = -1; + mutable std::mutex mutex; + std::mutex stdinMutex; + std::condition_variable changed; + bool finished = false; + int readers = 0; + int exitCode = -1; + std::string stdoutText; + std::string stderrText; +}; + std::filesystem::path GetKnownPath(KnownPath path, std::string* errorMessage) { switch (path) { case KnownPath::AppData: return env_path("XDG_DATA_HOME", home() / ".local/share"); @@ -133,24 +151,50 @@ std::filesystem::path GetKnownPath(KnownPath path, std::string* errorMessage) { return {}; } -ProcessResult LaunchProcess(const ProcessOptions& options) { - if (options.executable.empty()) return {false, 0, "Executable path is required"}; - int error_pipe[2]; - if (pipe2(error_pipe, O_CLOEXEC) != 0) return {false, 0, std::strerror(errno)}; +Process StartProcess(const ProcessOptions& options, std::string* errorMessage) { + if (options.executable.empty()) { + if (errorMessage) *errorMessage = "Executable path is required"; + return {}; + } + int error_pipe[2] = {-1, -1}; + int stdout_pipe[2] = {-1, -1}; + int stderr_pipe[2] = {-1, -1}; + int stdin_pipe[2] = {-1, -1}; + const auto close_pipe = [](int (&pipe_value)[2]) { + if (pipe_value[0] >= 0) close(pipe_value[0]); + if (pipe_value[1] >= 0) close(pipe_value[1]); + pipe_value[0] = pipe_value[1] = -1; + }; + if (pipe2(error_pipe, O_CLOEXEC) != 0 || + (options.captureOutput && + (pipe2(stdout_pipe, O_CLOEXEC) != 0 || pipe2(stderr_pipe, O_CLOEXEC) != 0)) || + (options.pipeStdin && pipe2(stdin_pipe, O_CLOEXEC) != 0)) { + if (errorMessage) *errorMessage = std::strerror(errno); + close_pipe(error_pipe); close_pipe(stdout_pipe); close_pipe(stderr_pipe); close_pipe(stdin_pipe); + return {}; + } const pid_t child = fork(); if (child < 0) { - const std::string error = std::strerror(errno); - close(error_pipe[0]); - close(error_pipe[1]); - return {false, 0, error}; + if (errorMessage) *errorMessage = std::strerror(errno); + close_pipe(error_pipe); close_pipe(stdout_pipe); close_pipe(stderr_pipe); close_pipe(stdin_pipe); + return {}; } if (child == 0) { close(error_pipe[0]); - if (options.detached) setsid(); - if (!options.workingDirectory.empty() && chdir(options.workingDirectory.c_str()) != 0) { + const auto fail = [&](int code) { const int error = errno; write(error_pipe[1], &error, sizeof(error)); - _exit(126); + _exit(code); + }; + if (options.detached && setsid() < 0) fail(126); + if (options.captureOutput) { + if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0 || + dup2(stderr_pipe[1], STDERR_FILENO) < 0) fail(126); + } + if (options.pipeStdin && dup2(stdin_pipe[0], STDIN_FILENO) < 0) fail(126); + close_pipe(stdout_pipe); close_pipe(stderr_pipe); close_pipe(stdin_pipe); + if (!options.workingDirectory.empty() && chdir(options.workingDirectory.c_str()) != 0) { + fail(126); } std::vector values{options.executable.string()}; values.insert(values.end(), options.arguments.begin(), options.arguments.end()); @@ -158,26 +202,210 @@ ProcessResult LaunchProcess(const ProcessOptions& options) { for (auto& value : values) argv.push_back(value.data()); argv.push_back(nullptr); execvp(argv[0], argv.data()); - const int error = errno; - write(error_pipe[1], &error, sizeof(error)); - _exit(127); + fail(127); } close(error_pipe[1]); + error_pipe[1] = -1; + if (stdout_pipe[1] >= 0) { close(stdout_pipe[1]); stdout_pipe[1] = -1; } + if (stderr_pipe[1] >= 0) { close(stderr_pipe[1]); stderr_pipe[1] = -1; } + if (stdin_pipe[0] >= 0) { close(stdin_pipe[0]); stdin_pipe[0] = -1; } int child_error = 0; - const ssize_t error_bytes = read(error_pipe[0], &child_error, sizeof(child_error)); - close(error_pipe[0]); + ssize_t error_bytes = -1; + do { error_bytes = read(error_pipe[0], &child_error, sizeof(child_error)); } + while (error_bytes < 0 && errno == EINTR); + const int pipe_error = errno; + close_pipe(error_pipe); + if (error_bytes < 0) { + kill(child, SIGKILL); + int status = 0; + while (waitpid(child, &status, 0) < 0 && errno == EINTR) {} + close_pipe(stdout_pipe); close_pipe(stderr_pipe); close_pipe(stdin_pipe); + if (errorMessage) *errorMessage = std::strerror(pipe_error); + return {}; + } if (error_bytes > 0) { int status = 0; while (waitpid(child, &status, 0) < 0 && errno == EINTR) {} - return {false, 0, std::strerror(child_error)}; + close_pipe(stdout_pipe); close_pipe(stderr_pipe); close_pipe(stdin_pipe); + if (errorMessage) *errorMessage = std::strerror(child_error); + return {}; } - std::thread([child] { + auto state = std::make_shared(); + state->processId = child; + state->stdinWrite = stdin_pipe[1]; + stdin_pipe[1] = -1; + state->readers = options.captureOutput ? 2 : 0; + const auto read_pipe = [](std::shared_ptr state, int pipe_value, + bool standardError) { + std::string output; + char buffer[4096]; + ssize_t count = 0; + while ((count = read(pipe_value, buffer, sizeof(buffer))) > 0) + output.append(buffer, static_cast(count)); + close(pipe_value); + std::lock_guard lock(state->mutex); + (standardError ? state->stderrText : state->stdoutText) = std::move(output); + --state->readers; + state->changed.notify_all(); + }; + if (options.captureOutput) { + std::thread(read_pipe, state, stdout_pipe[0], false).detach(); + std::thread(read_pipe, state, stderr_pipe[0], true).detach(); + stdout_pipe[0] = stderr_pipe[0] = -1; + } + std::thread([state] { int status = 0; - while (waitpid(child, &status, 0) < 0 && errno == EINTR) {} + pid_t waited = -1; + do { waited = waitpid(state->processId, &status, 0); } while (waited < 0 && errno == EINTR); + std::lock_guard lock(state->mutex); + if (waited > 0 && WIFEXITED(status)) state->exitCode = WEXITSTATUS(status); + else if (waited > 0 && WIFSIGNALED(status)) state->exitCode = 128 + WTERMSIG(status); + state->finished = true; + state->changed.notify_all(); }).detach(); - return {true, static_cast(child), {}}; + return Process(std::move(state)); } +ProcessResult LaunchProcess(const ProcessOptions& options) { + std::string error; + auto process = StartProcess(options, &error); + return {static_cast(process), process.Id(), std::move(error)}; +} + +std::uint32_t Process::Id() const noexcept { + return state_ ? static_cast(state_->processId) : 0; +} + +bool Process::IsRunning() const { + if (!state_) return false; + std::lock_guard lock(state_->mutex); + return !state_->finished; +} + +bool Process::Wait(std::int64_t timeoutMs, std::string* errorMessage) { + if (!state_) { + if (errorMessage) *errorMessage = "Process is not valid"; + return false; + } + std::unique_lock lock(state_->mutex); + const auto done = [&] { return state_->finished && state_->readers == 0; }; + if (timeoutMs < 0) { + state_->changed.wait(lock, done); + return true; + } + return state_->changed.wait_for(lock, std::chrono::milliseconds(timeoutMs), done); +} + +bool Process::Terminate(std::string* errorMessage) { + if (!state_) { + if (errorMessage) *errorMessage = "Process is not valid"; + return false; + } + if (!IsRunning()) return true; + if (kill(state_->processId, SIGKILL) == 0 || errno == ESRCH) return true; + if (errorMessage) *errorMessage = std::strerror(errno); + return false; +} + +std::optional Process::ExitCode() const { + if (!state_) return std::nullopt; + std::lock_guard lock(state_->mutex); + return state_->finished ? std::optional(state_->exitCode) : std::nullopt; +} + +bool Process::WriteStdin(std::string_view data, std::string* errorMessage) { + if (!state_) { + if (errorMessage) *errorMessage = "Process is not valid"; + return false; + } + std::lock_guard lock(state_->stdinMutex); + if (state_->stdinWrite < 0) { + if (errorMessage) *errorMessage = "Process stdin is not piped"; + return false; + } + sigset_t blocked, previous, pending; + sigemptyset(&blocked); + sigaddset(&blocked, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &blocked, &previous); + sigpending(&pending); + const bool already_pending = sigismember(&pending, SIGPIPE) != 0; + std::size_t written = 0; + while (written < data.size()) { + const ssize_t count = write(state_->stdinWrite, data.data() + written, data.size() - written); + if (count < 0 && errno == EINTR) continue; + if (count <= 0) { + const int error = errno; + if (error == EPIPE && !already_pending) { + timespec timeout{}; + sigtimedwait(&blocked, nullptr, &timeout); + } + pthread_sigmask(SIG_SETMASK, &previous, nullptr); + if (errorMessage) *errorMessage = std::strerror(error); + return false; + } + written += static_cast(count); + } + pthread_sigmask(SIG_SETMASK, &previous, nullptr); + return true; +} + +bool Process::CloseStdin(std::string* errorMessage) { + if (!state_) { + if (errorMessage) *errorMessage = "Process is not valid"; + return false; + } + std::lock_guard lock(state_->stdinMutex); + if (state_->stdinWrite < 0) return true; + if (close(state_->stdinWrite) != 0) { + if (errorMessage) *errorMessage = std::strerror(errno); + return false; + } + state_->stdinWrite = -1; + return true; +} + +std::string Process::Stdout() const { + if (!state_) return {}; + std::lock_guard lock(state_->mutex); + return state_->stdoutText; +} + +std::string Process::Stderr() const { + if (!state_) return {}; + std::lock_guard lock(state_->mutex); + return state_->stderrText; +} + +RunProcessResult RunProcess(const ProcessOptions& options) { + RunProcessResult result; + std::string error; + auto process = StartProcess(options, &error); + if (!process) { + result.errorMessage = std::move(error); + return result; + } + result.started = true; + if (options.pipeStdin) process.CloseStdin(); + if (!process.Wait(options.timeoutMs, &result.errorMessage)) { + result.timedOut = true; + process.Terminate(&result.errorMessage); + if (!process.Wait(1000) && result.errorMessage.empty()) + result.errorMessage = "Timed out while closing process pipes"; + } + if (const auto code = process.ExitCode()) result.exitCode = *code; + result.stdoutText = process.Stdout(); + result.stderrText = process.Stderr(); + return result; +} + +bool IsProcessRunning(std::uint32_t processId) noexcept { + if (!processId) return false; + return kill(static_cast(processId), 0) == 0 || errno == EPERM; +} + +std::uint32_t GetCurrentProcessId() noexcept { return static_cast(getpid()); } +std::uint32_t GetParentProcessId() noexcept { return static_cast(getppid()); } + DynamicLibrary::~DynamicLibrary() { Reset(); } DynamicLibrary::DynamicLibrary(DynamicLibrary&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; diff --git a/src/platform_windows.cpp b/src/platform_windows.cpp index 811ddbe..d9880bd 100644 --- a/src/platform_windows.cpp +++ b/src/platform_windows.cpp @@ -7,9 +7,15 @@ #include #include +#include +#include #include +#include +#include #include +#include +#include #include #include @@ -93,6 +99,7 @@ std::filesystem::path shell_folder(REFKNOWNFOLDERID id, std::string* errorMessag } std::wstring quote(std::wstring value) { + if (value.empty()) return L"\"\""; if (value.find_first_of(L" \t\"") == std::wstring::npos) return value; std::wstring result = L"\""; std::size_t slashes = 0; @@ -121,6 +128,25 @@ LONG WINAPI unhandled_exception(EXCEPTION_POINTERS* info) { } // namespace +struct Process::State { + ~State() { + if (stdinWrite) CloseHandle(stdinWrite); + if (process) CloseHandle(process); + } + + HANDLE process = nullptr; + HANDLE stdinWrite = nullptr; + std::uint32_t id = 0; + mutable std::mutex mutex; + std::mutex stdinMutex; + std::condition_variable changed; + bool finished = false; + int readers = 0; + int exitCode = -1; + std::string stdoutText; + std::string stderrText; +}; + std::filesystem::path GetKnownPath(KnownPath path, std::string* errorMessage) { switch (path) { case KnownPath::AppData: return shell_folder(FOLDERID_RoamingAppData, errorMessage); @@ -160,24 +186,239 @@ std::filesystem::path GetKnownPath(KnownPath path, std::string* errorMessage) { return {}; } -ProcessResult LaunchProcess(const ProcessOptions& options) { - if (options.executable.empty()) return {false, 0, "Executable path is required"}; +Process StartProcess(const ProcessOptions& options, std::string* errorMessage) { + if (options.executable.empty()) { + if (errorMessage) *errorMessage = "Executable path is required"; + return {}; + } std::wstring command = quote(options.executable.wstring()); for (const auto& argument : options.arguments) command += L' ' + quote(widen(argument)); + SECURITY_ATTRIBUTES security{sizeof(security), nullptr, TRUE}; + HANDLE stdout_read = nullptr, stdout_write = nullptr; + HANDLE stderr_read = nullptr, stderr_write = nullptr; + HANDLE stdin_read = nullptr, stdin_write = nullptr; + const auto close = [](HANDLE& handle) { if (handle) CloseHandle(handle); handle = nullptr; }; + if (options.captureOutput && + (!CreatePipe(&stdout_read, &stdout_write, &security, 0) || + !SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0) || + !CreatePipe(&stderr_read, &stderr_write, &security, 0) || + !SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0))) { + if (errorMessage) *errorMessage = error_text("CreatePipe"); + close(stdout_read); close(stdout_write); close(stderr_read); close(stderr_write); + return {}; + } + if (options.pipeStdin && + (!CreatePipe(&stdin_read, &stdin_write, &security, 0) || + !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0))) { + if (errorMessage) *errorMessage = error_text("CreatePipe"); + close(stdout_read); close(stdout_write); close(stderr_read); close(stderr_write); + close(stdin_read); close(stdin_write); + return {}; + } STARTUPINFOW startup{}; startup.cb = sizeof(startup); + if (options.captureOutput || options.pipeStdin) { + startup.dwFlags = STARTF_USESTDHANDLES; + startup.hStdInput = options.pipeStdin ? stdin_read : GetStdHandle(STD_INPUT_HANDLE); + startup.hStdOutput = options.captureOutput ? stdout_write : GetStdHandle(STD_OUTPUT_HANDLE); + startup.hStdError = options.captureOutput ? stderr_write : GetStdHandle(STD_ERROR_HANDLE); + } PROCESS_INFORMATION process{}; const auto workingDirectory = options.workingDirectory.wstring(); DWORD flags = options.detached ? DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP : 0; - if (!CreateProcessW(options.executable.c_str(), command.data(), nullptr, nullptr, FALSE, flags, + if (!CreateProcessW(options.executable.c_str(), command.data(), nullptr, nullptr, + options.captureOutput || options.pipeStdin, flags, nullptr, workingDirectory.empty() ? nullptr : workingDirectory.c_str(), &startup, &process)) { - return {false, 0, error_text("CreateProcessW")}; + if (errorMessage) *errorMessage = error_text("CreateProcessW"); + close(stdout_read); close(stdout_write); close(stderr_read); close(stderr_write); + close(stdin_read); close(stdin_write); + return {}; } - const auto id = static_cast(process.dwProcessId); CloseHandle(process.hThread); - CloseHandle(process.hProcess); - return {true, id, {}}; + close(stdout_write); close(stderr_write); close(stdin_read); + auto state = std::make_shared(); + state->process = process.hProcess; + state->stdinWrite = stdin_write; + state->id = static_cast(process.dwProcessId); + state->readers = options.captureOutput ? 2 : 0; + const auto read_pipe = [](std::shared_ptr state, HANDLE pipe, + bool standardError) { + std::string output; + char buffer[4096]; + DWORD count = 0; + while (ReadFile(pipe, buffer, sizeof(buffer), &count, nullptr) && count) + output.append(buffer, count); + CloseHandle(pipe); + std::lock_guard lock(state->mutex); + (standardError ? state->stderrText : state->stdoutText) = std::move(output); + --state->readers; + state->changed.notify_all(); + }; + if (options.captureOutput) { + std::thread(read_pipe, state, stdout_read, false).detach(); + std::thread(read_pipe, state, stderr_read, true).detach(); + } + std::thread([state] { + WaitForSingleObject(state->process, INFINITE); + DWORD code = static_cast(-1); + GetExitCodeProcess(state->process, &code); + std::lock_guard lock(state->mutex); + state->exitCode = static_cast(code); + state->finished = true; + state->changed.notify_all(); + }).detach(); + return Process(std::move(state)); +} + +ProcessResult LaunchProcess(const ProcessOptions& options) { + std::string error; + auto process = StartProcess(options, &error); + return {static_cast(process), process.Id(), std::move(error)}; +} + +std::uint32_t Process::Id() const noexcept { return state_ ? state_->id : 0; } + +bool Process::IsRunning() const { + if (!state_) return false; + std::lock_guard lock(state_->mutex); + return !state_->finished; +} + +bool Process::Wait(std::int64_t timeoutMs, std::string* errorMessage) { + if (!state_) { + if (errorMessage) *errorMessage = "Process is not valid"; + return false; + } + std::unique_lock lock(state_->mutex); + const auto done = [&] { return state_->finished && state_->readers == 0; }; + if (timeoutMs < 0) { + state_->changed.wait(lock, done); + return true; + } + return state_->changed.wait_for(lock, std::chrono::milliseconds(timeoutMs), done); +} + +bool Process::Terminate(std::string* errorMessage) { + if (!state_) { + if (errorMessage) *errorMessage = "Process is not valid"; + return false; + } + if (!IsRunning()) return true; + if (TerminateProcess(state_->process, 1)) return true; + DWORD code = STILL_ACTIVE; + if (GetExitCodeProcess(state_->process, &code) && code != STILL_ACTIVE) return true; + if (errorMessage) *errorMessage = error_text("TerminateProcess"); + return false; +} + +std::optional Process::ExitCode() const { + if (!state_) return std::nullopt; + std::lock_guard lock(state_->mutex); + return state_->finished ? std::optional(state_->exitCode) : std::nullopt; +} + +bool Process::WriteStdin(std::string_view data, std::string* errorMessage) { + if (!state_) { + if (errorMessage) *errorMessage = "Process is not valid"; + return false; + } + std::lock_guard lock(state_->stdinMutex); + if (!state_->stdinWrite) { + if (errorMessage) *errorMessage = "Process stdin is not piped"; + return false; + } + std::size_t written = 0; + while (written < data.size()) { + DWORD count = 0; + const DWORD requested = static_cast((std::min)( + data.size() - written, static_cast((std::numeric_limits::max)()))); + if (!WriteFile(state_->stdinWrite, data.data() + written, requested, &count, nullptr)) { + if (errorMessage) *errorMessage = error_text("WriteFile"); + return false; + } + written += count; + } + return true; +} + +bool Process::CloseStdin(std::string* errorMessage) { + if (!state_) { + if (errorMessage) *errorMessage = "Process is not valid"; + return false; + } + std::lock_guard lock(state_->stdinMutex); + if (!state_->stdinWrite) return true; + if (!CloseHandle(state_->stdinWrite)) { + if (errorMessage) *errorMessage = error_text("CloseHandle"); + return false; + } + state_->stdinWrite = nullptr; + return true; +} + +std::string Process::Stdout() const { + if (!state_) return {}; + std::lock_guard lock(state_->mutex); + return state_->stdoutText; +} + +std::string Process::Stderr() const { + if (!state_) return {}; + std::lock_guard lock(state_->mutex); + return state_->stderrText; +} + +RunProcessResult RunProcess(const ProcessOptions& options) { + RunProcessResult result; + std::string error; + auto process = StartProcess(options, &error); + if (!process) { + result.errorMessage = std::move(error); + return result; + } + result.started = true; + if (options.pipeStdin) process.CloseStdin(); + if (!process.Wait(options.timeoutMs, &result.errorMessage)) { + result.timedOut = true; + process.Terminate(&result.errorMessage); + if (!process.Wait(1000) && result.errorMessage.empty()) + result.errorMessage = "Timed out while closing process pipes"; + } + if (const auto code = process.ExitCode()) result.exitCode = *code; + result.stdoutText = process.Stdout(); + result.stderrText = process.Stderr(); + return result; +} + +bool IsProcessRunning(std::uint32_t processId) noexcept { + if (!processId) return false; + HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, processId); + if (!process) return GetLastError() == ERROR_ACCESS_DENIED; + const bool running = WaitForSingleObject(process, 0) == WAIT_TIMEOUT; + CloseHandle(process); + return running; +} + +std::uint32_t GetCurrentProcessId() noexcept { return ::GetCurrentProcessId(); } + +std::uint32_t GetParentProcessId() noexcept { + const DWORD current = ::GetCurrentProcessId(); + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) return 0; + PROCESSENTRY32W entry{}; + entry.dwSize = sizeof(entry); + std::uint32_t parent = 0; + if (Process32FirstW(snapshot, &entry)) { + do { + if (entry.th32ProcessID == current) { + parent = entry.th32ParentProcessID; + break; + } + } while (Process32NextW(snapshot, &entry)); + } + CloseHandle(snapshot); + return parent; } DynamicLibrary::~DynamicLibrary() { Reset(); } diff --git a/src/process.cpp b/src/process.cpp new file mode 100644 index 0000000..93f0c78 --- /dev/null +++ b/src/process.cpp @@ -0,0 +1,15 @@ +#include + +namespace izo { + +Process::~Process() { CloseStdin(); } + +Process& Process::operator=(Process&& other) noexcept { + if (this != &other) { + CloseStdin(); + state_ = std::move(other.state_); + } + return *this; +} + +} // namespace izo