Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions openspec/changes/fix-child-process-cleanup/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Change: Fix child process cleanup on parent crash

## Why

When Goggles crashes (e.g., due to Vulkan errors or device mismatch in multi-GPU systems), the spawned child process continues running headlessly. Users are unaware the child is still running, and subsequent launches may fail due to resource conflicts (e.g., capture socket already bound).

The previous approach using `PR_SET_PDEATHSIG` directly from a multi-threaded process is unreliable because the signal is tied to the **thread** that called `fork()`, not the entire process.

## What Changes

Introduce a `goggles-reaper` watchdog process:

```
Goggles (main process, multi-threaded)
↓ fork() + exec("goggles-reaper") [SAFE: immediate exec]
goggles-reaper (watchdog, single-threaded)
↓ PR_SET_CHILD_SUBREAPER + PR_SET_PDEATHSIG
↓ fork() + exec(target_app) [SAFE: no threads]
Target Application
```

Benefits:
- Single-threaded reaper ensures `PR_SET_PDEATHSIG` works reliably
- `PR_SET_CHILD_SUBREAPER` catches orphaned grandchildren
- Process group kill ensures all descendants are terminated

## Impact

- Affected specs: `app-window`
- Affected code:
- `src/app/main.cpp` - spawn reaper instead of target app directly
- `src/app/reaper_main.cpp` - new watchdog process
- `src/app/CMakeLists.txt` - build reaper executable
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## ADDED Requirements

### Requirement: Child Process Death Signal

The application SHALL configure spawned child processes to receive SIGTERM when the parent process terminates unexpectedly.

#### Scenario: Parent crash terminates child

- **GIVEN** a child process was spawned via `-- <app>` mode
- **WHEN** the parent goggles process is killed (SIGKILL, crash, or abnormal termination)
- **THEN** the child process SHALL receive SIGTERM automatically
- **AND** the child process SHALL terminate

#### Scenario: Parent PID 1 reparenting race

- **GIVEN** a child process is being spawned
- **WHEN** the parent dies between `fork()` and `prctl()` setup
- **THEN** the child SHALL detect reparenting to PID 1
- **AND** SHALL exit immediately with failure code
22 changes: 22 additions & 0 deletions openspec/changes/fix-child-process-cleanup/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## 1. goggles-reaper Implementation

- [x] 1.1 Create `src/app/reaper_main.cpp`
- [x] 1.2 Set `PR_SET_CHILD_SUBREAPER` to adopt orphaned descendants
- [x] 1.3 Set `PR_SET_PDEATHSIG(SIGTERM)` to detect parent death
- [x] 1.4 Set up signal handlers for SIGTERM/SIGINT/SIGHUP to trigger child cleanup
- [x] 1.5 Spawn target app via `fork()` + `execvp()`
- [x] 1.6 Implement `kill_process_tree()` - recursively kill children via `/proc` scanning
- [x] 1.7 On signal: kill all children, wait for them, then exit
- [x] 1.8 Update `src/app/CMakeLists.txt` to build `goggles-reaper`

## 2. main.cpp Changes

- [x] 2.1 Use `posix_spawn()` to exec `goggles-reaper` (safe: immediate exec)
- [x] 2.2 Pass env vars and target command as arguments to reaper
- [x] 2.3 Remove direct `PR_SET_PDEATHSIG` usage from main.cpp

## 3. Testing

