feat(process): add managed process control

This commit is contained in:
2026-06-18 21:16:23 -05:00
parent 264c59864f
commit 0e4fbca3bb
5 changed files with 571 additions and 27 deletions

View File

@@ -10,6 +10,7 @@ target_sources(izo
src/command_line.cpp
src/dialogs.cpp
src/directory_watcher.cpp
src/process.cpp
src/time.cpp
$<$<PLATFORM_ID:Windows>:src/interaction_windows.cpp>
$<$<PLATFORM_ID:Windows>:src/platform_windows.cpp>

View File

@@ -2,7 +2,11 @@
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace izo {
@@ -12,6 +16,10 @@ struct ProcessOptions {
std::vector<std::string> 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<int> 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) : state_(std::move(state)) {}
std::shared_ptr<State> 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

View File

@@ -7,6 +7,7 @@
#include <dlfcn.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <sys/sysinfo.h>
#ifdef __linux__
@@ -17,6 +18,8 @@
#include <unistd.h>
#include <cerrno>
#include <chrono>
#include <condition_variable>
#include <cstdlib>
#include <cstring>
#include <fstream>
@@ -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<std::string> 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<Process::State>();
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<Process::State> 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<std::size_t>(count));
close(pipe_value);
std::lock_guard<std::mutex> 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<std::mutex> 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<std::uint32_t>(child), {}};
return Process(std::move(state));
}
ProcessResult LaunchProcess(const ProcessOptions& options) {
std::string error;
auto process = StartProcess(options, &error);
return {static_cast<bool>(process), process.Id(), std::move(error)};
}
std::uint32_t Process::Id() const noexcept {
return state_ ? static_cast<std::uint32_t>(state_->processId) : 0;
}
bool Process::IsRunning() const {
if (!state_) return false;
std::lock_guard<std::mutex> 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<std::mutex> 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<int> Process::ExitCode() const {
if (!state_) return std::nullopt;
std::lock_guard<std::mutex> lock(state_->mutex);
return state_->finished ? std::optional<int>(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<std::mutex> 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<std::size_t>(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<std::mutex> 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<std::mutex> lock(state_->mutex);
return state_->stdoutText;
}
std::string Process::Stderr() const {
if (!state_) return {};
std::lock_guard<std::mutex> 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<pid_t>(processId), 0) == 0 || errno == EPERM;
}
std::uint32_t GetCurrentProcessId() noexcept { return static_cast<std::uint32_t>(getpid()); }
std::uint32_t GetParentProcessId() noexcept { return static_cast<std::uint32_t>(getppid()); }
DynamicLibrary::~DynamicLibrary() { Reset(); }
DynamicLibrary::DynamicLibrary(DynamicLibrary&& other) noexcept : handle_(other.handle_) {
other.handle_ = nullptr;

View File

@@ -7,9 +7,15 @@
#include <windows.h>
#include <shlobj.h>
#include <tlhelp32.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdlib>
#include <limits>
#include <mutex>
#include <string>
#include <thread>
@@ -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<std::uint32_t>(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<Process::State>();
state->process = process.hProcess;
state->stdinWrite = stdin_write;
state->id = static_cast<std::uint32_t>(process.dwProcessId);
state->readers = options.captureOutput ? 2 : 0;
const auto read_pipe = [](std::shared_ptr<Process::State> 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<std::mutex> 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<DWORD>(-1);
GetExitCodeProcess(state->process, &code);
std::lock_guard<std::mutex> lock(state->mutex);
state->exitCode = static_cast<int>(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<bool>(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<std::mutex> 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<std::mutex> 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<int> Process::ExitCode() const {
if (!state_) return std::nullopt;
std::lock_guard<std::mutex> lock(state_->mutex);
return state_->finished ? std::optional<int>(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<std::mutex> 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<DWORD>((std::min)(
data.size() - written, static_cast<std::size_t>((std::numeric_limits<DWORD>::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<std::mutex> 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<std::mutex> lock(state_->mutex);
return state_->stdoutText;
}
std::string Process::Stderr() const {
if (!state_) return {};
std::lock_guard<std::mutex> 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(); }

15
src/process.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include <izo/Process.hpp>
namespace izo {
Process::~Process() { CloseStdin(); }
Process& Process::operator=(Process&& other) noexcept {
if (this != &other) {
CloseStdin();
state_ = std::move(other.state_);
}
return *this;
}
} // namespace izo