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 c36001a..1dd3d16 100644 --- a/src/hex_sdk_library/exec.cpp +++ b/src/hex_sdk_library/exec.cpp @@ -126,6 +126,17 @@ Exec(const Cmd& command, bool daemonize) // child process if (pid == 0) { + // 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); + } + 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) { 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; +}