- [x] 3.1 Update `tests/app/test_child_death_signal.cpp` for reaper architecture
- [x] 3.2 Manual test: kill goggles, verify target app terminates
- [x] 3.3 Manual test: target app exits, verify goggles exits cleanly
7 changes: 7 additions & 0 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ set_target_properties(goggles PROPERTIES
goggles_enable_clang_tidy(goggles)
goggles_enable_sanitizers(goggles)
goggles_enable_profiling(goggles)

# Reaper watchdog process
add_executable(goggles-reaper reaper_main.cpp)
set_target_properties(goggles-reaper PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
goggles_enable_clang_tidy(goggles-reaper)
23 changes: 20 additions & 3 deletions src/app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
#include "cli.hpp"

#include <SDL3/SDL.h>
#include <array>
#include <cerrno>
#include <chrono>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <filesystem>
#include <spawn.h>
#include <string>
#include <string_view>
Expand All @@ -20,6 +22,17 @@
#include <util/logging.hpp>
#include <vector>

static auto get_reaper_path() -> std::string {
std::array<char, 4096> exe_path{};
const ssize_t len = readlink("/proc/self/exe", exe_path.data(), exe_path.size() - 1);
if (len <= 0) {
return "goggles-reaper";
}
exe_path[static_cast<size_t>(len)] = '\0';
const std::filesystem::path exe_dir = std::filesystem::path(exe_path.data()).parent_path();
return (exe_dir / "goggles-reaper").string();
}

static auto spawn_target_app(const std::vector<std::string>& command,
const std::string& x11_display, const std::string& wayland_display,
uint32_t app_width, uint32_t app_height, const std::string& gpu_uuid)
Expand Down Expand Up @@ -71,18 +84,22 @@ static auto spawn_target_app(const std::vector<std::string>& command,
}
envp.push_back(nullptr);

// Build argv: goggles-reaper <target_command...>
const std::string reaper_path = get_reaper_path();
std::vector<char*> argv;
argv.reserve(command.size() + 1);
argv.reserve(command.size() + 2);
argv.push_back(const_cast<char*>(reaper_path.c_str()));
for (const auto& arg : command) {
argv.push_back(const_cast<char*>(arg.c_str()));
}
argv.push_back(nullptr);

pid_t pid = -1;
const int rc = posix_spawnp(&pid, argv[0], nullptr, nullptr, argv.data(), envp.data());
const int rc =
posix_spawn(&pid, reaper_path.c_str(), nullptr, nullptr, argv.data(), envp.data());
if (rc != 0) {
return goggles::make_error<pid_t>(goggles::ErrorCode::unknown_error,
std::string("posix_spawnp() failed: ") +
std::string("posix_spawn() failed: ") +
std::strerror(rc));
}

Expand Down
155 changes: 155 additions & 0 deletions src/app/reaper_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// goggles-reaper: single-threaded watchdog process
// Ensures target app and descendants are terminated when goggles dies

#include <array>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <dirent.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>

namespace {

volatile sig_atomic_t g_should_run = 1;

auto get_child_pids(pid_t parent_pid) -> std::vector<pid_t> {
std::vector<pid_t> pids;
DIR* proc_dir = opendir("/proc");
if (proc_dir == nullptr) {
return pids;
}

struct dirent* entry = nullptr;
while ((entry = readdir(proc_dir)) != nullptr) {
if (entry->d_type != DT_DIR || entry->d_name[0] < '0' || entry->d_name[0] > '9') {
continue;
}

std::array<char, 64> path{};
std::snprintf(path.data(), path.size(), "/proc/%s/stat", entry->d_name);

FILE* stat_file = std::fopen(path.data(), "r");
if (stat_file == nullptr) {
continue;
}

std::array<char, 512> buf{};
if (std::fgets(buf.data(), buf.size(), stat_file) == nullptr) {
std::fclose(stat_file);
continue;
}
std::fclose(stat_file);

const char* comm_end = std::strrchr(buf.data(), ')');
if (comm_end == nullptr || comm_end[1] == '\0') {
continue;
}

char state = 0;
pid_t ppid = -1;
if (std::sscanf(comm_end + 1, " %c %d", &state, &ppid) != 2) {
continue;
}

if (ppid == parent_pid) {
pids.push_back(static_cast<pid_t>(std::atoi(entry->d_name)));
}
}
closedir(proc_dir);
return pids;
}

auto kill_process_tree(pid_t pid, int sig) -> void {
const auto children = get_child_pids(pid);
kill(pid, sig);
for (const pid_t child : children) {
kill_process_tree(child, sig);
}
}

auto kill_all_children(int sig) -> void {
const auto children = get_child_pids(getpid());
for (const pid_t child : children) {
kill_process_tree(child, sig);
}
}

auto wait_all_children() -> void {
while (waitpid(-1, nullptr, 0) > 0 || errno == EINTR) {
}
}
Comment on lines +67 to +85
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Termination/wait strategy can hang indefinitely; needs timeout + SIGKILL escalation.

Even with perfect PID discovery, children can ignore SIGTERM. wait_all_children() currently blocks until all children exit, with no deadline.

Sketch of bounded wait + escalation (minimal deps using usleep)
-auto wait_all_children() -> void {
-    while (waitpid(-1, nullptr, 0) > 0 || errno == EINTR) {
-    }
-}
+auto reap_all_children_nonblocking() -> bool {
+    for (;;) {
+        const pid_t r = waitpid(-1, nullptr, WNOHANG);
+        if (r > 0) continue;
+        if (r == 0) return false; // still have children
+        return errno == ECHILD;   // true => none left
+    }
+}
+
+auto wait_all_children_with_timeout_ms(int timeout_ms) -> bool {
+    const int step_us = 20 * 1000;
+    int waited_ms = 0;
+    while (waited_ms < timeout_ms) {
+        if (reap_all_children_nonblocking()) return true;
+        usleep(step_us);
+        waited_ms += 20;
+    }
+    return reap_all_children_nonblocking();
+}

Then in cleanup:

-    kill_all_children(SIGTERM);
-    wait_all_children();
+    kill_all_children(SIGTERM);
+    if (!wait_all_children_with_timeout_ms(2000)) {
+        kill_all_children(SIGKILL);
+        (void)wait_all_children_with_timeout_ms(2000);
+    }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @src/app/reaper_main.cpp around lines 53 - 71, The current
wait_all_children() can block forever because children may ignore SIGTERM;
update the termination strategy: in kill_all_children() send SIGTERM to each
child (use kill_process_tree(pid, SIGTERM) so the function exists), then
implement a bounded wait in wait_all_children() that repeatedly loops with
non-blocking waitpid(..., WNOHANG) and usleep short intervals until either all
children have exited or a deadline (e.g., N seconds) is reached, handling EINTR
correctly; if the deadline is exceeded, escalate by calling
kill_process_tree(child, SIGKILL) for any still-running children and perform a
final blocking waitpid loop to reap them; ensure functions reference
get_child_pids, kill_process_tree, and kill_all_children so remaining PIDs can
be re-queried or tracked during escalation.


auto signal_handler(int /*sig*/) -> void {
g_should_run = 0;
}

auto setup_signal_handlers() -> void {
struct sigaction sa{};
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, nullptr);
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGHUP, &sa, nullptr);
}
Comment on lines +91 to +98
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Check sigaction return values.

If sigaction() fails, signal handlers won't be installed. Since PR_SET_PDEATHSIG sends SIGTERM (line 114), failure to install the SIGTERM handler means the reaper would terminate without cleaning up children when the parent dies, defeating the core purpose of this watchdog.

🛡️ Proposed fix
 auto setup_signal_handlers() -> void {
     struct sigaction sa{};
     sa.sa_handler = signal_handler;
     sigemptyset(&sa.sa_mask);
-    sigaction(SIGTERM, &sa, nullptr);
-    sigaction(SIGINT, &sa, nullptr);
-    sigaction(SIGHUP, &sa, nullptr);
+    if (sigaction(SIGTERM, &sa, nullptr) != 0) {
+        std::fprintf(stderr, "goggles-reaper: sigaction SIGTERM failed: %s\n", std::strerror(errno));
+        std::abort();
+    }
+    if (sigaction(SIGINT, &sa, nullptr) != 0) {
+        std::fprintf(stderr, "goggles-reaper: sigaction SIGINT failed: %s\n", std::strerror(errno));
+        std::abort();
+    }
+    if (sigaction(SIGHUP, &sa, nullptr) != 0) {
+        std::fprintf(stderr, "goggles-reaper: sigaction SIGHUP failed: %s\n", std::strerror(errno));
+        std::abort();
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto setup_signal_handlers() -> void {
struct sigaction sa{};
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, nullptr);
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGHUP, &sa, nullptr);
}
auto setup_signal_handlers() -> void {
struct sigaction sa{};
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGTERM, &sa, nullptr) != 0) {
std::fprintf(stderr, "goggles-reaper: sigaction SIGTERM failed: %s\n", std::strerror(errno));
std::abort();
}
if (sigaction(SIGINT, &sa, nullptr) != 0) {
std::fprintf(stderr, "goggles-reaper: sigaction SIGINT failed: %s\n", std::strerror(errno));
std::abort();
}
if (sigaction(SIGHUP, &sa, nullptr) != 0) {
std::fprintf(stderr, "goggles-reaper: sigaction SIGHUP failed: %s\n", std::strerror(errno));
std::abort();
}
}
🤖 Prompt for AI Agents
In @src/app/reaper_main.cpp around lines 91 - 98, setup_signal_handlers
currently ignores sigaction() return values so handlers may not be installed;
update the function to check each sigaction() call for failure, retrieve errno,
and handle errors (e.g., log a descriptive message including strerror(errno) via
your logging mechanism or perror and abort/exit with non‑zero status or throw an
exception) to ensure the process doesn’t silently continue without handlers
(especially since PR_SET_PDEATHSIG sends SIGTERM). Specifically, wrap the three
sigaction calls for SIGTERM, SIGINT and SIGHUP in checks, and if any returns -1
perform the chosen error handling path so failures are visible and the program
stops or propagates the error.


} // namespace

