From ccb33ed86d48b5dde3cdf24ed87ce9f080287705 Mon Sep 17 00:00:00 2001 From: Travis Wu Date: Fri, 26 Jun 2026 16:48:24 +0800 Subject: [PATCH 1/2] fix(exec): detach child stdin to /dev/null so commit subprocesses don't block on a serial console Exec() redirects the child's stdout/stderr but leaves stdin inherited from the parent (hex_config). On a serial-console install (IPMI Serial-over-LAN, /dev/ttyS0) the commit subprocesses inherit that UART as stdin. Tools that initialize a terminal on stdin -- e.g. mongosh during the mongodb module commit -- then block forever opening the controlling TTY in tty_port_block_til_ready (waiting for carrier; CLOCAL unset), stalling the entire policy apply at the mongodb stage. Commit subprocesses never read stdin, so redirect it to /dev/null, mirroring the existing stdout/stderr /dev/null handling. This makes commits independent of the console type (serial/SOL vs VGA). Co-Authored-By: Claude Opus 4.8 Signed-off-by: Travis Wu --- src/hex_sdk_library/exec.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/hex_sdk_library/exec.cpp b/src/hex_sdk_library/exec.cpp index c36001a..7755a78 100644 --- a/src/hex_sdk_library/exec.cpp +++ b/src/hex_sdk_library/exec.cpp @@ -126,6 +126,21 @@ Exec(const Cmd& command, bool daemonize) // child process if (pid == 0) { + // Detach stdin from any inherited controlling terminal. When a commit runs + // on a serial console (e.g. IPMI Serial-over-LAN, /dev/ttyS0), the child + // would otherwise inherit that UART as stdin; tools that initialize a + // terminal on stdin (such as mongosh) then block forever opening it in + // tty_port_block_til_ready (waiting for carrier, CLOCAL not set). Commit + // subprocesses never read stdin, so point it at /dev/null. Mirrors the + // stdout/stderr /dev/null handling below. + int devNullIn = open("/dev/null", O_RDONLY); + if (devNullIn == -1 || dup2(devNullIn, STDIN_FILENO) == -1) { + _exit(EXIT_FAILURE); + } + if (devNullIn > STDIN_FILENO) { + close(devNullIn); + } + if (command.captureStdout) { // close the read-ends of the pipe, child only needs to write if (close(stdoutPipe[0]) == -1) { From f9ec378f92989dc57de65d87ccf3178a7196f9ac Mon Sep 17 00:00:00 2001 From: Travis Wu Date: Fri, 26 Jun 2026 17:53:58 +0800 Subject: [PATCH 2/2] test(exec): cover the stdin /dev/null redirect; shorten its comment Add test_exec_stdin_01, which gives the parent process a non-/dev/null stdin and asserts an Exec()-spawned child still sees /dev/null -- guarding the serial-console hang fix. Wire a tests/ dir into the SDK library Makefile, since exec.cpp is compiled directly at the SDK root. Also condense the inline comment on the stdin redirect. Co-Authored-By: Claude Opus 4.8 Signed-off-by: Travis Wu --- src/hex_sdk_library/Makefile | 3 ++ src/hex_sdk_library/exec.cpp | 10 ++--- src/hex_sdk_library/tests/Makefile | 7 ++++ .../tests/test_exec_stdin_01.cpp | 42 +++++++++++++++++++ 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 src/hex_sdk_library/tests/Makefile create mode 100644 src/hex_sdk_library/tests/test_exec_stdin_01.cpp diff --git a/src/hex_sdk_library/Makefile b/src/hex_sdk_library/Makefile index 9db1b11..eaef153 100755 --- a/src/hex_sdk_library/Makefile +++ b/src/hex_sdk_library/Makefile @@ -26,6 +26,9 @@ SUBDIRS += dryrun SUBDIRS += crypto SUBDIRS += license +# Unit tests for sources built directly into the SDK library (e.g. exec.cpp). +SUBDIRS += tests + LIB = $(HEX_SDK_LIB_ARCHIVE) LIB_SRCS = exec.cpp diff --git a/src/hex_sdk_library/exec.cpp b/src/hex_sdk_library/exec.cpp index 7755a78..1dd3d16 100644 --- a/src/hex_sdk_library/exec.cpp +++ b/src/hex_sdk_library/exec.cpp @@ -126,13 +126,9 @@ Exec(const Cmd& command, bool daemonize) // child process if (pid == 0) { - // Detach stdin from any inherited controlling terminal. When a commit runs - // on a serial console (e.g. IPMI Serial-over-LAN, /dev/ttyS0), the child - // would otherwise inherit that UART as stdin; tools that initialize a - // terminal on stdin (such as mongosh) then block forever opening it in - // tty_port_block_til_ready (waiting for carrier, CLOCAL not set). Commit - // subprocesses never read stdin, so point it at /dev/null. Mirrors the - // stdout/stderr /dev/null handling below. + // Redirect stdin to /dev/null so the child doesn't inherit a serial console + // (e.g. IPMI SoL /dev/ttyS0) as stdin; tools like mongosh would otherwise + // block opening the controlling TTY. Mirrors the stdout/stderr handling below. int devNullIn = open("/dev/null", O_RDONLY); if (devNullIn == -1 || dup2(devNullIn, STDIN_FILENO) == -1) { _exit(EXIT_FAILURE); diff --git a/src/hex_sdk_library/tests/Makefile b/src/hex_sdk_library/tests/Makefile new file mode 100644 index 0000000..0dc59a5 --- /dev/null +++ b/src/hex_sdk_library/tests/Makefile @@ -0,0 +1,7 @@ +# HEX SDK + +include ../../../../build.mk + +TESTS_LIBS = $(HEX_SDK_LIB_ARCHIVE) + +include $(HEX_MAKEDIR)/hex_sdk.mk diff --git a/src/hex_sdk_library/tests/test_exec_stdin_01.cpp b/src/hex_sdk_library/tests/test_exec_stdin_01.cpp new file mode 100644 index 0000000..a540884 --- /dev/null +++ b/src/hex_sdk_library/tests/test_exec_stdin_01.cpp @@ -0,0 +1,42 @@ +// HEX SDK + +#include +#include + +#include + +#include +#include + +// Verify Exec() detaches the child's stdin to /dev/null instead of letting it +// inherit the parent's stdin. This guards the serial-console hang fix: we give +// the test process (the parent) a recognizable, non-/dev/null stdin and confirm +// the child still sees /dev/null. Without the fix the child would inherit the +// parent's fd and the assertion below would fail. +int main() +{ + // Point the parent's stdin at something that is clearly not /dev/null. + int fd = open("/dev/zero", O_RDONLY); + HEX_TEST_FATAL(fd != -1); + HEX_TEST_FATAL(dup2(fd, STDIN_FILENO) != -1); + if (fd > STDIN_FILENO) { + close(fd); + } + + // The child reports what its own stdin (fd 0) points to. + const ExecSyncResult result = + ExecBashSync(10, true /*captureStdout*/, false /*captureStderr*/, {}, "readlink /proc/self/fd/0"); + + HEX_TEST(result.exitCode == 0); + HEX_TEST(!result.isTimedOut); + + // Strip the trailing newline from readlink's output. + std::string target = result.stdoutOutput; + while (!target.empty() && (target.back() == '\n' || target.back() == '\r')) { + target.pop_back(); + } + + HEX_TEST(target == "/dev/null"); + + return HexTestResult; +}