diff --git a/libs/linglong/src/linglong/cli/cli.cpp b/libs/linglong/src/linglong/cli/cli.cpp index 54ec98d86..72f851b47 100644 --- a/libs/linglong/src/linglong/cli/cli.cpp +++ b/libs/linglong/src/linglong/cli/cli.cpp @@ -49,6 +49,7 @@ #include "linglong/utils/terminal/terminal_guard.h" #include "linglong/utils/unique_fd.h" #include "linglong/utils/xdp.h" +#include "ocppi/cli/CommandFailedError.hpp" #include "ocppi/runtime/ExecOption.hpp" #include "ocppi/runtime/RunOption.hpp" // IWYU pragma: keep #include "ocppi/types/ContainerListItem.hpp" @@ -1210,11 +1211,14 @@ utils::error::Result Cli::reuseContainer(const std::string &id, auto result = ociCLI.exec(id, "/run/linglong/container-init", reuseCommand, ioSetup->option); if (!result) { - // TODO: modify ocppi to expose CommandFailedError::exitStatus() - // so that non-zero exit codes from reused commands are preserved - // instead of collapsed to EXIT_FAILURE. LogE("failed to exec container: {}", LINGLONG_ERRV(result)); - _exit(EXIT_FAILURE); + try { + std::rethrow_exception(result.error()); + } catch (const ocppi::cli::CommandFailedError &e) { + _exit(e.exitStatus()); + } catch (...) { + _exit(EXIT_FAILURE); + } } _exit(EXIT_SUCCESS); @@ -1987,7 +1991,13 @@ int Cli::enter(const EnterOptions &options) if (!result) { auto err = LINGLONG_ERRV(result); this->printer.printErr(err); - return -1; + try { + std::rethrow_exception(result.error()); + } catch (const ocppi::cli::CommandFailedError &e) { + return e.exitStatus(); + } catch (...) { + return -1; + } } return 0; } diff --git a/libs/linglong/tests/ll-tests/src/ocppi/cli/common_cli_test.cpp b/libs/linglong/tests/ll-tests/src/ocppi/cli/common_cli_test.cpp index bdebf44fc..4659af5b5 100644 --- a/libs/linglong/tests/ll-tests/src/ocppi/cli/common_cli_test.cpp +++ b/libs/linglong/tests/ll-tests/src/ocppi/cli/common_cli_test.cpp @@ -7,7 +7,9 @@ #include #include "../../common/tempdir.h" +#include "ocppi/cli/CommandFailedError.hpp" #include "ocppi/cli/crun/Crun.hpp" +#include "ocppi/runtime/ExecOption.hpp" #include "ocppi/runtime/ListOption.hpp" #include "ocppi/types/ContainerListItem.hpp" @@ -56,3 +58,37 @@ TEST(CommonCLITest, ListForcesJsonOutput) }; EXPECT_EQ(arguments, expectedArguments); } + +TEST(CommonCLITest, ExecPreservesCommandExitStatus) +{ + TempDir tempDir("ocppi-common-cli-"); + + const auto runtimePath = tempDir.path() / "runtime"; + { + std::ofstream runtime(runtimePath); + ASSERT_TRUE(runtime.is_open()); + runtime << "#!/bin/sh\n" + "exit 42\n"; + } + std::filesystem::permissions(runtimePath, + std::filesystem::perms::owner_read + | std::filesystem::perms::owner_write + | std::filesystem::perms::owner_exec, + std::filesystem::perm_options::replace); + + auto cli = ocppi::cli::crun::Crun::New(runtimePath); + ASSERT_TRUE(cli.has_value()); + + ocppi::runtime::ExecOption option{}; + auto result = cli.value()->exec(ocppi::runtime::ContainerID{ "test" }, "/bin/true", {}, option); + ASSERT_FALSE(result.has_value()); + + try { + std::rethrow_exception(result.error()); + FAIL() << "expected CommandFailedError to be thrown"; + } catch (const ocppi::cli::CommandFailedError &e) { + EXPECT_EQ(e.exitStatus(), 42); + } catch (...) { + FAIL() << "expected CommandFailedError"; + } +} diff --git a/libs/ocppi/include/ocppi/cli/CommandFailedError.hpp b/libs/ocppi/include/ocppi/cli/CommandFailedError.hpp index e2057827f..1a98080fa 100644 --- a/libs/ocppi/include/ocppi/cli/CommandFailedError.hpp +++ b/libs/ocppi/include/ocppi/cli/CommandFailedError.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -11,16 +13,38 @@ class CommandFailedError : public std::runtime_error { static constexpr const char *prefix = "run command failed: "; + [[nodiscard]] + static auto exitCode(int ret) noexcept -> int + { + if (WIFEXITED(ret)) { + return WEXITSTATUS(ret); + } + if (WIFSIGNALED(ret)) { + return WTERMSIG(ret) + 128; + } + return ret; + } + public: explicit CommandFailedError(int ret, const std::string &command) : std::runtime_error(prefix + command + - " retval=" + std::to_string(ret)) + " retval=" + std::to_string(exitCode(ret))) + , ret_(exitCode(ret)) { } explicit CommandFailedError(int ret, const char *arg) : CommandFailedError(ret, std::string(arg)) { } + + [[nodiscard]] + auto exitStatus() const noexcept -> int + { + return this->ret_; + } + + private: + int ret_{ 0 }; }; }