auto main(int argc, char** argv) -> int {
if (argc < 2) {
std::fprintf(stderr, "goggles-reaper: missing command\n");
return EXIT_FAILURE;
}

const pid_t parent_pid = getppid();

// Become subreaper - orphaned descendants reparent to us
prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);

// Die when parent (goggles) dies
prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0);
if (getppid() != parent_pid) {
return EXIT_FAILURE;
}

Comment on lines +108 to +118
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Check prctl return values.

Both prctl() calls are critical to the reaper's functionality and their return values must be checked:

  • If PR_SET_CHILD_SUBREAPER fails: orphaned descendants won't reparent to the reaper, causing incomplete cleanup.
  • If PR_SET_PDEATHSIG fails: the reaper won't terminate when the parent dies, leaving processes running headlessly.
🐛 Proposed fix
     const pid_t parent_pid = getppid();
 
     // Become subreaper - orphaned descendants reparent to us
-    prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);
+    if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0) {
+        std::fprintf(stderr, "goggles-reaper: PR_SET_CHILD_SUBREAPER failed: %s\n", std::strerror(errno));
+        return EXIT_FAILURE;
+    }
 
     // Die when parent (goggles) dies
-    prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0);
+    if (prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) != 0) {
+        std::fprintf(stderr, "goggles-reaper: PR_SET_PDEATHSIG failed: %s\n", std::strerror(errno));
+        return EXIT_FAILURE;
+    }
     if (getppid() != parent_pid) {
         return EXIT_FAILURE;
     }
🤖 Prompt for AI Agents
In @src/app/reaper_main.cpp around lines 108 - 118, The prctl() calls for
PR_SET_CHILD_SUBREAPER and PR_SET_PDEATHSIG are currently unchecked; modify the
code around prctl(PR_SET_CHILD_SUBREAPER, ...) and prctl(PR_SET_PDEATHSIG, ...)
to check each return value (non-zero indicates failure), emit an error (e.g.,
via perror or process logger) that includes which prctl failed and errno, and
return EXIT_FAILURE if either call fails before proceeding to the getppid()
parent check; keep the existing parent_pid/getppid logic and only proceed when
both prctl calls succeed.

setup_signal_handlers();
Comment on lines +108 to +119
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

prctl(...) return values are unchecked (silent failure → incorrect guarantees).

If PR_SET_CHILD_SUBREAPER or PR_SET_PDEATHSIG fails, the core safety property may not hold.

Proposed error checks
-    prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);
+    if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0) {
+        std::fprintf(stderr, "goggles-reaper: PR_SET_CHILD_SUBREAPER failed: %s\n", std::strerror(errno));
+        return EXIT_FAILURE;
+    }
@@
-    prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0);
+    if (prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) != 0) {
+        std::fprintf(stderr, "goggles-reaper: PR_SET_PDEATHSIG failed: %s\n", std::strerror(errno));
+        return EXIT_FAILURE;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const pid_t parent_pid = getppid();
// Become subreaper - orphaned descendants reparent to us
prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);
// Die when parent (goggles) dies
prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0);
if (getppid() != parent_pid) {
return EXIT_FAILURE;
}
setup_signal_handlers();
const pid_t parent_pid = getppid();
// Become subreaper - orphaned descendants reparent to us
if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0) {
std::fprintf(stderr, "goggles-reaper: PR_SET_CHILD_SUBREAPER failed: %s\n", std::strerror(errno));
return EXIT_FAILURE;
}
// Die when parent (goggles) dies
if (prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) != 0) {
std::fprintf(stderr, "goggles-reaper: PR_SET_PDEATHSIG failed: %s\n", std::strerror(errno));
return EXIT_FAILURE;
}
if (getppid() != parent_pid) {
return EXIT_FAILURE;
}
setup_signal_handlers();


// Fork and exec target app
const pid_t child = fork();
if (child < 0) {
std::fprintf(stderr, "goggles-reaper: fork failed: %s\n", std::strerror(errno));
return EXIT_FAILURE;
}

if (child == 0) {
execvp(argv[1], &argv[1]);
std::fprintf(stderr, "goggles-reaper: exec failed: %s\n", std::strerror(errno));
_exit(EXIT_FAILURE);
}

// Wait for primary child or signal
int status = 0;
while (g_should_run != 0) {
const pid_t pid = waitpid(child, &status, 0);
if (pid == child) {
break;
}
if (pid == -1 && errno != EINTR) {
break;
}
}

// Cleanup all remaining children
g_should_run = 0;
kill_all_children(SIGKILL);
wait_all_children();

if (WIFEXITED(status)) {
return WEXITSTATUS(status);
}
return EXIT_FAILURE;
}
Comment on lines +1 to +155
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for reaper_main.cpp and Linux-specific symbols
rg -n "reaper_main\.cpp|goggles-reaper|PR_SET_CHILD_SUBREAPER|PR_SET_PDEATHSIG" -C 3

Repository: K1ngst0m/Goggles

Length of output: 10761


🏁 Script executed:

#!/bin/bash
# Search for Linux platform guards in CMake and code
rg -n "if.*LINUX|__linux__|CMAKE_SYSTEM_NAME.*Linux|CMAKE_SYSTEM" -C 2

Repository: K1ngst0m/Goggles

Length of output: 402


🏁 Script executed:

#!/bin/bash
# Look for CMakeLists.txt files to understand build structure
find . -name "CMakeLists.txt" -type f | head -20

Repository: K1ngst0m/Goggles

Length of output: 317


🏁 Script executed:

#!/bin/bash
# Check the structure of src/app directory
ls -la src/app/ 2>/dev/null || find . -path "*/src/app*" -type f -name "*.cpp" -o -name "*.h" -o -name "CMakeLists.txt" | head -20

Repository: K1ngst0m/Goggles

Length of output: 823


🏁 Script executed:

#!/bin/bash
# Read the src/app/CMakeLists.txt to see the full build configuration
cat -n src/app/CMakeLists.txt

Repository: K1ngst0m/Goggles

Length of output: 1107


🏁 Script executed:

#!/bin/bash
# Search for any platform guards around the reaper_main.cpp or goggles-reaper build
rg -n -B10 -A5 "add_executable.*reaper|goggles-reaper" src/app/CMakeLists.txt

Repository: K1ngst0m/Goggles

Length of output: 525


🏁 Script executed:

#!/bin/bash
# Check if there are any #ifdef or platform checks in reaper_main.cpp itself
head -30 src/app/reaper_main.cpp | cat -n

Repository: K1ngst0m/Goggles

Length of output: 1051


🏁 Script executed:

#!/bin/bash
# Check the main CMakeLists.txt for any platform-specific logic
cat -n CMakeLists.txt | head -50

Repository: K1ngst0m/Goggles

Length of output: 1182


🏁 Script executed:

#!/bin/bash
# Check the main CMakeLists.txt to see if there's any CMAKE_SYSTEM_NAME check
rg -n "CMAKE_SYSTEM_NAME|if.*WIN|if.*APPLE|if.*UNIX|if.*Linux" CMakeLists.txt

Repository: K1ngst0m/Goggles

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Verify the full reaper_main.cpp for any platform guards
wc -l src/app/reaper_main.cpp
cat -n src/app/reaper_main.cpp | grep -E "ifdef|if.*linux|if.*LINUX" || echo "No #ifdef guards found"

Repository: K1ngst0m/Goggles

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Check if there are any platform-specific configurations in Dependencies.cmake or CompilerConfig.cmake
rg -n "CMAKE_SYSTEM_NAME|WIN32|APPLE|LINUX" cmake/

Repository: K1ngst0m/Goggles

Length of output: 185


Add Linux platform guards to goggles-reaper build or source.

This file is inherently Linux-specific (/proc filesystem access, prctl() syscalls, fork()/execvp()). Currently, src/app/CMakeLists.txt (line 33) builds it unconditionally, and the source has no #ifdef __linux__ guards. Either add if(UNIX AND NOT APPLE) or if(CMAKE_SYSTEM_NAME STREQUAL Linux) to the CMake target, or wrap Linux-specific code sections with #ifdef __linux__ in the source.

🤖 Prompt for AI Agents
In @src/app/reaper_main.cpp around lines 1 - 141, The reaper is Linux-only;
either prevent building it on non-Linux or guard platform-specific APIs: update
the CMake target that currently adds src/app/reaper_main.cpp (around the
existing target in CMakeLists.txt) to build only when Linux (e.g., if(UNIX AND
NOT APPLE) or if(CMAKE_SYSTEM_NAME STREQUAL Linux)), OR wrap the Linux-specific
source body in #ifdef __linux__ / #endif so symbols using /proc, prctl
(PR_SET_CHILD_SUBREAPER, PR_SET_PDEATHSIG), fork(), execvp(), get_child_pids(),
kill_process_tree(), etc. are excluded on non-Linux platforms; choose one
approach and apply it consistently so the file is not compiled/used on non-Linux
systems.

13 changes: 13 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,16 @@ if(DEFINED ENV{CI} OR DEFINED ENV{GITHUB_ACTIONS})
set_property(TEST goggles_auto_input_forwarding_x11 PROPERTY DISABLED TRUE)
set_property(TEST goggles_auto_input_forwarding_wayland PROPERTY DISABLED TRUE)
endif()

# Integration test: validates child process receives SIGTERM when parent dies
add_executable(goggles_test_child_death_signal
app/test_child_death_signal.cpp
)

goggles_enable_sanitizers(goggles_test_child_death_signal)

add_test(NAME goggles_test_child_death_signal
COMMAND goggles_test_child_death_signal)

set_property(TEST goggles_test_child_death_signal
PROPERTY WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
Loading