diff --git a/CMakeLists.txt b/CMakeLists.txt index e6cb4a0..767f7e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,9 +113,10 @@ set(linyaps-box_CPM_LOCAL_PACKAGES_ONLY # ============================================================================== -if (linyaps-box_HARDENING) +if(linyaps-box_HARDENING) message(STATUS "applying harden settings") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie -Wl,-z,relro -Wl,-z,now") + set(CMAKE_EXE_LINKER_FLAGS + "${CMAKE_EXE_LINKER_FLAGS} -pie -Wl,-z,relro -Wl,-z,now") add_compile_options(-fstack-protector-strong) if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") @@ -160,12 +161,20 @@ set(linyaps-box_LIBRARY_SOURCE src/linyaps_box/impl/table_printer.h src/linyaps_box/interface.cpp src/linyaps_box/interface.h + src/linyaps_box/io/epoll.cpp + src/linyaps_box/io/epoll.h + src/linyaps_box/io/forwarder.cpp + src/linyaps_box/io/forwarder.h src/linyaps_box/printer.cpp src/linyaps_box/printer.h src/linyaps_box/runtime.cpp src/linyaps_box/runtime.h src/linyaps_box/status_directory.cpp src/linyaps_box/status_directory.h + src/linyaps_box/terminal.cpp + src/linyaps_box/terminal.h + src/linyaps_box/unixsocket.cpp + src/linyaps_box/unixsocket.h src/linyaps_box/utils/atomic_write.cpp src/linyaps_box/utils/atomic_write.h src/linyaps_box/utils/cgroups.cpp @@ -173,30 +182,41 @@ set(linyaps-box_LIBRARY_SOURCE src/linyaps_box/utils/close_range.cpp src/linyaps_box/utils/close_range.h src/linyaps_box/utils/defer.h + src/linyaps_box/utils/epoll.cpp + src/linyaps_box/utils/epoll.h + src/linyaps_box/utils/file.cpp src/linyaps_box/utils/file_describer.cpp src/linyaps_box/utils/file_describer.h - src/linyaps_box/utils/fstat.cpp - src/linyaps_box/utils/fstat.h + src/linyaps_box/utils/file.h src/linyaps_box/utils/inspect.cpp src/linyaps_box/utils/inspect.h + src/linyaps_box/utils/ioctl.h src/linyaps_box/utils/log.cpp src/linyaps_box/utils/log.h src/linyaps_box/utils/mkdir.cpp src/linyaps_box/utils/mkdir.h src/linyaps_box/utils/mknod.cpp src/linyaps_box/utils/mknod.h - src/linyaps_box/utils/open_file.cpp - src/linyaps_box/utils/open_file.h src/linyaps_box/utils/platform.cpp src/linyaps_box/utils/platform.h + src/linyaps_box/utils/process.cpp + src/linyaps_box/utils/process.h + src/linyaps_box/utils/ringbuffer.cpp + src/linyaps_box/utils/ringbuffer.h src/linyaps_box/utils/semver.cpp src/linyaps_box/utils/semver.h - src/linyaps_box/utils/socketpair.cpp - src/linyaps_box/utils/socketpair.h + src/linyaps_box/utils/session.cpp + src/linyaps_box/utils/session.h + src/linyaps_box/utils/signal.cpp + src/linyaps_box/utils/signal.h + src/linyaps_box/utils/socket.cpp + src/linyaps_box/utils/socket.h + src/linyaps_box/utils/span.h src/linyaps_box/utils/symlink.cpp src/linyaps_box/utils/symlink.h - src/linyaps_box/utils/touch.cpp - src/linyaps_box/utils/touch.h) + src/linyaps_box/utils/terminal.cpp + src/linyaps_box/utils/terminal.h + src/linyaps_box/utils/utils.h) set(LINYAPS_BOX_VERSION ${PROJECT_VERSION}) @@ -240,7 +260,7 @@ if(linyaps-box_ENABLE_CPM) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(CPM) - if (linyaps-box_CPM_LOCAL_PACKAGES_ONLY) + if(linyaps-box_CPM_LOCAL_PACKAGES_ONLY) set(CPM_USE_LOCAL_PACKAGES ON) endif() diff --git a/src/linyaps_box/app.cpp b/src/linyaps_box/app.cpp index 77a445e..269eec0 100644 --- a/src/linyaps_box/app.cpp +++ b/src/linyaps_box/app.cpp @@ -9,21 +9,9 @@ #include "linyaps_box/command/list.h" #include "linyaps_box/command/run.h" #include "linyaps_box/utils/log.h" +#include "linyaps_box/utils/utils.h" #include "utils/log.h" -namespace { - -template -struct subCommand : T... -{ - using T::operator()...; -}; - -template -subCommand(T...) -> subCommand; - -} // namespace - namespace linyaps_box { // The main function of the ll-box, @@ -56,24 +44,24 @@ try { return opts.global.return_code; } - return std::visit(subCommand{ [](const command::list_options &options) { - command::list(options); - return 0; - }, - [](const command::exec_options &options) -> int { - command::exec(options); - __builtin_unreachable(); - }, - [](const command::kill_options &options) { - command::kill(options); - return 0; - }, - [](const command::run_options &options) { - return command::run(options); - }, - [](const std::monostate &) { - return 0; - } }, + return std::visit(utils::Overload{ [](const command::list_options &options) { + command::list(options); + return 0; + }, + [](const command::exec_options &options) -> int { + command::exec(options); + __builtin_unreachable(); + }, + [](const command::kill_options &options) { + command::kill(options); + return 0; + }, + [](const command::run_options &options) { + return command::run(options); + }, + [](const std::monostate &) { + return 0; + } }, opts.subcommand_opt); } catch (const std::exception &e) { LINYAPS_BOX_ERR() << "Error: " << e.what(); diff --git a/src/linyaps_box/command/exec.cpp b/src/linyaps_box/command/exec.cpp index 0ffd442..4b9fdf0 100644 --- a/src/linyaps_box/command/exec.cpp +++ b/src/linyaps_box/command/exec.cpp @@ -31,19 +31,18 @@ void linyaps_box::command::exec(const struct exec_options &options) if (options.caps) { const auto &caps = options.caps.value(); auto transform_cap = [&caps](std::vector &cap_set) { - std::transform(caps.cbegin(), - caps.cend(), - std::back_inserter(cap_set), - [](const std::string &cap) { - cap_value_t val{ 0 }; - if (cap_from_name(cap.c_str(), &val) < 0) { - throw std::system_error(errno, - std::generic_category(), - "cap_from_name"); - } - - return val; - }); + std::transform( + caps.cbegin(), + caps.cend(), + std::back_inserter(cap_set), + [](const std::string &cap) { + cap_value_t val{ 0 }; + if (cap_from_name(cap.c_str(), &val) < 0) { + throw std::system_error(errno, std::system_category(), "cap_from_name"); + } + + return val; + }); }; transform_cap(proc.capabilities.effective); diff --git a/src/linyaps_box/command/options.cpp b/src/linyaps_box/command/options.cpp index c3a7bf5..4b2dad4 100644 --- a/src/linyaps_box/command/options.cpp +++ b/src/linyaps_box/command/options.cpp @@ -5,6 +5,7 @@ #include "linyaps_box/command/options.h" #include "linyaps_box/config.h" +#include "linyaps_box/utils/file.h" #include "linyaps_box/version.h" #include @@ -66,6 +67,26 @@ linyaps_box::command::options linyaps_box::command::parse(int argc, char *argv[] "Pass N additional file descriptors to the container") ->default_val(0); + auto socket_check = [](const std::string &str) { + try { + auto ret = utils::lstat(str); + if (!utils::is_type(ret.st_mode, std::filesystem::file_type::socket)) { + return "console-socket must be a socket"; + } + } catch (const std::system_error &e) { + return e.what(); + } + + return ""; + }; + + cmd_run->add_option("--console-socket", + run_opt.console_socket, + "Path to an unix socket that will receive the master end of the console's " + "pseudoterminal") + ->type_name("SOCKET") + ->check(socket_check); + exec_options exec_opt{ options.global }; auto *cmd_exec = app.add_subcommand("exec", "Exec a command in a running container") ->positionals_at_end(); @@ -86,6 +107,13 @@ linyaps_box::command::options linyaps_box::command::parse(int argc, char *argv[] return ""; }, "env_check"); + cmd_exec->add_option("--console-socket", + exec_opt.console_socket, + "Path to an unix socket that will receive the master end of the console's " + "pseudoterminal") + ->type_name("SOCKET") + ->check(socket_check); + cmd_exec->add_flag("--tty", exec_opt.tty, "Allocate a pseudo-TTY")->default_val(false); // TODO: enable capabilities and no_new_privs support after rewrite exec, // cmd_exec->add_option("-c,--cap", options.exec.caps, "Set capabilities") // ->check( diff --git a/src/linyaps_box/command/options.h b/src/linyaps_box/command/options.h index de6b739..dd34324 100644 --- a/src/linyaps_box/command/options.h +++ b/src/linyaps_box/command/options.h @@ -42,6 +42,7 @@ struct exec_options } bool no_new_privs; + bool tty; std::reference_wrapper global_; std::vector command; std::string user; @@ -49,6 +50,7 @@ struct exec_options std::string ID; std::optional cwd; std::optional> envs; + std::optional console_socket; }; struct run_options @@ -62,7 +64,8 @@ struct run_options std::string ID; std::string bundle; std::string config; - int preserve_fds; + std::string console_socket; + int preserve_fds{}; }; struct kill_options diff --git a/src/linyaps_box/command/run.cpp b/src/linyaps_box/command/run.cpp index e15fad3..acd7330 100644 --- a/src/linyaps_box/command/run.cpp +++ b/src/linyaps_box/command/run.cpp @@ -14,11 +14,17 @@ auto linyaps_box::command::run(const struct run_options &options) -> int std::make_unique(options.global_.get().root); runtime_t runtime(std::move(dir)); const create_container_options_t create_container_options{ options.global_.get().manager, - options.preserve_fds, options.ID, options.bundle, options.config }; - auto container = runtime.create_container(create_container_options); - return container.run(container.get_config().process); + + run_container_options_t run_options; + run_options.preserve_fds = options.preserve_fds; + + if (container.get_config().process.terminal && !options.console_socket.empty()) { + run_options.console_socket = unixSocketClient::connect(options.console_socket); + } + + return container.run(std::move(run_options)); } diff --git a/src/linyaps_box/config.cpp b/src/linyaps_box/config.cpp index 273e88b..1630233 100644 --- a/src/linyaps_box/config.cpp +++ b/src/linyaps_box/config.cpp @@ -13,8 +13,8 @@ namespace { // This function is used to parse the mount options from the config file and it only will be called // once. -auto parse_mount_options(const std::vector &options) - -> std::tuple +auto parse_mount_options(const std::vector &options) -> std:: + tuple { const std::unordered_map propagation_flags_map{ { "rprivate", MS_PRIVATE | MS_REC }, { "private", MS_PRIVATE }, @@ -62,12 +62,15 @@ auto parse_mount_options(const std::vector &options) { "symfollow", LINGYAPS_MS_NOSYMFOLLOW }, }; - const std::unordered_map extra_flags_map{ - { "copy-symlink", linyaps_box::config::mount_t::COPY_SYMLINK } - }; + const std::unordered_map + extra_flags_map{ { "copy-symlink", + linyaps_box::config::mount_t::extension::COPY_SYMLINK } }; unsigned long flags = 0; - std::uint8_t extra_flags = 0; + linyaps_box::config::mount_t::extension extra_flags{ + linyaps_box::config::mount_t::extension::NONE + }; + unsigned long propagation_flags = 0; std::stringstream data; @@ -209,19 +212,19 @@ auto parse_linux(const nlohmann::json &obj, const nlohmann::json::json_pointer & linyaps_box::config::linux_t::namespace_t n; auto type = json["type"].get(); if (type == "pid") { - n.type = linyaps_box::config::linux_t::namespace_t::type_t::PID; + n.type_ = linyaps_box::config::linux_t::namespace_t::type::PID; } else if (type == "network") { - n.type = linyaps_box::config::linux_t::namespace_t::type_t::NET; + n.type_ = linyaps_box::config::linux_t::namespace_t::type::NET; } else if (type == "ipc") { - n.type = linyaps_box::config::linux_t::namespace_t::type_t::IPC; + n.type_ = linyaps_box::config::linux_t::namespace_t::type::IPC; } else if (type == "uts") { - n.type = linyaps_box::config::linux_t::namespace_t::type_t::UTS; + n.type_ = linyaps_box::config::linux_t::namespace_t::type::UTS; } else if (type == "mount") { - n.type = linyaps_box::config::linux_t::namespace_t::type_t::MOUNT; + n.type_ = linyaps_box::config::linux_t::namespace_t::type::MOUNT; } else if (type == "user") { - n.type = linyaps_box::config::linux_t::namespace_t::type_t::USER; + n.type_ = linyaps_box::config::linux_t::namespace_t::type::USER; } else if (type == "cgroup") { - n.type = linyaps_box::config::linux_t::namespace_t::type_t::CGROUP; + n.type_ = linyaps_box::config::linux_t::namespace_t::type::CGROUP; } else { throw std::runtime_error("unsupported namespace type: " + type); } @@ -300,8 +303,10 @@ auto parse_1_2_0(const nlohmann::json &j) -> linyaps_box::config // https://github.com/opencontainers/runtime-spec/blob/09fcb39bb7185b46dfb206bc8f3fea914c674779/config.md?plain=1#L245 if (cfg.process.terminal && j.contains(ptr / "process" / "consoleSize")) { - cfg.process.console.height = j[ptr / "process" / "consoleSize" / "height"].get(); - cfg.process.console.width = j[ptr / "process" / "consoleSize" / "width"].get(); + cfg.process.console_size = linyaps_box::config::process_t::console_size_t{ + j[ptr / "process" / "consoleSize" / "height"].get(), + j[ptr / "process" / "consoleSize" / "width"].get() + }; } cfg.process.cwd = j[ptr / "process" / "cwd"].get(); @@ -427,7 +432,7 @@ auto parse_1_2_0(const nlohmann::json &j) -> linyaps_box::config const auto it = m.find("options"); if (it != m.end()) { auto options = it->get>(); - std::tie(mount.flags, mount.propagation_flags, mount.extra_flags, mount.data) = + std::tie(mount.flags, mount.propagation_flags, mount.extension_flags, mount.data) = parse_mount_options(options); } @@ -465,3 +470,27 @@ linyaps_box::config linyaps_box::config::parse(std::istream &is) auto j = nlohmann::json::parse(is); return parse_1_2_0(j); } + +std::string linyaps_box::to_string(linyaps_box::config::linux_t::namespace_t::type type) noexcept +{ + switch (type) { + case config::linux_t::namespace_t::type::NONE: + return "none"; + case config::linux_t::namespace_t::type::IPC: + return "ipc"; + case config::linux_t::namespace_t::type::UTS: + return "uts"; + case config::linux_t::namespace_t::type::MOUNT: + return "mount"; + case config::linux_t::namespace_t::type::PID: + return "pid"; + case config::linux_t::namespace_t::type::NET: + return "net"; + case config::linux_t::namespace_t::type::USER: + return "user"; + case config::linux_t::namespace_t::type::CGROUP: + return "cgroup"; + } + + __builtin_unreachable(); +} diff --git a/src/linyaps_box/config.h b/src/linyaps_box/config.h index 00c62f5..8a78a53 100644 --- a/src/linyaps_box/config.h +++ b/src/linyaps_box/config.h @@ -35,15 +35,15 @@ struct config struct process_t { - bool terminal = false; + bool terminal{ false }; - struct console_t + struct console_size_t { - uint height = 0; - uint width = 0; + unsigned short height{ 0 }; + unsigned short width{ 0 }; }; - console_t console; + std::optional console_size; std::filesystem::path cwd; std::vector env; @@ -87,6 +87,7 @@ struct config std::optional> additional_gids; }; + // TODO: user is optional user_t user; }; @@ -103,8 +104,8 @@ struct config struct namespace_t { - enum type_t { - INVALID = 0, + enum class type : unsigned int { + NONE = 0, IPC = CLONE_NEWIPC, UTS = CLONE_NEWUTS, MOUNT = CLONE_NEWNS, @@ -114,7 +115,7 @@ struct config CGROUP = CLONE_NEWCGROUP, }; - type_t type{ type_t::INVALID }; + type type_{ type::NONE }; std::optional path; }; @@ -150,12 +151,12 @@ struct config struct mount_t { - enum extension : std::uint8_t { COPY_SYMLINK = 1 }; + enum class extension : std::uint8_t { NONE = 0, COPY_SYMLINK }; std::optional source; std::optional destination; std::string type; - std::uint8_t extra_flags{ 0 }; + extension extension_flags{ 0 }; unsigned long flags{ 0 }; unsigned long propagation_flags{ 0 }; std::string data; @@ -174,4 +175,76 @@ struct config std::optional> annotations; }; +std::string to_string(linyaps_box::config::linux_t::namespace_t::type type) noexcept; + } // namespace linyaps_box + +constexpr linyaps_box::config::mount_t::extension +operator|(linyaps_box::config::mount_t::extension lhs, linyaps_box::config::mount_t::extension rhs) +{ + return static_cast( + static_cast>(lhs) + | static_cast>(rhs)); +} + +constexpr linyaps_box::config::mount_t::extension +operator&(linyaps_box::config::mount_t::extension lhs, linyaps_box::config::mount_t::extension rhs) +{ + return static_cast( + static_cast>(lhs) + & static_cast>(rhs)); +} + +constexpr linyaps_box::config::mount_t::extension & +operator|=(linyaps_box::config::mount_t::extension &lhs, + linyaps_box::config::mount_t::extension rhs) noexcept +{ + lhs = lhs | rhs; + return lhs; +} + +constexpr linyaps_box::config::mount_t::extension & +operator&=(linyaps_box::config::mount_t::extension &lhs, + linyaps_box::config::mount_t::extension rhs) noexcept +{ + lhs = lhs & rhs; + return lhs; +} + +constexpr linyaps_box::config::linux_t::namespace_t::type +operator|(linyaps_box::config::linux_t::namespace_t::type lhs, + linyaps_box::config::linux_t::namespace_t::type rhs) noexcept +{ + return static_cast( + static_cast>( + lhs) + | static_cast>( + rhs)); +} + +constexpr linyaps_box::config::linux_t::namespace_t::type +operator&(linyaps_box::config::linux_t::namespace_t::type lhs, + linyaps_box::config::linux_t::namespace_t::type rhs) noexcept +{ + return static_cast( + static_cast>( + lhs) + & static_cast>( + rhs)); +} + +constexpr linyaps_box::config::linux_t::namespace_t::type & +operator|=(linyaps_box::config::linux_t::namespace_t::type &lhs, + linyaps_box::config::linux_t::namespace_t::type rhs) noexcept +{ + lhs = lhs | rhs; + return lhs; +} + +constexpr linyaps_box::config::linux_t::namespace_t::type & +operator&=(linyaps_box::config::linux_t::namespace_t::type &lhs, + linyaps_box::config::linux_t::namespace_t::type rhs) noexcept +{ + lhs = lhs & rhs; + return lhs; +} diff --git a/src/linyaps_box/container.cpp b/src/linyaps_box/container.cpp index 14e6142..f5bf905 100644 --- a/src/linyaps_box/container.cpp +++ b/src/linyaps_box/container.cpp @@ -5,23 +5,30 @@ #include "linyaps_box/container.h" #include "linyaps_box/impl/disabled_cgroup_manager.h" +#include "linyaps_box/io/epoll.h" +#include "linyaps_box/io/forwarder.h" +#include "linyaps_box/terminal.h" +#include "linyaps_box/unixsocket.h" #include "linyaps_box/utils/cgroups.h" #include "linyaps_box/utils/close_range.h" +#include "linyaps_box/utils/file.h" #include "linyaps_box/utils/file_describer.h" -#include "linyaps_box/utils/fstat.h" #include "linyaps_box/utils/inspect.h" #include "linyaps_box/utils/log.h" #include "linyaps_box/utils/mkdir.h" #include "linyaps_box/utils/mknod.h" -#include "linyaps_box/utils/open_file.h" #include "linyaps_box/utils/platform.h" -#include "linyaps_box/utils/socketpair.h" +#include "linyaps_box/utils/process.h" +#include "linyaps_box/utils/session.h" +#include "linyaps_box/utils/signal.h" +#include "linyaps_box/utils/socket.h" #include "linyaps_box/utils/symlink.h" -#include "linyaps_box/utils/touch.h" +#include "linyaps_box/utils/terminal.h" #include #include #include +#include #include #include @@ -40,8 +47,8 @@ #include #include #include +#include #include -#include #ifdef LINYAPS_BOX_ENABLE_CAP #include @@ -188,7 +195,7 @@ void execute_hook(const linyaps_box::config::hooks_t::hook_t &hook) { auto pid = fork(); if (pid < 0) { - throw std::system_error(errno, std::generic_category(), "fork"); + throw std::system_error(errno, std::system_category(), "fork"); } if (pid == 0) { @@ -245,7 +252,7 @@ void execute_hook(const linyaps_box::config::hooks_t::hook_t &hook) continue; } - throw std::system_error(errno, std::generic_category(), "waitpid " + std::to_string(pid)); + throw std::system_error(errno, std::system_category(), "waitpid " + std::to_string(pid)); } if (WIFEXITED(status)) { @@ -259,9 +266,10 @@ void execute_hook(const linyaps_box::config::hooks_t::hook_t &hook) struct clone_fn_args { + int preserve_fds; const linyaps_box::container *container; - const linyaps_box::config::process_t *process; linyaps_box::utils::file_descriptor socket; + std::optional console_socket; }; // NOTE: All function in this namespace are running in the container namespace. @@ -289,14 +297,14 @@ void initialize_container(const linyaps_box::config &config, std::ofstream ofs("/proc/self/oom_score_adj"); if (!ofs) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "failed to open /proc/self/oom_score_adj"); } ofs << score; if (!ofs) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "failed to write to /proc/self/oom_score_adj"); } } @@ -344,7 +352,7 @@ void syscall_mount(const char *_special_file, auto ret = ::mount(_special_file, _dir, _fstype, _rwflag, _data); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "mount"); + throw std::system_error(errno, std::system_category(), "mount"); } } @@ -594,7 +602,7 @@ void do_propagation_mount(const linyaps_box::utils::file_descriptor &destination namespaces->cbegin(), namespaces->cend(), [](const linyaps_box::config::linux_t::namespace_t &ns) -> bool { - return ns.type == linyaps_box::config::linux_t::namespace_t::type_t::CGROUP; + return ns.type_ == linyaps_box::config::linux_t::namespace_t::type::CGROUP; }); if (mount.destination == "/sys/fs/cgroup" && is_sys_rbind) { if (unshared_cgroup_ns != namespaces->cend()) { @@ -683,7 +691,7 @@ class mounter auto parent_fd = ::openat(rootfsfd.get(), "..", O_PATH | O_CLOEXEC); if (parent_fd < 0) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "openat: failed to open " + rootfsfd.current_path().string() + "/.."); } @@ -711,12 +719,12 @@ class mounter return; } - auto unshared_mount_ns = - std::find_if(std::cbegin(*(config.linux->namespaces)), - std::cend(*(config.linux->namespaces)), - [](const linyaps_box::config::linux_t::namespace_t &ns) -> bool { - return ns.type == linyaps_box::config::linux_t::namespace_t::MOUNT; - }); + auto unshared_mount_ns = std::find_if( + std::cbegin(*(config.linux->namespaces)), + std::cend(*(config.linux->namespaces)), + [](const linyaps_box::config::linux_t::namespace_t &ns) -> bool { + return ns.type_ == linyaps_box::config::linux_t::namespace_t::type::MOUNT; + }); if (unshared_mount_ns == std::cend(*(config.linux->namespaces))) { LINYAPS_BOX_DEBUG() << "no unshared mount namespace"; return; @@ -773,7 +781,8 @@ class mounter void mount(const linyaps_box::config::mount_t &mount) { - if ((mount.extra_flags & linyaps_box::config::mount_t::COPY_SYMLINK) != 0) { + if ((mount.extension_flags & linyaps_box::config::mount_t::extension::COPY_SYMLINK) + != linyaps_box::config::mount_t::extension::NONE) { auto ret = create_destination_symlink(root, mount.source.value(), mount.destination.value()); @@ -921,7 +930,7 @@ class mounter int ret = ::statfs(proc.proc_path().c_str(), &buf); if (ret != 0) { - throw std::system_error(errno, std::generic_category(), "statfs"); + throw std::system_error(errno, std::system_category(), "statfs"); } if (buf.f_type == PROC_SUPER_MAGIC) { @@ -941,7 +950,7 @@ class mounter struct statfs buf{}; if (::statfs(sys.proc_path().c_str(), &buf) != 0) { - throw std::system_error(errno, std::generic_category(), "statfs"); + throw std::system_error(errno, std::system_category(), "statfs"); } if (buf.f_type == SYSFS_MAGIC) { @@ -976,7 +985,7 @@ class mounter int ret = ::statfs(dev.proc_path().c_str(), &buf); if (ret != 0) { - throw std::system_error(errno, std::generic_category(), "statfs"); + throw std::system_error(errno, std::system_category(), "statfs"); } if (buf.f_type == TMPFS_MAGIC) { @@ -1105,11 +1114,11 @@ class mounter auto new_dev = linyaps_box::utils::open_at(root, path, O_PATH); path = new_dev.proc_path(); if (chmod(path.c_str(), mode) < 0) { - throw std::system_error(errno, std::generic_category(), "chmod"); + throw std::system_error(errno, std::system_category(), "chmod"); } if (chown(path.c_str(), uid, gid) < 0) { - throw std::system_error(errno, std::generic_category(), "chown"); + throw std::system_error(errno, std::system_category(), "chown"); } } catch (const std::system_error &e) { if (e.code().value() != EPERM) { @@ -1196,8 +1205,9 @@ void configure_mounts(const linyaps_box::container &container, const std::filesy LINYAPS_BOX_DEBUG() << "Mounts configured"; } -[[noreturn]] void execute_process(const linyaps_box::config::process_t &process) +[[noreturn]] void execute_process(const linyaps_box::config &config) { + const auto &process = config.process; std::vector c_args; c_args.reserve(process.args.size()); for (const auto &arg : process.args) { @@ -1214,24 +1224,24 @@ void configure_mounts(const linyaps_box::container &container, const std::filesy auto ret = chdir(process.cwd.c_str()); if (ret != 0) { - throw std::system_error(errno, std::generic_category(), "chdir"); + throw std::system_error(errno, std::system_category(), "chdir"); } ret = setgid(process.user.gid); if (ret != 0) { - throw std::system_error(errno, std::generic_category(), "setgid"); + throw std::system_error(errno, std::system_category(), "setgid"); } if (process.user.additional_gids) { ret = setgroups(process.user.additional_gids->size(), process.user.additional_gids->data()); if (ret != 0) { - throw std::system_error(errno, std::generic_category(), "setgroups"); + throw std::system_error(errno, std::system_category(), "setgroups"); } } ret = setuid(process.user.uid); if (ret != 0) { - throw std::system_error(errno, std::generic_category(), "setuid"); + throw std::system_error(errno, std::system_category(), "setuid"); } LINYAPS_BOX_DEBUG() << "All opened file describers:\n" << linyaps_box::utils::inspect_fds(); @@ -1252,13 +1262,13 @@ void configure_mounts(const linyaps_box::container &container, const std::filesy const_cast(c_args.data()), // NOLINT const_cast(c_env.data())); // NOLINT - throw std::system_error(errno, std::generic_category(), "execvpe"); + throw std::system_error(errno, std::system_category(), "execvpe"); } -void wait_prestart_hooks_result(const linyaps_box::container &container, +void wait_prestart_hooks_result(const linyaps_box::config &config, linyaps_box::utils::file_descriptor &socket) { - if (!container.get_config().hooks.prestart) { + if (!config.hooks.prestart) { return; } @@ -1281,10 +1291,10 @@ void wait_prestart_hooks_result(const linyaps_box::container &container, throw unexpected_sync_message(sync_message::PRESTART_HOOKS_EXECUTED, message); } -void wait_create_runtime_result(const linyaps_box::container &container, +void wait_create_runtime_result(const linyaps_box::config &config, linyaps_box::utils::file_descriptor &socket) { - if (!container.get_config().hooks.create_runtime) { + if (!config.hooks.create_runtime) { return; } @@ -1307,16 +1317,16 @@ void wait_create_runtime_result(const linyaps_box::container &container, throw unexpected_sync_message(sync_message::CREATERUNTIME_HOOKS_EXECUTED, message); } -void create_container_hooks(const linyaps_box::container &container, +void create_container_hooks(const linyaps_box::config &config, linyaps_box::utils::file_descriptor &socket) { - if (!container.get_config().hooks.create_container) { + if (!config.hooks.create_container) { return; } LINYAPS_BOX_DEBUG() << "Execute create container hooks"; - for (const auto &hook : container.get_config().hooks.create_container.value()) { + for (const auto &hook : config.hooks.create_container.value()) { execute_hook(hook); } @@ -1342,19 +1352,19 @@ void do_pivot_root(const linyaps_box::container &container, const std::filesyste auto ret = fchdir(new_root.get()); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "fchdir"); + throw std::system_error(errno, std::system_category(), "fchdir"); } LINYAPS_BOX_DEBUG() << "Pivot root new root: " << linyaps_box::utils::inspect_fd(new_root.get()); ret = syscall(__NR_pivot_root, ".", "."); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "pivot_root"); + throw std::system_error(errno, std::system_category(), "pivot_root"); } ret = fchdir(old_root.get()); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "fchdir"); + throw std::system_error(errno, std::system_category(), "fchdir"); } // make sure that umount event couldn't propagate to host @@ -1363,7 +1373,7 @@ void do_pivot_root(const linyaps_box::container &container, const std::filesyste // umount old root ret = umount2(".", MNT_DETACH); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "umount2"); + throw std::system_error(errno, std::system_category(), "umount2"); } do { @@ -1372,13 +1382,13 @@ void do_pivot_root(const linyaps_box::container &container, const std::filesyste break; } if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "umount2"); + throw std::system_error(errno, std::system_category(), "umount2"); } } while (ret == 0); ret = chdir("/"); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "chdir"); + throw std::system_error(errno, std::system_category(), "chdir"); } // restore the propagation type of rootfs mountpoint @@ -1422,11 +1432,11 @@ security_status get_runtime_security_status() return status; } -void set_capabilities(const linyaps_box::container &container, int last_cap) +void set_capabilities(const linyaps_box::config &config, int last_cap) { #ifdef LINYAPS_BOX_ENABLE_CAP LINYAPS_BOX_DEBUG() << "Set capabilities"; - const auto &capabilities = container.get_config().process.capabilities; + const auto &capabilities = config.process.capabilities; if (last_cap <= 0) { throw std::runtime_error("kernel does not support capabilities"); @@ -1441,7 +1451,7 @@ void set_capabilities(const linyaps_box::container &container, int last_cap) }) == bounding_set.cend()) { if (cap_drop_bound(cap) < 0) { - throw std::system_error(errno, std::generic_category(), "cap_drop_bound"); + throw std::system_error(errno, std::system_category(), "cap_drop_bound"); } } } @@ -1457,7 +1467,7 @@ void set_capabilities(const linyaps_box::container &container, int last_cap) CAP_SET); if (ret < 0) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "failed to set effective capabilities"); } } @@ -1471,7 +1481,7 @@ void set_capabilities(const linyaps_box::container &container, int last_cap) CAP_SET); if (ret < 0) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "failed to set permitted capabilities"); } } @@ -1485,7 +1495,7 @@ void set_capabilities(const linyaps_box::container &container, int last_cap) CAP_SET); if (ret < 0) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "failed to set inheritable capabilities"); } } @@ -1493,59 +1503,59 @@ void set_capabilities(const linyaps_box::container &container, int last_cap) // keep current capabilities, we need these caps on later ret = prctl(PR_SET_KEEPCAPS, 1); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "keep current capabilities"); + throw std::system_error(errno, std::system_category(), "keep current capabilities"); } - const auto &process = container.get_config().process; + const auto &process = config.process; ret = setresuid(process.user.uid, process.user.uid, process.user.uid); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "setresuid"); + throw std::system_error(errno, std::system_category(), "setresuid"); } ret = setresgid(process.user.gid, process.user.gid, process.user.gid); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "setresgid"); + throw std::system_error(errno, std::system_category(), "setresgid"); } ret = cap_set_proc(caps.get()); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "cap_set_proc"); + throw std::system_error(errno, std::system_category(), "cap_set_proc"); } #ifdef PR_CAP_AMBIENT ret = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0L, 0L, 0L); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "cap_ambient_clear_all"); + throw std::system_error(errno, std::system_category(), "cap_ambient_clear_all"); } std::for_each(capabilities.ambient.cbegin(), capabilities.ambient.cend(), [](cap_value_t cap) { auto ret = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0L, 0L); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "cap_ambient_raise"); + throw std::system_error(errno, std::system_category(), "cap_ambient_raise"); } }); #endif #endif - if (container.get_config().process.no_new_privileges) { + if (config.process.no_new_privileges) { LINYAPS_BOX_DEBUG() << "Set no new privileges"; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { - throw std::system_error(errno, std::generic_category(), "prctl"); + throw std::system_error(errno, std::system_category(), "prctl"); } } } -void start_container_hooks(const linyaps_box::container &container, +void start_container_hooks(const linyaps_box::config &config, linyaps_box::utils::file_descriptor &socket) { - if (!container.get_config().hooks.start_container) { + if (!config.hooks.start_container) { return; } LINYAPS_BOX_DEBUG() << "Execute start container hooks"; - for (const auto &hook : container.get_config().hooks.start_container.value()) { + for (const auto &hook : config.hooks.start_container.value()) { execute_hook(hook); } @@ -1576,14 +1586,12 @@ void close_other_fds(std::set except_fds) continue; } - linyaps_box::utils::close_range(low, high, 0); + linyaps_box::utils::close_range(low, high, CLOSE_RANGE_CLOEXEC); } } -void processing_extensions(const linyaps_box::container &container) +void processing_extensions(const linyaps_box::config &config) { - const auto &config = container.get_config(); - if (!config.annotations) { return; } @@ -1624,14 +1632,14 @@ void processing_extensions(const linyaps_box::container &container) std::ofstream ofs(ns_last_pid); if (!ofs) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "failed to open /proc/sys/kernel/ns_last_pid"); } ofs << it->second; if (!ofs) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "failed to write to /proc/sys/kernel/ns_last_pid"); } @@ -1642,17 +1650,49 @@ void processing_extensions(const linyaps_box::container &container) LINYAPS_BOX_DEBUG() << "Container extensions processing completed"; } -void signal_USR1_handler([[maybe_unused]] int sig) +void configure_terminal(const linyaps_box::container &container, + const linyaps_box::unixSocketClient &socket) { - LINYAPS_BOX_DEBUG() << "Signal USR1 received:" << sig; + LINYAPS_BOX_DEBUG() << "Configure terminal"; + const auto &process = container.get_config().process; + + auto [master, slave] = linyaps_box::create_pty_pair(); + + slave.setup_stdio(); + + auto ret = fchown(slave.file_describer().get(), process.user.uid, process.user.gid); + if (ret != 0) { + throw std::system_error(errno, std::system_category(), "fchown"); + } + + if (process.console_size) { + slave.set_size({ process.console_size->height, process.console_size->height, 0, 0 }); + } + + auto root = linyaps_box::utils::open("/", O_PATH | O_CLOEXEC | O_DIRECTORY); + + linyaps_box::config::mount_t mount{}; + mount.source = slave.file_describer().current_path(); + mount.destination = "/dev/console"; + mount.flags = MS_BIND; + + auto dest_fd = do_bind_mount(root, mount); + dest_fd.release(); + + socket.send_fd(std::move(master).take()); } int clone_fn(void *data) noexcept try { if (getenv("LINYAPS_BOX_CONTAINER_PROCESS_TRACE_ME") != nullptr) { + auto signal_USR1_handler = []([[maybe_unused]] int) { + LINYAPS_BOX_DEBUG() << "Signal USR1 received."; + }; + auto ret = signal(SIGUSR1, signal_USR1_handler); + if (ret == SIG_ERR) { - throw std::system_error(errno, std::generic_category(), "signal"); + throw std::system_error(errno, std::system_category(), "signal"); } assert(ret == SIG_DFL); @@ -1662,7 +1702,7 @@ try { ret = signal(SIGUSR1, SIG_DFL); if (ret == SIG_ERR) { - throw std::system_error(errno, std::generic_category(), "signal"); + throw std::system_error(errno, std::system_category(), "signal"); } assert(ret == signal_USR1_handler); } @@ -1677,14 +1717,14 @@ try { STDOUT_FILENO, STDERR_FILENO, }; - for (auto fd = 0; fd < args.container->preserve_fds(); ++fd) { + for (auto fd = 0; fd < args.preserve_fds; ++fd) { except_fds.insert(fd + 3); } except_fds.insert(static_cast(args.socket.get())); close_other_fds(std::move(except_fds)); const auto &container = *args.container; - const auto &process = *args.process; + const auto &config = container.get_config(); auto &socket = args.socket; auto rootfs = container.get_config().root.path; @@ -1696,17 +1736,33 @@ try { initialize_container(container.get_config(), socket); auto [runtime_cap] = get_runtime_security_status(); // get runtime status before pivot root configure_mounts(container, rootfs); - wait_prestart_hooks_result(container, socket); - wait_create_runtime_result(container, socket); - create_container_hooks(container, socket); + wait_prestart_hooks_result(config, socket); + wait_create_runtime_result(config, socket); + create_container_hooks(config, socket); // TODO: selinux label/apparmor profile do_pivot_root(container, rootfs); + + if (args.console_socket) { + linyaps_box::utils::setsid(); + configure_terminal(container, args.console_socket.value()); + } + set_umask(container.get_config().process.user.umask); // processing all extensions before drop capabilities - processing_extensions(container); - set_capabilities(container, runtime_cap); - start_container_hooks(container, socket); - execute_process(process); + processing_extensions(config); + set_capabilities(config, runtime_cap); + + // unblock and reset all signals before we execute the target + sigset_t set; + linyaps_box::utils::sigfillset(set); + linyaps_box::utils::sigprocmask(SIG_UNBLOCK, set, nullptr); + linyaps_box::utils::reset_signals(set); + + start_container_hooks(config, socket); + execute_process(config); +} catch (const std::system_error &e) { + LINYAPS_BOX_ERR() << "clone failed: " << e.what(); + return -1; } catch (const std::exception &e) { LINYAPS_BOX_ERR() << "clone failed: " << e.what(); return -1; @@ -1720,61 +1776,31 @@ try { // NOTE: All function in this namespace are running in the runtime namespace. namespace runtime_ns { -[[nodiscard]] int generate_clone_flag( +[[nodiscard]] unsigned generate_clone_flag( const std::optional> &namespaces) { LINYAPS_BOX_DEBUG() << "Generate clone flags"; - int flag = SIGCHLD; + unsigned flag = SIGCHLD; LINYAPS_BOX_DEBUG() << "Add SIGCHLD, flag=0x" << std::hex << flag; if (!namespaces) { return flag; } - uint32_t setted_namespaces = 0; + linyaps_box::config::linux_t::namespace_t::type setted_namespaces{ + linyaps_box::config::linux_t::namespace_t::type::NONE + }; + for (const auto &ns : *namespaces) { - switch (ns.type) { - case linyaps_box::config::linux_t::namespace_t::IPC: { - flag |= CLONE_NEWIPC; - LINYAPS_BOX_DEBUG() << "Add CLONE_NEWIPC, flag=0x" << std::hex << flag; - } break; - case linyaps_box::config::linux_t::namespace_t::UTS: { - flag |= CLONE_NEWUTS; - LINYAPS_BOX_DEBUG() << "Add CLONE_NEWUTS, flag=0x" << std::hex << flag; - } break; - case linyaps_box::config::linux_t::namespace_t::MOUNT: { - flag |= CLONE_NEWNS; - LINYAPS_BOX_DEBUG() << "Add CLONE_NEWNS, flag=0x" << std::hex << flag; - } break; - case linyaps_box::config::linux_t::namespace_t::PID: { - flag |= CLONE_NEWPID; - LINYAPS_BOX_DEBUG() << "Add CLONE_NEWPID, flag=0x" << std::hex << flag; - } break; - case linyaps_box::config::linux_t::namespace_t::NET: { - flag |= CLONE_NEWNET; - LINYAPS_BOX_DEBUG() << "Add CLONE_NEWNET, flag=0x" << std::hex << flag; - } break; - case linyaps_box::config::linux_t::namespace_t::USER: { - flag |= CLONE_NEWUSER; - LINYAPS_BOX_DEBUG() << "Add CLONE_NEWUSER, flag=0x" << std::hex << flag; - } break; - case linyaps_box::config::linux_t::namespace_t::CGROUP: { - flag |= CLONE_NEWCGROUP; - LINYAPS_BOX_DEBUG() << "Add CLONE_NEWCGROUP, flag=0x" << std::hex << flag; - } break; - case linyaps_box::config::linux_t::namespace_t::INVALID: { - throw std::invalid_argument("invalid namespace type: " + std::to_string(ns.type)); - } break; - default: { - throw std::invalid_argument("unknown namespace type: " + std::to_string(ns.type)); - } - } - - if ((setted_namespaces & ns.type) != 0) { + flag |= static_cast(ns.type_); + LINYAPS_BOX_DEBUG() << "Add " << to_string(ns.type_) << " , flag=0x" << std::hex << flag; + + if ((setted_namespaces & ns.type_) + != linyaps_box::config::linux_t::namespace_t::type::NONE) { throw std::invalid_argument("duplicate namespace"); } - setted_namespaces |= ns.type; + setted_namespaces |= ns.type_; } LINYAPS_BOX_DEBUG() << "Clone flag=0x" << std::hex << flag; @@ -1830,25 +1856,27 @@ void set_rlimits(const linyaps_box::config::process_t::rlimits_t &rlimits) std::for_each(rlimits.begin(), rlimits.end(), [](const linyaps_box::config::process_t::rlimit_t &rlimit) { - struct rlimit rl{ rlimit.soft, rlimit.hard }; + const struct rlimit rl{ rlimit.soft, rlimit.hard }; auto resource = linyaps_box::utils::str_to_rlimit(rlimit.type); LINYAPS_BOX_DEBUG() << "Set rlimit " << rlimit.type << ": Soft=" << rlimit.soft << ", Hard=" << rlimit.hard; if (setrlimit(resource, &rl) == -1) { - throw std::system_error(errno, std::generic_category(), "setrlimit"); + throw std::system_error(errno, std::system_category(), "setrlimit"); } }); } std::tuple start_container_process( - const linyaps_box::container &container, const linyaps_box::config::process_t &process) + const linyaps_box::container &container, linyaps_box::run_container_options_t &options) { - LINYAPS_BOX_DEBUG() << "All opened file describers before socketpair:\n" + const auto &config = container.get_config(); + LINYAPS_BOX_DEBUG() << "All opened file describers before open sockets:\n" << linyaps_box::utils::inspect_fds(); + auto sockets = linyaps_box::utils::socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); - LINYAPS_BOX_DEBUG() << "All opened file describers after socketpair:\n" + + LINYAPS_BOX_DEBUG() << "All opened file describers after open sockets:\n" << linyaps_box::utils::inspect_fds(); - const auto &config = container.get_config(); // config rlimits before we enter new user namespace if (const auto &rlimits = config.process.rlimits; rlimits) { @@ -1861,13 +1889,17 @@ std::tuple start_container_process( } const int clone_flag = runtime_ns::generate_clone_flag(namespaces); - clone_fn_args args = { &container, &process, std::move(sockets.second) }; + clone_fn_args args = { options.preserve_fds, + &container, + std::move(sockets.second), + std::move(options.console_socket) }; LINYAPS_BOX_DEBUG() << "OCI runtime in runtime namespace: PID=" << getpid() << " PIDNS=" << linyaps_box::utils::get_pid_namespace(); const child_stack stack; - const int child_pid = clone(container_ns::clone_fn, stack.top(), clone_flag, (void *)&args); + const int child_pid = + clone(container_ns::clone_fn, stack.top(), clone_flag, static_cast(&args)); if (child_pid < 0) { throw std::runtime_error("clone failed"); } @@ -1904,7 +1936,7 @@ std::tuple start_container_process( auto pid = fork(); if (pid < 0) { - throw std::system_error(errno, std::generic_category(), "fork"); + throw std::system_error(errno, std::system_category(), "fork"); } if (pid == 0) { @@ -1933,7 +1965,7 @@ std::tuple start_container_process( continue; } - throw std::system_error(errno, std::generic_category(), "waitpid"); + throw std::system_error(errno, std::system_category(), "waitpid"); } if (WIFEXITED(status)) { @@ -2018,7 +2050,7 @@ void configure_gid_mapping(pid_t pid, const linyaps_box::container &container) } if (ret != ENOENT) { - throw std::system_error(ret, std::generic_category(), "newgidmap"); + throw std::system_error(ret, std::system_category(), "newgidmap"); } // maybe we have CAP_SETGID? @@ -2042,7 +2074,7 @@ void configure_gid_mapping(pid_t pid, const linyaps_box::container &container) } throw std::system_error{ errno, - std::generic_category(), + std::system_category(), "write to " + file.current_path().string() }; } @@ -2101,7 +2133,7 @@ void configure_uid_mapping(pid_t pid, const linyaps_box::container &container) } if (ret != ENOENT) { - throw std::system_error(ret, std::generic_category(), "newuidmap"); + throw std::system_error(ret, std::system_category(), "newuidmap"); } // try to write mapping directly, maybe we have CAP_SETUID? @@ -2125,7 +2157,7 @@ void configure_uid_mapping(pid_t pid, const linyaps_box::container &container) } throw std::system_error{ errno, - std::generic_category(), + std::system_category(), "write to " + file.current_path().string() }; } @@ -2161,7 +2193,8 @@ void configure_container_namespaces(const linyaps_box::container &container, if (std::find_if(namespaces->cbegin(), namespaces->cend(), [](const linyaps_box::config::linux_t::namespace_t &ns) -> bool { - return ns.type == linyaps_box::config::linux_t::namespace_t::USER; + return ns.type_ + == linyaps_box::config::linux_t::namespace_t::type::USER; }) != namespaces->end()) { auto pid = container.status().PID; @@ -2267,6 +2300,13 @@ void wait_create_container_result(const linyaps_box::container &container, throw unexpected_sync_message(sync_message::CREATECONTAINER_HOOKS_EXECUTED, message); } +auto recv_master_tty(const linyaps_box::unixSocketClient &socket) -> linyaps_box::terminal_master +{ + std::string payload; + auto ret = socket.recv_fd(payload); + return linyaps_box::terminal_master{ std::move(ret) }; +} + void wait_socket_close(linyaps_box::utils::file_descriptor &socket) try { LINYAPS_BOX_DEBUG() << "All opened file describers:\n" << linyaps_box::utils::inspect_fds(); @@ -2304,25 +2344,134 @@ void poststop_hooks(const linyaps_box::container &container) noexcept } } -[[nodiscard]] int wait_container_process(pid_t pid) +struct wait_process_ctx { - int status = 0; + pid_t pid{ -1 }; + std::optional master; + std::optional host_tty; + linyaps_box::utils::file_descriptor in; + linyaps_box::utils::file_descriptor out; +}; - pid_t ret = -1; - while (ret == -1) { - ret = waitpid(pid, &status, 0); +[[nodiscard]] int wait_container_process(wait_process_ctx ctx) +{ + LINYAPS_BOX_DEBUG() << "Wait container process: " << ctx.pid; - if (ret != -1) { - break; + sigset_t set; + linyaps_box::utils::sigfillset(set); + linyaps_box::utils::sigprocmask(SIG_BLOCK, set, nullptr); + auto signalfd = linyaps_box::utils::create_signalfd(set); + + int exit_code{ 0 }; + bool child_exited{ false }; + + // try to reap child process. Child process may exit before we create signalfd and it wouldn't + // receive SIGCHLD anymore. If we don't do this, the child process (or its children) will be + // zombie process + auto ret = linyaps_box::utils::waitpid(ctx.pid, WNOHANG); + + if (ret.status == linyaps_box::utils::WaitStatus::Reaped) { + child_exited = true; + exit_code = WIFSIGNALED(ret.exit_code) ? 128 + WTERMSIG(ret.exit_code) + : WEXITSTATUS(ret.exit_code); + } else if (ret.status == linyaps_box::utils::WaitStatus::NoChild) { + throw std::runtime_error("target pid " + std::to_string(ctx.pid) + " is not a child"); + } + + linyaps_box::io::Epoll epoll; + auto signalfd_pollable = epoll.add(signalfd, EPOLLIN); + assert(signalfd_pollable); + if (UNLIKELY(!signalfd_pollable)) { + throw std::runtime_error("failed to add signalfd to epoll"); + } + + constexpr auto buffer_size = 256 * 1024; + std::optional master_out; + std::optional in_fwd; + std::optional out_fwd; + + if (ctx.master) { + master_out = ctx.master->get().duplicate(); + master_out->set_nonblock(true); + + if (!child_exited) { + in_fwd.emplace(epoll, buffer_size); + in_fwd->set_src(ctx.in); + in_fwd->set_dst(ctx.master->get()); } - if (errno == EINTR || errno == EAGAIN) { - continue; + out_fwd.emplace(epoll, buffer_size); + out_fwd->set_src(master_out.value()); + out_fwd->set_dst(ctx.out); + } + + auto handle_signal = [&] { + struct signalfd_siginfo info{}; + if (signalfd.read(info) != linyaps_box::unixSocketClient::IOStatus::Success) { + return; + } + + if (info.ssi_signo == SIGCHLD) { + auto res = linyaps_box::utils::waitpid(ctx.pid, WNOHANG); + if (res.status == linyaps_box::utils::WaitStatus::Reaped) { + child_exited = true; + in_fwd.reset(); + exit_code = WIFSIGNALED(res.exit_code) ? 128 + WTERMSIG(res.exit_code) + : WEXITSTATUS(res.exit_code); + } + } else if (info.ssi_signo == SIGWINCH && ctx.master) { + ctx.master->resize(ctx.host_tty->get_size()); + } else if (!child_exited) { + kill(ctx.pid, info.ssi_signo); + } + }; + + while (!child_exited || out_fwd) { + bool keep_pumping{ false }; + + if (in_fwd) { + auto s = in_fwd->handle_forwarding(); + if (s == linyaps_box::io::Forwarder::Status::Finished) { + in_fwd.reset(); + } else if (s == linyaps_box::io::Forwarder::Status::Busy + && !in_fwd->is_src_pollable()) { + keep_pumping = true; + } + } + + if (out_fwd) { + auto s = out_fwd->handle_forwarding(); + if (s == linyaps_box::io::Forwarder::Status::Finished) { + out_fwd.reset(); + } else if (s == linyaps_box::io::Forwarder::Status::Busy + && !out_fwd->is_dst_pollable()) { + keep_pumping = true; + } + } + + const auto &events = epoll.wait(keep_pumping ? 0 : -1); + for (const auto &ev : events) { + if (ev.data.fd == signalfd.get()) { + handle_signal(); + if (child_exited) { + break; + } + + continue; + } } - throw std::system_error(errno, std::generic_category(), "waitpid"); + if (child_exited && out_fwd) { + auto status = out_fwd->handle_forwarding(); + if (status == linyaps_box::io::Forwarder::Status::Finished) { + out_fwd.reset(); + } else if (status == linyaps_box::io::Forwarder::Status::Busy) { + keep_pumping = true; + } + } } - return WEXITSTATUS(status); + + return exit_code; } } // namespace runtime_ns @@ -2332,7 +2481,6 @@ void poststop_hooks(const linyaps_box::container &container) noexcept linyaps_box::container::container(const status_directory &status_dir, const create_container_options_t &options) : container_ref(status_dir, options.ID) - , preserve_fds_(options.preserve_fds) , data(new linyaps_box::container_data) , bundle(options.bundle) { @@ -2356,7 +2504,7 @@ linyaps_box::container::container(const status_directory &status_dir, #ifndef LINYAPS_BOX_STATIC_LINK auto *pw = getpwuid(host_uid_); if (pw == nullptr) { - throw std::system_error(errno, std::generic_category(), "getpwuid"); + throw std::system_error(errno, std::system_category(), "getpwuid"); } #endif { @@ -2401,16 +2549,32 @@ const std::filesystem::path &linyaps_box::container::get_bundle() const } // maybe we need a internal run function? -int linyaps_box::container::run(const config::process_t &process) const +int linyaps_box::container::run(run_container_options_t options) const { int container_process_exit_code{ -1 }; + + utils::prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); + try { // TODO: there are some thing that should be done before starting the container process // e.g. do something before creating cgroup by selecting manager, selinux label, seccomp // setup, etc. + std::optional recv_socketpair; + if (config.process.terminal && !options.console_socket) { + auto [socket1, socket2] = utils::socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + options.console_socket = unixSocketClient{ std::move(socket1) }; + recv_socketpair = unixSocketClient{ std::move(socket2) }; + } + + // block all signals so that we can't be interrupted + sigset_t set; + utils::sigfillset(set); + sigdelset(&set, SIGUSR1); // for debug + utils::sigprocmask(SIG_BLOCK, set, nullptr); + // TODO: cgroup preenter - auto [child_pid, socket] = runtime_ns::start_container_process(*this, process); + auto [child_pid, socket] = runtime_ns::start_container_process(*this, options); { auto status = this->status(); @@ -2424,6 +2588,17 @@ int linyaps_box::container::run(const config::process_t &process) const runtime_ns::prestart_hooks(*this, socket); runtime_ns::create_runtime_hooks(*this, socket); runtime_ns::wait_create_container_result(*this, socket); + + auto master = [&recv_socketpair]() -> std::optional { + if (!recv_socketpair) { + return std::nullopt; + } + + auto ret = runtime_ns::recv_master_tty(recv_socketpair.value()); + recv_socketpair->release(); + return ret; + }(); + runtime_ns::wait_socket_close(socket); { @@ -2438,7 +2613,35 @@ int linyaps_box::container::run(const config::process_t &process) const // TODO: support detach from the parent's process // Now we wait for the container process to exit - container_process_exit_code = runtime_ns::wait_container_process(this->status().PID); + runtime_ns::wait_process_ctx ctx; + ctx.pid = this->status().PID; + + if (master) { + LINYAPS_BOX_DEBUG() << "Container requires a terminal"; + + master->get().set_nonblock(true); + + ctx.in = utils::file_descriptor{ utils::fileno(stdin), false }; + ctx.in.set_nonblock(true); + + ctx.out = utils::file_descriptor{ utils::fileno(stdout), false }; + ctx.out.set_nonblock(true); + + if (utils::isatty(ctx.in)) { + ctx.host_tty = linyaps_box::terminal_slave{ ctx.in.duplicate() }; + ctx.host_tty->set_raw(); + } else if (utils::isatty(ctx.out)) { + ctx.host_tty = linyaps_box::terminal_slave{ ctx.out.duplicate() }; + ctx.host_tty->set_raw(); + } else { + auto default_tty = utils::open("/dev/tty", O_RDWR | O_CLOEXEC); + ctx.host_tty = linyaps_box::terminal_slave{ std::move(default_tty) }; + } + } + + ctx.master = std::move(master); + + container_process_exit_code = runtime_ns::wait_container_process(std::move(ctx)); runtime_ns::poststop_hooks(*this); } catch (const std::system_error &e) { diff --git a/src/linyaps_box/container.h b/src/linyaps_box/container.h index 5a8c638..fd2249d 100644 --- a/src/linyaps_box/container.h +++ b/src/linyaps_box/container.h @@ -7,6 +7,7 @@ #include "linyaps_box/cgroup_manager.h" #include "linyaps_box/container_ref.h" #include "linyaps_box/status_directory.h" +#include "linyaps_box/unixsocket.h" #include "linyaps_box/utils/file_describer.h" namespace linyaps_box { @@ -16,12 +17,17 @@ struct container_data; struct create_container_options_t { cgroup_manager_t manager; - int preserve_fds; std::string ID; std::filesystem::path bundle; std::filesystem::path config; }; +struct run_container_options_t +{ + int preserve_fds; + std::optional console_socket; +}; + class container final : public container_ref { public: @@ -34,7 +40,7 @@ class container final : public container_ref [[nodiscard]] auto get_config() const -> const linyaps_box::config &; [[nodiscard]] auto get_bundle() const -> const std::filesystem::path &; - [[nodiscard]] auto run(const config::process_t &process) const -> int; + [[nodiscard]] auto run(run_container_options_t options) const -> int; // TODO:: support fully container capabilities, e.g. create, start, stop, delete... friend auto get_private_data(const container &c) noexcept -> container_data &; ~container() noexcept override; @@ -43,11 +49,8 @@ class container final : public container_ref [[nodiscard]] auto host_uid() const noexcept { return host_uid_; } - [[nodiscard]] auto preserve_fds() const noexcept { return preserve_fds_; } - private: void cgroup_preenter(const cgroup_options &options, utils::file_descriptor &dirfd); - int preserve_fds_; gid_t host_gid_; uid_t host_uid_; container_data *data{ nullptr }; diff --git a/src/linyaps_box/container_ref.cpp b/src/linyaps_box/container_ref.cpp index 45a0696..a96fa44 100644 --- a/src/linyaps_box/container_ref.cpp +++ b/src/linyaps_box/container_ref.cpp @@ -35,7 +35,7 @@ void linyaps_box::container_ref::kill(int signal) const std::stringstream ss; ss << "failed to kill process " << pid << " with signal " << signal; - throw std::system_error(errno, std::generic_category(), std::move(ss).str()); + throw std::system_error(errno, std::system_category(), std::move(ss).str()); } void linyaps_box::container_ref::exec(const linyaps_box::config::process_t &process) @@ -49,6 +49,7 @@ void linyaps_box::container_ref::exec(const linyaps_box::config::process_t &proc "--user", "--mount", "--pid", + "--no-fork", // FIXME: // Old nsenter command do not support --wdns, // so we have to implement nsenter by ourself in the future. @@ -103,7 +104,7 @@ void linyaps_box::container_ref::exec(const linyaps_box::config::process_t &proc ss << " " << arg; } - throw std::system_error(errno, std::generic_category(), std::move(ss).str()); + throw std::system_error(errno, std::system_category(), std::move(ss).str()); } const linyaps_box::status_directory &linyaps_box::container_ref::status_dir() const diff --git a/src/linyaps_box/io/epoll.cpp b/src/linyaps_box/io/epoll.cpp new file mode 100644 index 0000000..8f5bf79 --- /dev/null +++ b/src/linyaps_box/io/epoll.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/io/epoll.h" + +#include "linyaps_box/utils/epoll.h" + +namespace linyaps_box::io { + +Epoll::Epoll(linyaps_box::utils::file_descriptor &&fd) + : epoll_fd{ std::move(fd) } +{ + events_buffer.resize(10); +} + +Epoll::Epoll(bool close_on_exec) + : epoll_fd{ linyaps_box::utils::epoll_create1(close_on_exec ? EPOLL_CLOEXEC : 0) } +{ +} + +auto Epoll::add(const utils::file_descriptor &fd, uint32_t events) -> bool +{ + struct epoll_event event{}; + event.events = events; + event.data.fd = fd.get(); + + try { + linyaps_box::utils::epoll_ctl(epoll_fd, + linyaps_box::utils::epoll_operation::add, + fd, + &event); + } catch (std::system_error &e) { + if (e.code().value() == EPERM) { + return false; + } + + throw; + } + + if (events_buffer.size() == events_buffer.capacity()) { + events_buffer.reserve(events_buffer.capacity() * 2); + } + + events_buffer.resize(events_buffer.size() + 1); + return true; +} + +void Epoll::modify(const utils::file_descriptor &fd, uint32_t events) +{ + struct epoll_event event{}; + event.events = events; + event.data.fd = fd.get(); + + linyaps_box::utils::epoll_ctl(epoll_fd, + linyaps_box::utils::epoll_operation::modify, + fd, + &event); +} + +void Epoll::remove(const utils::file_descriptor &fd) +{ + linyaps_box::utils::epoll_ctl(epoll_fd, + linyaps_box::utils::epoll_operation::remove, + fd, + nullptr); +} + +auto Epoll::wait(int timeout) -> const std::vector & +{ + auto nevents = linyaps_box::utils::epoll_wait(epoll_fd, events_buffer, timeout); + events_buffer.resize(nevents); + return events_buffer; +} + +} // namespace linyaps_box::io diff --git a/src/linyaps_box/io/epoll.h b/src/linyaps_box/io/epoll.h new file mode 100644 index 0000000..4182bd5 --- /dev/null +++ b/src/linyaps_box/io/epoll.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/utils/file_describer.h" + +#include + +#include + +namespace linyaps_box::io { + +class Epoll +{ +public: + explicit Epoll(bool close_on_exec = true); + explicit Epoll(linyaps_box::utils::file_descriptor &&fd); + ~Epoll() = default; + Epoll(const Epoll &) = delete; + Epoll &operator=(const Epoll &) = delete; + Epoll(Epoll &&) = default; + Epoll &operator=(Epoll &&) = default; + + // false if fd doesn't support epoll + [[nodiscard]] auto add(const utils::file_descriptor &fd, uint32_t events) -> bool; + auto modify(const utils::file_descriptor &fd, uint32_t events) -> void; + auto remove(const utils::file_descriptor &fd) -> void; + auto wait(int timeout) -> const std::vector &; + +private: + std::vector events_buffer; + linyaps_box::utils::file_descriptor epoll_fd; +}; + +} // namespace linyaps_box::io diff --git a/src/linyaps_box/io/forwarder.cpp b/src/linyaps_box/io/forwarder.cpp new file mode 100644 index 0000000..279bfa1 --- /dev/null +++ b/src/linyaps_box/io/forwarder.cpp @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/io/forwarder.h" + +#include "linyaps_box/utils/log.h" +#include "linyaps_box/utils/utils.h" + +namespace linyaps_box::io { + +Forwarder::Forwarder(Epoll &poller, std::size_t buffer_size) + : poller(poller) + , rb(utils::ring_buffer::create(buffer_size)) +{ +} + +auto Forwarder::set_src(const utils::file_descriptor &src) -> void +{ + src_.fd = &src; + const auto ev = EPOLLIN | EPOLLET; + + src_.pollable = poller.get().add(*src_.fd, ev); + src_.last_events = src_.pollable ? ev : 0; + + LINYAPS_BOX_DEBUG() << "Forwarder: Source fd: " << src_.fd->get() + << ", pollable: " << src_.pollable; +} + +auto Forwarder::set_dst(const utils::file_descriptor &dst) -> void +{ + dst_.fd = &dst; + const auto ev = EPOLLOUT | EPOLLET; + + dst_.pollable = poller.get().add(*dst_.fd, ev); + dst_.last_events = dst_.pollable ? ev : 0; + + LINYAPS_BOX_DEBUG() << "Forwarder: Destination fd: " << dst_.fd->get() + << ", pollable: " << dst_.pollable; +} + +auto Forwarder::handle_forwarding() -> Status +{ + if (UNLIKELY((src_.fd == nullptr) || (dst_.fd == nullptr))) { + throw std::runtime_error("src or dst is not set"); + } + + bool moved{ false }; + while (!rb->empty()) { + auto vecs = rb->get_read_vecs(); + const auto iov_cnt = (vecs[1].iov_len > 0) ? 2U : 1U; + + std::size_t bytes_written{ 0 }; + auto status = dst_.fd->write_vecs({ vecs.data(), iov_cnt }, bytes_written); + + if (bytes_written > 0) { + rb->advance_head(bytes_written); + moved = true; + } + + if (status == utils::file_descriptor::IOStatus::Success) { + if (bytes_written == 0) { + break; + } + + const std::size_t requested = vecs[0].iov_len + vecs[1].iov_len; + if (bytes_written < requested) { + break; + } + + continue; + } + + if (status == utils::file_descriptor::IOStatus::Closed) { + return Status::Finished; + } + + break; + } + + if (!src_eof) { + while (!rb->full()) { + auto vecs = rb->get_write_vecs(); + const auto iov_cnt = (vecs[1].iov_len > 0) ? 2U : 1U; + + std::size_t bytes_read{ 0 }; + auto status = src_.fd->read_vecs({ vecs.data(), iov_cnt }, bytes_read); + + if (bytes_read > 0) { + rb->advance_tail(bytes_read); + moved = true; + } + + if (status == utils::file_descriptor::IOStatus::Eof + || status == utils::file_descriptor::IOStatus::Closed) { + src_eof = true; + break; + } + + if (status == utils::file_descriptor::IOStatus::Success) { + if (bytes_read == 0) { + break; + } + + const std::size_t requested = vecs[0].iov_len + vecs[1].iov_len; + if (bytes_read < requested) { + break; + } + + continue; + } + + if (status == utils::file_descriptor::IOStatus::TryAgain) { + break; + } + } + } + + update_interests(); + + if (src_eof) { + return rb->empty() ? Status::Finished : Status::SourceClosed; + } + + return moved ? Status::Busy : Status::Idle; +} + +auto Forwarder::update_interests() -> void +{ + auto update = [this](FdContext &ctx, uint32_t next) { + if (!ctx.pollable) { + return; + } + + if (ctx.last_events != next) { + this->poller.get().modify(*ctx.fd, next); + ctx.last_events = next; + } + }; + + update(src_, (src_eof || rb->full()) ? 0 : (EPOLLIN | EPOLLET)); + update(dst_, rb->empty() ? 0 : (EPOLLOUT | EPOLLET)); +} + +Forwarder::~Forwarder() noexcept +{ + try { + if (src_.fd != nullptr && src_.pollable) { + this->poller.get().remove(*src_.fd); + } + } catch (std::system_error &e) { + LINYAPS_BOX_ERR() << "Failed to remove source fd " << src_.fd->get() + << " from epoll: " << e.what(); + } + + try { + if (dst_.fd != nullptr && dst_.pollable) { + this->poller.get().remove(*dst_.fd); + } + } catch (std::system_error &e) { + LINYAPS_BOX_ERR() << "Failed to remove destination fd " << dst_.fd->get() + << " from epoll: " << e.what(); + } +} + +} // namespace linyaps_box::io diff --git a/src/linyaps_box/io/forwarder.h b/src/linyaps_box/io/forwarder.h new file mode 100644 index 0000000..9e5c359 --- /dev/null +++ b/src/linyaps_box/io/forwarder.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/io/epoll.h" +#include "linyaps_box/utils/file_describer.h" +#include "linyaps_box/utils/ringbuffer.h" + +namespace linyaps_box::io { + +class Forwarder +{ +public: + enum class Status : uint8_t { Busy, Idle, SourceClosed, Finished }; + + explicit Forwarder(Epoll &poller, std::size_t buffer_size = BUFSIZ); + + Forwarder(const Forwarder &) = delete; + Forwarder &operator=(const Forwarder &) = delete; + Forwarder(Forwarder &&) = delete; + Forwarder &operator=(Forwarder &&) = delete; + + ~Forwarder() noexcept; + + auto set_src(const utils::file_descriptor &src) -> void; + + auto set_dst(const utils::file_descriptor &dst) -> void; + + [[nodiscard]] auto is_src_pollable() const noexcept -> bool { return src_.pollable; }; + + [[nodiscard]] auto is_dst_pollable() const noexcept -> bool { return dst_.pollable; } + + [[nodiscard]] auto handle_forwarding() -> Status; + +private: + struct FdContext + { + const utils::file_descriptor *fd{ nullptr }; + uint32_t last_events{ std::numeric_limits::max() }; + bool pollable{ false }; + }; + + void update_interests(); + + bool src_eof{ false }; + std::reference_wrapper poller; + FdContext src_; + FdContext dst_; + utils::ring_buffer_ptr rb; +}; + +} // namespace linyaps_box::io diff --git a/src/linyaps_box/terminal.cpp b/src/linyaps_box/terminal.cpp new file mode 100644 index 0000000..77d7f57 --- /dev/null +++ b/src/linyaps_box/terminal.cpp @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/terminal.h" + +#include "linyaps_box/utils/file.h" +#include "linyaps_box/utils/ioctl.h" +#include "linyaps_box/utils/log.h" +#include "linyaps_box/utils/terminal.h" + +#include + +#include + +namespace linyaps_box { + +auto create_pty_pair() -> std::pair +{ + // let the container process control the terminal instead of OCI Runtime + auto master = utils::open("/dev/ptmx", O_RDWR | O_CLOEXEC | O_NOCTTY); + + auto pts = utils::ptsname(master); + unlockpt(master); + auto slave = utils::open(pts, O_RDWR | O_CLOEXEC); + + return { terminal_master{ std::move(master) }, terminal_slave{ std::move(slave) } }; +} + +auto terminal_master::resize(struct winsize size) -> void +{ + utils::ioctl(master_, TIOCSWINSZ, &size); +} + +terminal_slave::terminal_slave(terminal_slave &&other) noexcept + : termios(std::exchange(other.termios, std::nullopt)) + , slave_(std::move(other.slave_)) +{ +} + +terminal_slave &terminal_slave::operator=(terminal_slave &&other) noexcept +{ + if (this == &other) { + return *this; + } + + termios = std::exchange(other.termios, std::nullopt); + slave_ = std::move(other.slave_); + + return *this; +} + +auto terminal_slave::setup_stdio() -> void +{ + LINYAPS_BOX_DEBUG() << "Setup stdio"; + slave_.duplicate_to(utils::fileno(stdin), 0); + slave_.duplicate_to(utils::fileno(stdout), 0); + slave_.duplicate_to(utils::fileno(stderr), 0); + utils::ioctl(slave_, TIOCSCTTY, 0); +} + +auto terminal_slave::set_size(struct winsize size) -> void +{ + utils::ioctl(slave_, TIOCSWINSZ, &size); +} + +auto terminal_slave::set_raw() -> void +{ + if (termios) { + return; + } + + LINYAPS_BOX_DEBUG() << "Set terminal " << slave_.get() << " to raw mode"; + + struct termios orig_term{}; + utils::tcgetattr(slave_, orig_term); + + auto raw = orig_term; + ::cfmakeraw(&raw); + + utils::tcsetattr(slave_, TCSANOW, raw); + + termios = orig_term; +} + +auto terminal_slave::get_size() -> struct winsize +{ + struct winsize size{}; + utils::ioctl(slave_, TIOCGWINSZ, &size); + return size; +} + +terminal_slave::~terminal_slave() noexcept + +try { + if (termios && slave_.valid()) { + utils::tcsetattr(slave_, TCSANOW, termios.value()); + } +} catch (std::system_error &e) { + LINYAPS_BOX_ERR() << "Failed to restore terminal:" << e.what(); +} catch (std::exception &e) { + LINYAPS_BOX_ERR() << "Failed to restore terminal:" << e.what(); +} catch (...) { + LINYAPS_BOX_ERR() << "Failed to restore terminal: unknown exception"; +} + +} // namespace linyaps_box diff --git a/src/linyaps_box/terminal.h b/src/linyaps_box/terminal.h new file mode 100644 index 0000000..da01a7b --- /dev/null +++ b/src/linyaps_box/terminal.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/utils/file_describer.h" + +#include + +#include + +#include + +namespace linyaps_box { + +class terminal_master +{ +public: + explicit terminal_master(utils::file_descriptor master) + : master_(std::move(master)) { }; + + ~terminal_master() noexcept = default; + + terminal_master(terminal_master &&) noexcept = default; + terminal_master &operator=(terminal_master &&) noexcept = default; + + terminal_master(const terminal_master &) = delete; + terminal_master &operator=(const terminal_master &) = delete; + + [[nodiscard]] auto take() && -> utils::file_descriptor { return std::move(master_); } + + [[nodiscard]] auto get() const & noexcept -> const utils::file_descriptor & { return master_; } + + [[nodiscard]] auto get() & noexcept -> utils::file_descriptor & { return master_; } + + auto resize(struct winsize size) -> void; + +private: + utils::file_descriptor master_; +}; + +class terminal_slave +{ +public: + explicit terminal_slave(utils::file_descriptor slave) + : slave_(std::move(slave)) { }; + + ~terminal_slave() noexcept; + + terminal_slave(terminal_slave &&) noexcept; + terminal_slave &operator=(terminal_slave &&) noexcept; + + terminal_slave(const terminal_slave &) = delete; + terminal_slave &operator=(const terminal_slave &) = delete; + + auto setup_stdio() -> void; + + auto set_size(struct winsize size) -> void; + + auto get_size() -> struct winsize; + + [[nodiscard]] auto take() && -> utils::file_descriptor { return std::move(slave_); } + + [[nodiscard]] auto file_describer() const & noexcept -> const utils::file_descriptor & + { + return slave_; + } + + auto set_raw() -> void; + +private: + std::optional termios; + utils::file_descriptor slave_; +}; + +auto create_pty_pair() -> std::pair; + +} // namespace linyaps_box diff --git a/src/linyaps_box/unixsocket.cpp b/src/linyaps_box/unixsocket.cpp new file mode 100644 index 0000000..44f802f --- /dev/null +++ b/src/linyaps_box/unixsocket.cpp @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/unixsocket.h" + +#include "linyaps_box/utils/file.h" +#include "linyaps_box/utils/inspect.h" +#include "linyaps_box/utils/log.h" +#include "linyaps_box/utils/socket.h" + +#include +#include + +namespace linyaps_box { + +unixSocketClient::unixSocketClient(linyaps_box::utils::file_descriptor socket) + : linyaps_box::utils::file_descriptor(std::move(socket)) +{ + if (auto type = this->type(); type != std::filesystem::file_type::socket) { + auto msg = std::string{ "expected socket, got " }; + msg.append(linyaps_box::utils::to_string(type)); + throw std::runtime_error(msg); + } +} + +unixSocketClient unixSocketClient::connect(const std::filesystem::path &path) +{ + auto fd = linyaps_box::utils::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + fd.set_nonblock(true); + + struct sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + auto str = path.string(); + if (str.length() >= sizeof(addr.sun_path)) { + throw std::system_error(ENAMETOOLONG, std::system_category(), "path too long"); + } + + // TODO: add timeout + std::copy(str.cbegin(), str.cend(), addr.sun_path); + utils::connect(fd, reinterpret_cast(&addr), sizeof(addr)); + + return unixSocketClient(std::move(fd)); +} + +// TODO: support request/response +// https://github.com/opencontainers/runtime-tools/blob/v0.9.0/docs/command-line-interface.md#console-socket +auto unixSocketClient::send_fd(utils::file_descriptor &&fd, std::string_view payload) const -> void +{ + if (!fd.valid()) { + throw std::runtime_error("invalid file descriptor"); + } + + LINYAPS_BOX_DEBUG() << "Send fd \n" + << utils::inspect_fd(fd.get()) << "\n to socket \n" + << utils::inspect_fd(this->get()); + + const auto raw_fd = fd.get(); + alignas(struct cmsghdr) std::array ctrl_buf{}; + + std::byte placeholder{ 0 }; + struct iovec iov{}; + if (payload.empty()) { + iov.iov_base = &placeholder; + iov.iov_len = sizeof(placeholder); + } else { + iov.iov_base = const_cast(payload.data()); + iov.iov_len = payload.size(); + } + + struct msghdr msg{}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = ctrl_buf.data(); + msg.msg_controllen = ctrl_buf.size(); + + auto *ctrl_msg = CMSG_FIRSTHDR(&msg); + ctrl_msg->cmsg_level = SOL_SOCKET; + ctrl_msg->cmsg_type = SCM_RIGHTS; + ctrl_msg->cmsg_len = CMSG_LEN(sizeof(int)); + + std::memcpy(CMSG_DATA(ctrl_msg), &raw_fd, sizeof(raw_fd)); + msg.msg_controllen = ctrl_msg->cmsg_len; + + while (true) { + auto ret = ::sendmsg(get(), &msg, 0); + if (ret < 0) { + if (errno == EINTR) { + continue; + } + + throw std::system_error(errno, std::system_category(), "sendmsg"); + } + + break; + } +} + +auto unixSocketClient::recv_fd(std::string &payload) const -> utils::file_descriptor +{ + LINYAPS_BOX_DEBUG() << "Receive fd from socket \n" << utils::inspect_fd(this->get()); + + constexpr auto batch_size = 4096; + payload.clear(); + payload.reserve(batch_size); + + struct msghdr msg{}; + struct iovec iov{ payload.data(), batch_size }; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + alignas(struct cmsghdr) std::array cmsg_buf{}; + msg.msg_control = cmsg_buf.data(); + msg.msg_controllen = cmsg_buf.size(); + + ssize_t len{ 0 }; + while (true) { + len = ::recvmsg(get(), &msg, 0); + if (len > 0) { + break; + } + + if (len == 0) { + throw std::runtime_error("Socket closed by peer"); + } + + if (errno == EINTR) { + continue; + } + + throw std::system_error(errno, std::system_category(), "recvmsg"); + } + + payload.resize(len); + + // TODO: if msg_flags contains MSG_TRUNC, then the message was truncated + // we need to read more data from the socket + // currently, we just ignore it + + auto *cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg == nullptr || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) { + throw std::runtime_error("No file descriptor received in control message"); + } + + int received_fd{ -1 }; + std::memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int)); + if (received_fd < 0) { + throw std::runtime_error("Invalid file descriptor received in control message"); + } + + LINYAPS_BOX_DEBUG() << "Received fd " << utils::inspect_fd(received_fd); + + return utils::file_descriptor{ received_fd }; +} + +} // namespace linyaps_box diff --git a/src/linyaps_box/unixsocket.h b/src/linyaps_box/unixsocket.h new file mode 100644 index 0000000..fc254d0 --- /dev/null +++ b/src/linyaps_box/unixsocket.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/utils/file_describer.h" + +namespace linyaps_box { +class unixSocketClient : public linyaps_box::utils::file_descriptor +{ +public: + explicit unixSocketClient(linyaps_box::utils::file_descriptor socket); + + static unixSocketClient connect(const std::filesystem::path &path); + + unixSocketClient(const unixSocketClient &) = delete; + auto operator=(const unixSocketClient &) -> unixSocketClient & = delete; + + unixSocketClient(unixSocketClient &&other) noexcept = default; + auto operator=(unixSocketClient &&other) noexcept -> unixSocketClient & = default; + + ~unixSocketClient() override = default; + + auto send_fd(utils::file_descriptor &&fd, std::string_view payload = {}) const -> void; + + [[nodiscard]] auto recv_fd(std::string &payload) const -> utils::file_descriptor; +}; +} // namespace linyaps_box diff --git a/src/linyaps_box/utils/close_range.cpp b/src/linyaps_box/utils/close_range.cpp index 71cb6cb..1adf278 100644 --- a/src/linyaps_box/utils/close_range.cpp +++ b/src/linyaps_box/utils/close_range.cpp @@ -18,7 +18,7 @@ void syscall_close_range(uint fd, uint max_fd, int flags) { auto ret = syscall(__NR_close_range, fd, max_fd, flags); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "close_range"); + throw std::system_error(errno, std::system_category(), "close_range"); } } @@ -34,7 +34,7 @@ void close_range_fallback(uint first, uint last, int flags) auto *dir = opendir("/proc/self/fd"); if (dir == nullptr) { - throw std::system_error(errno, std::generic_category(), "opendir /proc/self/fd"); + throw std::system_error(errno, std::system_category(), "opendir /proc/self/fd"); } auto close_dir = make_defer([dir]() noexcept { @@ -49,7 +49,7 @@ void close_range_fallback(uint first, uint last, int flags) // because we should skip the file descriptor which is opened by opendir auto self_fd = dirfd(dir); if (self_fd < 0) { - throw std::system_error(errno, std::generic_category(), "dirfd"); + throw std::system_error(errno, std::system_category(), "dirfd"); } struct dirent *next{ nullptr }; @@ -71,12 +71,12 @@ void close_range_fallback(uint first, uint last, int flags) if ((static_cast(flags) & CLOSE_RANGE_CLOEXEC) != 0) { if (::fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "failed to set up close-on-exec to " + name); } } else { if (::close(fd) < 0) { - throw std::system_error(errno, std::generic_category(), "failed to close " + name); + throw std::system_error(errno, std::system_category(), "failed to close " + name); } } } diff --git a/src/linyaps_box/utils/epoll.cpp b/src/linyaps_box/utils/epoll.cpp new file mode 100644 index 0000000..1ea27f1 --- /dev/null +++ b/src/linyaps_box/utils/epoll.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/utils/epoll.h" + +namespace linyaps_box::utils { + +auto epoll_create1(int flags) -> linyaps_box::utils::file_descriptor +{ + auto ret = ::epoll_create1(flags); + if (ret < 0) { + throw std::system_error(errno, std::system_category(), "epoll_create1"); + } + + return linyaps_box::utils::file_descriptor{ ret }; +} + +auto epoll_wait(const linyaps_box::utils::file_descriptor &efd, + std::vector &events, + int timeout) -> uint +{ + while (true) { + auto ret = ::epoll_wait(efd.get(), events.data(), static_cast(events.size()), timeout); + if (ret < 0) { + if (errno == EINTR) { + continue; + } + + throw std::system_error(errno, std::system_category(), "epoll_wait"); + } + + return ret; + } +} + +auto epoll_ctl(const linyaps_box::utils::file_descriptor &efd, + epoll_operation op, + const linyaps_box::utils::file_descriptor &fd, + struct epoll_event *event) -> void +{ + auto ret = ::epoll_ctl(efd.get(), static_cast(op), fd.get(), event); + if (ret < 0) { + throw std::system_error(errno, std::system_category(), "epoll_ctl"); + } +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/epoll.h b/src/linyaps_box/utils/epoll.h new file mode 100644 index 0000000..5b9e0b2 --- /dev/null +++ b/src/linyaps_box/utils/epoll.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/utils/file_describer.h" + +#include + +#include + +namespace linyaps_box::utils { + +enum class epoll_operation : uint8_t { + add = EPOLL_CTL_ADD, + modify = EPOLL_CTL_MOD, + remove = EPOLL_CTL_DEL +}; + +auto epoll_create1(int flags) -> linyaps_box::utils::file_descriptor; + +[[nodiscard]] auto epoll_wait(const linyaps_box::utils::file_descriptor &efd, + std::vector &events, + int timeout) -> uint; + +auto epoll_ctl(const linyaps_box::utils::file_descriptor &efd, + epoll_operation op, + const linyaps_box::utils::file_descriptor &fd, + struct epoll_event *event = nullptr) -> void; + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/file.cpp b/src/linyaps_box/utils/file.cpp new file mode 100644 index 0000000..485d5ef --- /dev/null +++ b/src/linyaps_box/utils/file.cpp @@ -0,0 +1,321 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/utils/file.h" + +#include "linyaps_box/utils/inspect.h" +#include "linyaps_box/utils/log.h" + +#include + +#include + +#ifdef LINYAPS_BOX_HAVE_OPENAT2_H +#include +#endif + +#include + +#ifndef RESOLVE_IN_ROOT +#define RESOLVE_IN_ROOT 0x10 +#endif + +#ifndef __NR_openat2 +#define __NR_openat2 437 +#endif + +namespace { +auto open_at_fallback(const linyaps_box::utils::file_descriptor &root, + const std::filesystem::path &path, + int flag, + mode_t mode) -> linyaps_box::utils::file_descriptor +{ + LINYAPS_BOX_DEBUG() << "fallback openat " << path.c_str() << " at FD=" << root.get() << " with " + << linyaps_box::utils::inspect_fcntl_or_open_flags( + static_cast(flag)) + << "\n\t" << linyaps_box::utils::inspect_fd(root.get()); + // TODO: we need implement a compatible fallback + // currently we just use openat and do some simple check + const auto &file_path = path.relative_path(); + const auto fd = ::openat(root.get(), file_path.c_str(), flag, mode); + if (fd < 0) { + auto full_path = root.current_path() / path.relative_path(); + throw std::system_error(errno, + std::system_category(), + std::string{ "openat: failed to open " } + full_path.string()); + } + + return linyaps_box::utils::file_descriptor{ fd }; +} + +auto syscall_openat2(int dirfd, const char *path, uint64_t flag, uint64_t mode, uint64_t resolve) + -> linyaps_box::utils::file_descriptor +{ + struct openat2_how + { + uint64_t flags; + uint64_t mode; + uint64_t resolve; + } how{ flag, mode, resolve }; + + const auto ret = syscall(__NR_openat2, dirfd, path, &how, sizeof(openat2_how), 0); + if (ret < 0) { + throw std::system_error(errno, std::system_category(), "openat2"); + } + + return linyaps_box::utils::file_descriptor{ static_cast(ret) }; +} + +} // namespace + +namespace linyaps_box::utils { + +auto open(const std::filesystem::path &path, int flag, mode_t mode) + -> linyaps_box::utils::file_descriptor +{ + LINYAPS_BOX_DEBUG() << "open " << path.c_str() << " with " + << inspect_fcntl_or_open_flags(static_cast(flag)); + const auto fd = ::open(path.c_str(), flag, mode); + if (fd == -1) { + throw std::system_error(errno, + std::system_category(), + "open: failed to open " + path.string()); + } + + return linyaps_box::utils::file_descriptor{ fd }; +} + +auto open_at(const linyaps_box::utils::file_descriptor &root, + const std::filesystem::path &path, + int flag, + mode_t mode) -> linyaps_box::utils::file_descriptor +{ + LINYAPS_BOX_DEBUG() << "open " << path.c_str() << " at FD=" << root.get() << " with " + << inspect_fcntl_or_open_flags(static_cast(flag)) << "\n\t" + << inspect_fd(root.get()); + + static bool support_openat2{ true }; + while (support_openat2) { + try { + return syscall_openat2(root.get(), + path.c_str(), + static_cast(flag), + mode, + RESOLVE_IN_ROOT); + } catch (const std::system_error &e) { + const auto code = e.code().value(); + if (code == EINTR || code == EAGAIN) { + continue; + } + + if (code == ENOSYS) { + support_openat2 = false; + break; + } + + if (code == EINVAL || code == EPERM) { + break; + } + + throw std::system_error( + code, + std::system_category(), + std::string{ e.what() } + ": failed to open " + + (root.current_path() / path.relative_path()).string()); + } + } + + // NOTE: openat2 only available after linux 5.15 + return open_at_fallback(root, path, flag, mode); +} + +auto touch(const file_descriptor &root, const std::filesystem::path &path, int flag, mode_t mode) + -> linyaps_box::utils::file_descriptor +{ + LINYAPS_BOX_DEBUG() << "touch " << path << " at " << inspect_fd(root.get()); + const auto fd = ::openat(root.get(), path.c_str(), flag, mode); + if (fd == -1) { + throw std::system_error(errno, + std::system_category(), + "openat: " + (root.current_path() / path.relative_path()).string()); + } + + return linyaps_box::utils::file_descriptor{ fd }; +} + +auto fstat(const file_descriptor &fd) -> struct stat +{ + struct stat statbuf{}; + auto ret = ::fstat(fd.get(), &statbuf); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "fstat"); + } + + return statbuf; + +} + +auto fstatat(const file_descriptor &fd, const std::filesystem::path &path, int flag) -> struct stat + +{ + struct stat statbuf{}; + auto ret = ::fstatat(fd.get(), path.c_str(), &statbuf, flag); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "fstatat"); + } + + return statbuf; + +} + +auto fstatat(const file_descriptor &fd, const std::filesystem::path &path) -> struct stat + +{ + return linyaps_box::utils::fstatat(fd, path, AT_EMPTY_PATH); + +} + +auto lstatat(const file_descriptor &fd, const std::filesystem::path &path) -> struct stat + +{ + return linyaps_box::utils::fstatat(fd, path, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + +} + +auto lstat(const std::filesystem::path &path) -> struct stat + +{ + struct stat statbuf{}; + auto ret = ::lstat(path.c_str(), &statbuf); + if (ret == -1) { + throw std::system_error(errno, + std::system_category(), + "lstat " + path.string() + " failed:"); + } + + return statbuf; + +} + +auto statfs(const file_descriptor &fd) -> struct statfs + +{ + struct statfs statbuf{}; + auto ret = ::statfs(fd.proc_path().c_str(), &statbuf); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "statfs"); + } + + return statbuf; + +} + +auto +to_linux_file_type(std::filesystem::file_type type) noexcept -> int + +{ + switch (type) { + case std::filesystem::file_type::regular: + return S_IFREG; + case std::filesystem::file_type::directory: + return S_IFDIR; + case std::filesystem::file_type::symlink: + return S_IFLNK; + case std::filesystem::file_type::block: + return S_IFBLK; + case std::filesystem::file_type::character: + return S_IFCHR; + case std::filesystem::file_type::fifo: + return S_IFIFO; + case std::filesystem::file_type::socket: + return S_IFSOCK; + case std::filesystem::file_type::unknown: { + LINYAPS_BOX_WARNING() << "Try to convert unknown type to linux file type"; + return 0; + } + case std::filesystem::file_type::none: { + LINYAPS_BOX_DEBUG() << "Try to convert none type to linux file type"; + assert(false); + return -1; + } + case std::filesystem::file_type::not_found: { + LINYAPS_BOX_WARNING() << "Try to convert not_found type to linux file type"; + assert(false); + return -1; + } + default: { + LINYAPS_BOX_ERR() << "Try to convert unhandled file type " << static_cast(type) + << " to linux file type"; + assert(false); + return -1; + } + } +} + +auto to_fs_file_type(mode_t type) noexcept -> std::filesystem::file_type +{ + switch (type & S_IFMT) { + case S_IFREG: + return std::filesystem::file_type::regular; + case S_IFDIR: + return std::filesystem::file_type::directory; + case S_IFLNK: + return std::filesystem::file_type::symlink; + case S_IFBLK: + return std::filesystem::file_type::block; + case S_IFCHR: + return std::filesystem::file_type::character; + case S_IFIFO: + return std::filesystem::file_type::fifo; + case S_IFSOCK: + return std::filesystem::file_type::socket; + default: + return std::filesystem::file_type::unknown; + } +} + +auto is_type(mode_t mode, std::filesystem::file_type type) noexcept -> bool +{ + auto f_type = to_linux_file_type(type); + if (f_type <= 0) { + return false; + } + + return is_type(mode, f_type); +} + +auto is_type(mode_t mode, mode_t type) noexcept -> bool +{ + return (mode & S_IFMT) == type; +} + +auto to_string(std::filesystem::file_type type) noexcept -> std::string_view +{ + switch (type) { + case std::filesystem::file_type::none: + return "None"; + case std::filesystem::file_type::not_found: + return "Not found"; + case std::filesystem::file_type::regular: + return "Regular"; + case std::filesystem::file_type::directory: + return "Directory"; + case std::filesystem::file_type::symlink: + return "Symlink"; + case std::filesystem::file_type::block: + return "Block"; + case std::filesystem::file_type::character: + return "Character"; + case std::filesystem::file_type::fifo: + return "FIFO"; + case std::filesystem::file_type::socket: + return "Socket"; + case std::filesystem::file_type::unknown: + return "Unknown"; + } + + __builtin_unreachable(); +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/file.h b/src/linyaps_box/utils/file.h new file mode 100644 index 0000000..4f16afc --- /dev/null +++ b/src/linyaps_box/utils/file.h @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/utils/file_describer.h" + +#include + +#include + +#include +#include + +namespace linyaps_box::utils { + +template +auto fcntl(const file_descriptor &fd, int operation, Args... args) -> unsigned int +{ + auto ret = ::fcntl(fd.get(), operation, std::forward(args)...); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "fcntl"); + } + + return ret; +} + +auto open(const std::filesystem::path &path, int flag = O_PATH | O_CLOEXEC, mode_t mode = 0) + -> file_descriptor; + +auto open_at(const file_descriptor &root, + const std::filesystem::path &path, + int flag = O_PATH | O_CLOEXEC, + mode_t mode = 0) -> file_descriptor; + +auto touch(const file_descriptor &root, + const std::filesystem::path &path, + int flag, + mode_t mode = 0644) -> file_descriptor; + +auto fstat(const file_descriptor &fd) -> struct stat; + +auto fstatat(const file_descriptor &fd, const std::filesystem::path &path, int flag) -> struct stat; + +auto fstatat(const file_descriptor &fd, const std::filesystem::path &path) -> struct stat; + +auto lstatat(const file_descriptor &fd, const std::filesystem::path &path) -> struct stat; + +auto lstat(const std::filesystem::path &path) -> struct stat; + +auto statfs(const file_descriptor &fd) -> struct statfs; + +auto to_linux_file_type(std::filesystem::file_type type) noexcept -> int; + +auto to_fs_file_type(mode_t type) noexcept -> std::filesystem::file_type; + +auto is_type(mode_t mode, std::filesystem::file_type type) noexcept -> bool; + +auto is_type(mode_t mode, mode_t type) noexcept -> bool; + +auto to_string(std::filesystem::file_type type) noexcept -> std::string_view; + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/file_describer.cpp b/src/linyaps_box/utils/file_describer.cpp index c7e9983..99c2d61 100644 --- a/src/linyaps_box/utils/file_describer.cpp +++ b/src/linyaps_box/utils/file_describer.cpp @@ -4,11 +4,15 @@ #include "linyaps_box/utils/file_describer.h" +#include "linyaps_box/utils/file.h" +#include "linyaps_box/utils/ioctl.h" #include "linyaps_box/utils/log.h" #include +#include #include +#include linyaps_box::utils::file_descriptor_closed_exception::file_descriptor_closed_exception() : std::runtime_error("file descriptor is closed") @@ -27,14 +31,25 @@ linyaps_box::utils::file_descriptor_invalid_exception::file_descriptor_invalid_e linyaps_box::utils::file_descriptor_invalid_exception:: ~file_descriptor_invalid_exception() noexcept = default; -linyaps_box::utils::file_descriptor::file_descriptor(int fd) - : fd_(fd) +linyaps_box::utils::file_descriptor::file_descriptor(int fd, bool auto_close) + : auto_close_(auto_close) + , fd_(fd) { + if (fd < 0) { + throw file_descriptor_invalid_exception("invalid file descriptor"); + } + + fd_ = fd; + + auto flag = fcntl(*this, F_GETFL); + if ((flag & O_NONBLOCK) != 0) { + nonblock_ = true; + } } linyaps_box::utils::file_descriptor::~file_descriptor() { - if (fd_ < 0) { + if (fd_ < 0 || !auto_close_) { return; } @@ -44,27 +59,48 @@ linyaps_box::utils::file_descriptor::~file_descriptor() } linyaps_box::utils::file_descriptor::file_descriptor(file_descriptor &&other) noexcept + : nonblock_(other.nonblock_) + , auto_close_(other.auto_close_) + , fd_(other.fd_) { - *this = std::move(other); + other.fd_ = -1; +} + +auto linyaps_box::utils::file_descriptor::operator=(file_descriptor &&other) noexcept + -> linyaps_box::utils::file_descriptor & +{ + if (this == &other) { + return *this; + } + + std::swap(this->fd_, other.fd_); + std::swap(this->auto_close_, other.auto_close_); + std::swap(this->nonblock_, other.nonblock_); + return *this; } auto linyaps_box::utils::file_descriptor::release() -> void { - int ret = -1; - std::swap(ret, fd_); + int tmp = -1; + std::swap(tmp, fd_); - if (::close(ret) < 0) { - auto msg{ "failed to close file descriptor " + std::to_string(ret) + ": " + if (tmp >= 0 && ::close(tmp) < 0) { + auto msg{ "failed to close file descriptor " + std::to_string(tmp) + ": " + ::strerror(errno) }; throw file_descriptor_invalid_exception(msg); } } -auto linyaps_box::utils::file_descriptor::get() const noexcept -> int +auto linyaps_box::utils::file_descriptor::get() const & noexcept -> int { return fd_; } +auto linyaps_box::utils::file_descriptor::get() && noexcept -> int +{ + return std::exchange(fd_, -1); +} + auto linyaps_box::utils::file_descriptor::duplicate() const -> linyaps_box::utils::file_descriptor { if (fd_ == -1) { @@ -77,42 +113,51 @@ auto linyaps_box::utils::file_descriptor::duplicate() const -> linyaps_box::util auto ret = dup(fd_); if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "fcntl"); + throw std::system_error(errno, std::system_category(), "dup"); } + file_descriptor new_fd{ ret }; + // dup will lost the close-on-exec flag // and we don't want to use FD_DUPFD_CLOEXEC due to it require a specific fd number - auto flag = fcntl(fd_, F_GETFD); - if (flag < 0) { - throw std::system_error(errno, std::generic_category(), "fcntl"); + auto flag = fcntl(*this, F_GETFD); + fcntl(new_fd, F_SETFD, flag); + + return new_fd; +} + +auto linyaps_box::utils::file_descriptor::duplicate_to(int target, int flags) const -> void +{ + if (fd_ == -1) { + throw file_descriptor_closed_exception(); } - if (fcntl(ret, F_SETFD, flag) < 0) { - throw std::system_error(errno, std::generic_category(), "fcntl"); + if (fd_ == AT_FDCWD) { + throw file_descriptor_invalid_exception("cannot duplicate AT_FDCWD"); } - return file_descriptor{ ret }; + auto ret = dup3(fd_, target, flags); + if (ret < 0) { + throw std::system_error(errno, std::system_category(), "dup3"); + } } auto linyaps_box::utils::file_descriptor::operator<<(const std::byte &byte) -> linyaps_box::utils::file_descriptor & { while (true) { - auto ret = write(fd_, &byte, 1); - if (ret == 1) { + auto status = write(byte); + switch (status) { + case IOStatus::Success: return *this; - } - if (ret == 0 || errno == EINTR || errno == EAGAIN) { + case IOStatus::TryAgain: continue; + case IOStatus::Eof: + return *this; + case IOStatus::Closed: + throw file_descriptor_closed_exception(); } - throw std::system_error(errno, std::generic_category(), "write"); } -} - -auto linyaps_box::utils::file_descriptor::operator=(file_descriptor &&other) noexcept - -> linyaps_box::utils::file_descriptor & -{ - std::swap(this->fd_, other.fd_); return *this; } @@ -120,18 +165,19 @@ auto linyaps_box::utils::file_descriptor::operator>>(std::byte &byte) -> linyaps_box::utils::file_descriptor & { while (true) { - auto ret = read(fd_, &byte, 1); - if (ret == 1) { + auto status = read(byte); + switch (status) { + case IOStatus::Success: return *this; - } - if (ret == 0) { - throw file_descriptor_closed_exception(); - } - if (errno == EINTR || errno == EAGAIN) { + case IOStatus::TryAgain: continue; + case IOStatus::Eof: + return *this; + case IOStatus::Closed: + throw file_descriptor_closed_exception(); } - throw std::system_error(errno, std::generic_category(), "read"); } + return *this; } auto linyaps_box::utils::file_descriptor::proc_path() const -> std::filesystem::path @@ -156,3 +202,180 @@ auto linyaps_box::utils::file_descriptor::cwd() -> file_descriptor { return file_descriptor{ AT_FDCWD }; } + +auto linyaps_box::utils::file_descriptor::set_nonblock(bool nonblock) -> void +{ + LINYAPS_BOX_DEBUG() << "set fd " << fd_ << " to nonblock: " << std::boolalpha << nonblock; + + auto flags = fcntl(*this, F_GETFL, 0); + fcntl(*this, + F_SETFL, + nonblock ? (flags | O_NONBLOCK) : (flags & ~static_cast(O_NONBLOCK))); + nonblock_ = nonblock; +} + +auto linyaps_box::utils::file_descriptor::type() const -> std::filesystem::file_type +{ + auto stat = linyaps_box::utils::fstat(*this); + return linyaps_box::utils::to_fs_file_type(stat.st_mode); +} + +auto linyaps_box::utils::file_descriptor::read_span(span ws, + std::size_t &bytes_read) const -> IOStatus +{ + bytes_read = 0; + if (ws.empty()) { + return IOStatus::Success; + } + + while (bytes_read < ws.size()) { + const auto ret = ::read(fd_, ws.data() + bytes_read, ws.size() - bytes_read); + + if (ret > 0) { + bytes_read += static_cast(ret); + continue; + } + + if (ret == 0) { + return bytes_read > 0 ? IOStatus::Success : IOStatus::Eof; + } + + if (errno == EINTR) { + continue; + } + + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (nonblock_) { + return bytes_read > 0 ? IOStatus::Success : IOStatus::TryAgain; + } + + continue; + } + + if (errno == EIO || errno == ECONNRESET) { + return bytes_read > 0 ? IOStatus::Success : IOStatus::Closed; + } + + throw std::system_error(errno, std::system_category(), "failed to read from fd"); + } + + return IOStatus::Success; +} + +auto linyaps_box::utils::file_descriptor::write_span(span rs, + std::size_t &bytes_written) const -> IOStatus +{ + bytes_written = 0; + if (rs.empty()) { + return IOStatus::Success; + } + + while (bytes_written < rs.size()) { + const auto ret = ::write(fd_, rs.data() + bytes_written, rs.size() - bytes_written); + + if (ret > 0) { + bytes_written += static_cast(ret); + continue; + } + + if (ret == 0) { + return bytes_written > 0 ? IOStatus::Success : IOStatus::TryAgain; + } + + if (errno == EINTR) { + continue; + } + + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (nonblock_) { + return bytes_written > 0 ? IOStatus::Success : IOStatus::TryAgain; + } + + // maybe we can retry? + continue; + } + + if (errno == EPIPE || errno == ECONNRESET || errno == ENOTCONN || errno == EIO) { + return bytes_written > 0 ? IOStatus::Success : IOStatus::Closed; + } + + throw std::system_error(errno, std::system_category(), "failed to write to fd"); + } + + return IOStatus::Success; +} + +auto linyaps_box::utils::file_descriptor::read_vecs(span ws, + std::size_t &bytes_read) const -> IOStatus +{ + bytes_read = 0; + if (ws.empty()) { + return IOStatus::Success; + } + + while (true) { + const auto ret = ::readv(fd_, ws.data(), static_cast(ws.size())); + + if (ret > 0) { + bytes_read = static_cast(ret); + return IOStatus::Success; + } + + if (ret == 0) { + return IOStatus::Eof; + } + + if (errno == EINTR) { + continue; + } + + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (nonblock_) { + return IOStatus::TryAgain; + } + + continue; + } + + if (errno == EIO || errno == ECONNRESET) { + return IOStatus::Closed; + } + + throw std::system_error(errno, std::system_category(), "failed to readv from fd"); + } +} + +auto linyaps_box::utils::file_descriptor::write_vecs(span rs, + std::size_t &bytes_written) const -> IOStatus +{ + bytes_written = 0; + if (rs.empty()) { + return IOStatus::Success; + } + + while (true) { + const auto ret = ::writev(fd_, rs.data(), static_cast(rs.size())); + + if (ret >= 0) { + bytes_written = static_cast(ret); + return IOStatus::Success; + } + + if (errno == EINTR) { + continue; + } + + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (nonblock_) { + return IOStatus::TryAgain; + } + continue; + } + + if (errno == EPIPE || errno == ECONNRESET || errno == ENOTCONN || errno == EIO) { + return IOStatus::Closed; + } + + throw std::system_error(errno, std::system_category(), "failed to writev to fd"); + } +} diff --git a/src/linyaps_box/utils/file_describer.h b/src/linyaps_box/utils/file_describer.h index af8d336..9737792 100644 --- a/src/linyaps_box/utils/file_describer.h +++ b/src/linyaps_box/utils/file_describer.h @@ -4,8 +4,11 @@ #pragma once +#include "linyaps_box/utils/span.h" + #include +#include #include namespace linyaps_box::utils { @@ -39,9 +42,12 @@ class file_descriptor_invalid_exception : public std::runtime_error class file_descriptor { public: - explicit file_descriptor(int fd = -1); + enum class IOStatus : uint8_t { Success, TryAgain, Eof, Closed }; + + file_descriptor() = default; + explicit file_descriptor(int fd, bool auto_close = true); - ~file_descriptor(); + virtual ~file_descriptor(); file_descriptor(const file_descriptor &) = delete; auto operator=(const file_descriptor &) -> file_descriptor & = delete; @@ -49,12 +55,18 @@ class file_descriptor file_descriptor(file_descriptor &&other) noexcept; auto operator=(file_descriptor &&other) noexcept -> file_descriptor &; - [[nodiscard]] auto get() const noexcept -> int; + [[nodiscard]] auto get() const & noexcept -> int; + + [[nodiscard]] auto get() && noexcept -> int; + + [[nodiscard]] auto valid() const -> bool { return fd_ != -1; } auto release() -> void; [[nodiscard]] auto duplicate() const -> file_descriptor; + auto duplicate_to(int target, int flags) const -> void; + auto operator<<(const std::byte &byte) -> file_descriptor &; auto operator>>(std::byte &byte) -> file_descriptor &; @@ -65,7 +77,42 @@ class file_descriptor static auto cwd() -> file_descriptor; + [[nodiscard]] auto type() const -> std::filesystem::file_type; + + auto set_nonblock(bool nonblock) -> void; + + auto read_span(span ws, std::size_t &bytes_read) const -> IOStatus; + + auto write_span(span rs, std::size_t &bytes_written) const -> IOStatus; + + auto read_vecs(span ws, std::size_t &bytes_read) const -> IOStatus; + + auto write_vecs(span rs, std::size_t &bytes_written) const -> IOStatus; + + template + [[nodiscard]] auto read(T &out) const -> IOStatus + { + static_assert(std::is_trivially_copyable_v, "T must be trivially copyable for raw read"); + + std::size_t bytes_read{ 0 }; + auto ws = span(reinterpret_cast(&out), sizeof(T)); + return read_span(ws, bytes_read); + } + + template + [[nodiscard]] auto write(const T &in) const -> IOStatus + { + static_assert(std::is_trivially_copyable_v, "T must be trivially copyable"); + + std::size_t bytes_written{ 0 }; + auto rs = span(reinterpret_cast(&in), sizeof(T)); + return write_span(rs, bytes_written); + } + private: + // keep this layout, for padding optimization + bool nonblock_{ false }; + bool auto_close_{ false }; int fd_{ -1 }; }; diff --git a/src/linyaps_box/utils/fstat.cpp b/src/linyaps_box/utils/fstat.cpp deleted file mode 100644 index 97e6aa5..0000000 --- a/src/linyaps_box/utils/fstat.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: LGPL-3.0-or-later - -#include "linyaps_box/utils/fstat.h" - -#include "linyaps_box/utils/log.h" - -#include - -namespace linyaps_box::utils { - -auto fstatat(const file_descriptor &fd, std::filesystem::path path, int flag) -> struct stat -{ - if (!path.empty() && path.is_absolute()) { - path = path.lexically_relative("/"); - } - - struct stat statbuf{}; - auto ret = ::fstatat(fd.get(), path.c_str(), &statbuf, flag); - if (ret == -1) { - throw std::system_error(errno, std::generic_category(), "fstatat"); - } - - return statbuf; - -} - -auto fstatat(const file_descriptor &fd, const std::filesystem::path &path) -> struct stat - -{ - return linyaps_box::utils::fstatat(fd, path, AT_EMPTY_PATH); - -} - -auto lstatat(const file_descriptor &fd, const std::filesystem::path &path) -> struct stat - -{ - return linyaps_box::utils::fstatat(fd, path, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); - -} - -auto statfs(const file_descriptor &fd) -> struct statfs - -{ - struct statfs statbuf{}; - auto ret = ::statfs(fd.proc_path().c_str(), &statbuf); - if (ret == -1) { - throw std::system_error(errno, std::generic_category(), "statfs"); - } - - return statbuf; - -} - -auto -to_linux_file_type(std::filesystem::file_type type) noexcept -> int - -{ - switch (type) { - case std::filesystem::file_type::regular: - return S_IFREG; - case std::filesystem::file_type::directory: - return S_IFDIR; - case std::filesystem::file_type::symlink: - return S_IFLNK; - case std::filesystem::file_type::block: - return S_IFBLK; - case std::filesystem::file_type::character: - return S_IFCHR; - case std::filesystem::file_type::fifo: - return S_IFIFO; - case std::filesystem::file_type::socket: - return S_IFSOCK; - case std::filesystem::file_type::unknown: { - LINYAPS_BOX_WARNING() << "Try to convert unknown type to linux file type"; - return 0; - } - case std::filesystem::file_type::none: { - LINYAPS_BOX_DEBUG() << "Try to convert none type to linux file type"; - assert(false); - return -1; - } - case std::filesystem::file_type::not_found: { - LINYAPS_BOX_WARNING() << "Try to convert not_found type to linux file type"; - assert(false); - return -1; - } - default: { - LINYAPS_BOX_ERR() << "Try to convert unhandled file type " << static_cast(type) - << " to linux file type"; - assert(false); - return -1; - } - } -} - -auto to_fs_file_type(mode_t type) noexcept -> std::filesystem::file_type -{ - switch (type) { - case S_IFREG: - return std::filesystem::file_type::regular; - case S_IFDIR: - return std::filesystem::file_type::directory; - case S_IFLNK: - return std::filesystem::file_type::symlink; - case S_IFBLK: - return std::filesystem::file_type::block; - case S_IFCHR: - return std::filesystem::file_type::character; - case S_IFIFO: - return std::filesystem::file_type::fifo; - case S_IFSOCK: - return std::filesystem::file_type::socket; - default: - return std::filesystem::file_type::unknown; - } -} - -auto is_type(mode_t mode, std::filesystem::file_type type) noexcept -> bool -{ - auto f_type = to_linux_file_type(type); - if (f_type <= 0) { - return false; - } - - return is_type(mode, f_type); -} - -auto is_type(mode_t mode, mode_t type) noexcept -> bool -{ - return (mode & S_IFMT) == type; -} - -auto to_string(std::filesystem::file_type type) noexcept -> std::string_view -{ - switch (type) { - case std::filesystem::file_type::none: - return "None"; - case std::filesystem::file_type::not_found: - return "Not found"; - case std::filesystem::file_type::regular: - return "Regular"; - case std::filesystem::file_type::directory: - return "Directory"; - case std::filesystem::file_type::symlink: - return "Symlink"; - case std::filesystem::file_type::block: - return "Block"; - case std::filesystem::file_type::character: - return "Character"; - case std::filesystem::file_type::fifo: - return "FIFO"; - case std::filesystem::file_type::socket: - return "Socket"; - case std::filesystem::file_type::unknown: - return "Unknown"; - } - - __builtin_unreachable(); -} - -} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/fstat.h b/src/linyaps_box/utils/fstat.h deleted file mode 100644 index 5a127a1..0000000 --- a/src/linyaps_box/utils/fstat.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2022-2025 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: LGPL-3.0-or-later - -#pragma once - -#include "linyaps_box/utils/file_describer.h" - -#include - -#include -#include - -namespace linyaps_box::utils { -auto fstatat(const file_descriptor &fd, std::filesystem::path path, int flag) -> struct stat; -auto fstatat(const file_descriptor &fd, const std::filesystem::path &path) -> struct stat; -auto lstatat(const file_descriptor &fd, const std::filesystem::path &path) -> struct stat; -auto statfs(const file_descriptor &fd) -> struct statfs; - -auto to_linux_file_type(std::filesystem::file_type type) noexcept -> int; -auto to_fs_file_type(mode_t type) noexcept -> std::filesystem::file_type; -auto is_type(mode_t mode, std::filesystem::file_type type) noexcept -> bool; -auto is_type(mode_t mode, mode_t type) noexcept -> bool; -auto to_string(std::filesystem::file_type type) noexcept -> std::string_view; -} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/inspect.cpp b/src/linyaps_box/utils/inspect.cpp index 58f0ae7..aa2f0de 100644 --- a/src/linyaps_box/utils/inspect.cpp +++ b/src/linyaps_box/utils/inspect.cpp @@ -45,7 +45,7 @@ auto inspect_fdinfo(const std::filesystem::path &fdinfoPath) -> std::string auto inspect_fdinfo(int fd) -> std::string { std::stringstream ss; - ss << linyaps_box::utils::inspect_path(fd) << " "; + ss << fd << " -> " << linyaps_box::utils::inspect_path(fd) << "\n"; ss << inspect_fdinfo(std::filesystem::path("/proc/self/fdinfo/" + std::to_string(fd))); return ss.str(); } @@ -166,7 +166,7 @@ auto inspect_permissions(int fd) -> std::string struct stat buf{}; if (fstat(fd, &buf) == -1) { - throw std::system_error(errno, std::generic_category(), "fstat"); + throw std::system_error(errno, std::system_category(), "fstat"); } ss << buf.st_uid << ":" << buf.st_gid << " "; diff --git a/src/linyaps_box/utils/ioctl.h b/src/linyaps_box/utils/ioctl.h new file mode 100644 index 0000000..e522f5b --- /dev/null +++ b/src/linyaps_box/utils/ioctl.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/utils/file_describer.h" +#include "linyaps_box/utils/utils.h" + +#include + +namespace linyaps_box::utils { + +template +auto ioctl(const file_descriptor &fd, + decltype(get_n_params_type<1>(ioctl))::type request, + Args... args) -> unsigned int +{ + auto ret = ::ioctl(fd.get(), request, std::forward(args)...); + if (ret != 0) { + throw std::system_error(errno, std::system_category(), "ioctl"); + } + + return ret; +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/log.cpp b/src/linyaps_box/utils/log.cpp index 4bdcf98..74a85a1 100644 --- a/src/linyaps_box/utils/log.cpp +++ b/src/linyaps_box/utils/log.cpp @@ -4,9 +4,11 @@ #include "linyaps_box/utils/log.h" +#include "linyaps_box/utils/symlink.h" +#include "linyaps_box/utils/terminal.h" + #include -#include #include #include @@ -76,7 +78,10 @@ auto force_log_to_stderr() -> bool auto stderr_is_a_tty() -> bool { - static const bool result = isatty(fileno(stderr)) != 0; + static const bool result = [] { + auto err = linyaps_box::utils::fileno(stderr); + return isatty(file_descriptor{ err, false }); + }(); return result; } @@ -111,14 +116,8 @@ auto get_current_log_level() -> unsigned int auto get_pid_namespace(int pid) -> std::string { const auto &pidns_path = "/proc/" + ((pid != 0) ? std::to_string(pid) : "self") + "/ns/pid"; + auto result = readlink(pidns_path).string(); - std::array buf{}; - auto length = ::readlink(pidns_path.c_str(), buf.data(), PATH_MAX); - if (length < 0) { - return "not available"; - } - - const std::string_view result(buf.data(), static_cast(length)); constexpr std::string_view prefix = "pid:["; constexpr char suffix = ']'; constexpr auto prefix_len = prefix.size(); @@ -136,7 +135,7 @@ auto get_pid_namespace(int pid) -> std::string return "invalid format"; } - return std::string{ result.substr(prefix_len, result.size() - total_wrapper_len) }; + return result.substr(prefix_len, result.size() - total_wrapper_len); } } // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/mkdir.cpp b/src/linyaps_box/utils/mkdir.cpp index 7ee1d3a..8eb4ae6 100644 --- a/src/linyaps_box/utils/mkdir.cpp +++ b/src/linyaps_box/utils/mkdir.cpp @@ -48,7 +48,7 @@ auto linyaps_box::utils::mkdir(const file_descriptor &root, std::filesystem::pat LINYAPS_BOX_DEBUG() << "current path: " << utils::inspect_path(current.get()) << " perm:" << utils::inspect_permissions(current.get()); throw std::system_error(errno, - std::generic_category(), + std::system_category(), "mkdirat: failed to create " + (current.current_path() / part).string()); } @@ -56,7 +56,7 @@ auto linyaps_box::utils::mkdir(const file_descriptor &root, std::filesystem::pat fd = ::openat(current.get(), part.c_str(), O_PATH); if (fd == -1) { throw std::system_error(errno, - std::generic_category(), + std::system_category(), "openat: failed to open " + (current.current_path() / part).string()); } diff --git a/src/linyaps_box/utils/open_file.cpp b/src/linyaps_box/utils/open_file.cpp deleted file mode 100644 index 20f3e8a..0000000 --- a/src/linyaps_box/utils/open_file.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-FileCopyrightText: 2022-2025 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: LGPL-3.0-or-later - -#include "linyaps_box/utils/open_file.h" - -#include "linyaps_box/utils/inspect.h" -#include "linyaps_box/utils/log.h" - -#include - -#ifdef LINYAPS_BOX_HAVE_OPENAT2_H -#include -#endif - -#include - -#ifndef RESOLVE_IN_ROOT -#define RESOLVE_IN_ROOT 0x10 -#endif - -#ifndef __NR_openat2 -#define __NR_openat2 437 -#endif - -namespace { -auto open_at_fallback(const linyaps_box::utils::file_descriptor &root, - const std::filesystem::path &path, - int flag, - mode_t mode) -> linyaps_box::utils::file_descriptor -{ - LINYAPS_BOX_DEBUG() << "fallback openat " << path.c_str() << " at FD=" << root.get() << " with " - << linyaps_box::utils::inspect_fcntl_or_open_flags( - static_cast(flag)) - << "\n\t" << linyaps_box::utils::inspect_fd(root.get()); - // TODO: we need implement a compatible fallback - // currently we just use openat and do some simple check - const auto &file_path = path.relative_path(); - const auto fd = ::openat(root.get(), file_path.c_str(), flag, mode); - if (fd < 0) { - auto full_path = root.current_path() / path.relative_path(); - throw std::system_error(errno, - std::generic_category(), - std::string{ "openat: failed to open " } + full_path.string()); - } - - return linyaps_box::utils::file_descriptor{ fd }; -} - -auto syscall_openat2(int dirfd, const char *path, uint64_t flag, uint64_t mode, uint64_t resolve) - -> linyaps_box::utils::file_descriptor -{ - struct openat2_how - { - uint64_t flags; - uint64_t mode; - uint64_t resolve; - } how{ flag, mode, resolve }; - - const auto ret = syscall(__NR_openat2, dirfd, path, &how, sizeof(openat2_how), 0); - if (ret < 0) { - throw std::system_error(errno, std::generic_category(), "openat2"); - } - - return linyaps_box::utils::file_descriptor{ static_cast(ret) }; -} -} // namespace - -auto linyaps_box::utils::open(const std::filesystem::path &path, int flag, mode_t mode) - -> linyaps_box::utils::file_descriptor -{ - LINYAPS_BOX_DEBUG() << "open " << path.c_str() << " with " - << inspect_fcntl_or_open_flags(static_cast(flag)); - const auto fd = ::open(path.c_str(), flag, mode); - if (fd == -1) { - throw std::system_error(errno, - std::generic_category(), - "open: failed to open " + path.string()); - } - - return linyaps_box::utils::file_descriptor{ fd }; -} - -auto linyaps_box::utils::open_at(const linyaps_box::utils::file_descriptor &root, - const std::filesystem::path &path, - int flag, - mode_t mode) -> linyaps_box::utils::file_descriptor -{ - LINYAPS_BOX_DEBUG() << "open " << path.c_str() << " at FD=" << root.get() << " with " - << inspect_fcntl_or_open_flags(static_cast(flag)) << "\n\t" - << inspect_fd(root.get()); - - static bool support_openat2{ true }; - while (support_openat2) { - try { - return syscall_openat2(root.get(), - path.c_str(), - static_cast(flag), - mode, - RESOLVE_IN_ROOT); - } catch (const std::system_error &e) { - const auto code = e.code().value(); - if (code == EINTR || code == EAGAIN) { - continue; - } - - if (code == ENOSYS) { - support_openat2 = false; - break; - } - - if (code == EINVAL || code == EPERM) { - break; - } - - throw std::system_error( - code, - std::generic_category(), - std::string{ e.what() } + ": failed to open " - + (root.current_path() / path.relative_path()).string()); - } - } - - // NOTE: openat2 only available after linux 5.15 - return open_at_fallback(root, path, flag, mode); -} diff --git a/src/linyaps_box/utils/open_file.h b/src/linyaps_box/utils/open_file.h deleted file mode 100644 index 7063753..0000000 --- a/src/linyaps_box/utils/open_file.h +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2022-2025 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: LGPL-3.0-or-later - -#pragma once - -#include "linyaps_box/utils/file_describer.h" - -#include - -#include - -namespace linyaps_box::utils { - -auto open(const std::filesystem::path &path, int flag = O_PATH | O_CLOEXEC, mode_t mode = 0) - -> file_descriptor; - -auto open_at(const file_descriptor &root, - const std::filesystem::path &path, - int flag = O_PATH | O_CLOEXEC, - mode_t mode = 0) -> file_descriptor; - -} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/platform.cpp b/src/linyaps_box/utils/platform.cpp index 18ab929..ada8fc1 100644 --- a/src/linyaps_box/utils/platform.cpp +++ b/src/linyaps_box/utils/platform.cpp @@ -4,7 +4,11 @@ #include "linyaps_box/utils/platform.h" +#include "linyaps_box/utils/log.h" + +#include // IWYU pragma: keep #include +#include #include #include @@ -67,4 +71,27 @@ auto str_to_rlimit(std::string_view str) -> int return it->second; } + +auto get_path_max(const std::filesystem::path &fs_dir) noexcept -> std::size_t +{ + errno = 0; + auto max = pathconf(fs_dir.c_str(), _PC_PATH_MAX); + if (max == -1) { + if (errno != 0) { + auto saved_errno = errno; + LINYAPS_BOX_WARNING() << "Failed to get pathconf: " << ::strerror(saved_errno) + << ", use default value"; + errno = 0; + } +#ifdef PATH_MAX + return PATH_MAX; +#else + // Should we make the default value to be configured value? + return 4096; +#endif + } + + return static_cast(max); +} + } // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/platform.h b/src/linyaps_box/utils/platform.h index 7cc2f8a..3ce5896 100644 --- a/src/linyaps_box/utils/platform.h +++ b/src/linyaps_box/utils/platform.h @@ -3,9 +3,11 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #pragma once +#include #include namespace linyaps_box::utils { auto str_to_signal(std::string_view str) -> int; auto str_to_rlimit(std::string_view str) -> int; +auto get_path_max(const std::filesystem::path &fs_dir) noexcept -> std::size_t; } // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/process.cpp b/src/linyaps_box/utils/process.cpp new file mode 100644 index 0000000..489fb4a --- /dev/null +++ b/src/linyaps_box/utils/process.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/utils/process.h" + +#include + +#include + +namespace linyaps_box::utils { + +auto waitpid(pid_t pid, int options) -> WaitResult +{ + int status{ 0 }; + while (true) { + auto ret = ::waitpid(pid, &status, options); + if (ret > 0) { + return { WaitStatus::Reaped, ret, status }; + } + + if (ret == 0) { // fow WNOHANG + return { WaitStatus::None }; + } + + if (errno == EINTR || errno == EAGAIN) { + continue; + } + + if (errno == ECHILD) { + return { WaitStatus::NoChild }; + } + + throw std::system_error(errno, std::system_category(), "waitpid"); + } +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/process.h b/src/linyaps_box/utils/process.h new file mode 100644 index 0000000..3d939fb --- /dev/null +++ b/src/linyaps_box/utils/process.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include +#include +#include + +#include +#include + +namespace linyaps_box::utils { + +enum class WaitStatus : uint8_t { Reaped, None, NoChild }; + +struct WaitResult +{ + WaitStatus status{ WaitStatus::None }; + pid_t pid{ -1 }; + int exit_code{ -1 }; +}; + +auto waitpid(pid_t pid, int options) -> WaitResult; + +template +auto prctl(int option, Args... args) -> uint +{ + auto ret = ::prctl(option, std::forward(args)...); + if (ret < 0) { + throw std::system_error(errno, std::system_category(), "prctl"); + } + + return ret; +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/ringbuffer.cpp b/src/linyaps_box/utils/ringbuffer.cpp new file mode 100644 index 0000000..38ce1f4 --- /dev/null +++ b/src/linyaps_box/utils/ringbuffer.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/utils/ringbuffer.h" + +namespace linyaps_box::utils { + +ring_buffer_deleter::ring_buffer_deleter(std::size_t total_size) noexcept + : total_size{ total_size } +{ +} + +auto ring_buffer_deleter::operator()(ring_buffer *rb) const -> void +{ + rb->~ring_buffer(); + ::operator delete(rb, + total_size, + std::align_val_t(std::hardware_constructive_interference_size)); +} + +auto ring_buffer::create(std::size_t requested_capacity) + -> std::unique_ptr +{ + requested_capacity = std::max(requested_capacity, 2); + + std::size_t capacity = 1; + while (capacity < requested_capacity) { + capacity <<= 1U; + } + + const auto total_size = sizeof(ring_buffer) + capacity; + auto *mem = ::operator new(total_size, + std::align_val_t(std::hardware_constructive_interference_size)); + auto *rb = new (mem) ring_buffer(capacity); + return { rb, ring_buffer_deleter(total_size) }; +} + +auto ring_buffer::get_read_vecs() const noexcept -> iov_view +{ + if (empty()) { + return {}; + } + + const auto *base = data_ptr(); + if (tail_ > head_) { + return { { { const_cast(base + head_), tail_ - head_ }, // NOLINT + { nullptr, 0 } } }; + } + + return { { { const_cast(base + head_), capacity_ - head_ }, // NOLINT + { const_cast(base), tail_ } } }; // NOLINT +} + +auto ring_buffer::get_write_vecs() const noexcept -> iov_view +{ + if (full()) { + return {}; + } + + const auto *base = data_ptr(); + const auto space = capacity_ - size() - 1; + + if (tail_ >= head_) { + const auto first_part = std::min(space, capacity_ - tail_); + const auto second_part = space - first_part; + return { { { const_cast(base + tail_), first_part }, // NOLINT + { const_cast(base), second_part } } }; // NOLINT + } + + return { { { const_cast(base + tail_), space }, { nullptr, 0 } } }; // NOLINT +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/ringbuffer.h b/src/linyaps_box/utils/ringbuffer.h new file mode 100644 index 0000000..05c466a --- /dev/null +++ b/src/linyaps_box/utils/ringbuffer.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace linyaps_box::utils { + +class ring_buffer; + +struct ring_buffer_deleter +{ + explicit ring_buffer_deleter(std::size_t total_size) noexcept; + + auto operator()(ring_buffer *rb) const -> void; + +private: + std::size_t total_size{ 0 }; +}; + +class alignas(std::hardware_constructive_interference_size) ring_buffer +{ + using iov_view = std::array; + +public: + enum class Status : uint8_t { Success, TryAgain, Full, Empty, Finished }; + + ring_buffer(const ring_buffer &) = delete; + ring_buffer &operator=(const ring_buffer &) = delete; + ring_buffer(ring_buffer &&) = default; + ring_buffer &operator=(ring_buffer &&) = default; + ~ring_buffer() = default; + + static auto create(std::size_t requested_capacity) + -> std::unique_ptr; + + [[nodiscard]] auto get_read_vecs() const noexcept -> iov_view; + + auto advance_head(std::size_t n) noexcept -> void { head_ = (head_ + n) & mask_; } + + [[nodiscard]] auto get_write_vecs() const noexcept -> iov_view; + + auto advance_tail(std::size_t n) noexcept -> void { tail_ = (tail_ + n) & mask_; } + + [[nodiscard]] auto empty() const noexcept -> bool { return head_ == tail_; } + + [[nodiscard]] auto full() const noexcept -> bool { return ((tail_ + 1) & mask_) == head_; } + + [[nodiscard]] auto capacity() const noexcept -> std::size_t { return capacity_ - 1; } + + [[nodiscard]] auto size() const noexcept -> std::size_t { return (tail_ - head_) & mask_; } + +private: + explicit ring_buffer(std::size_t cap) + : capacity_(cap) + , mask_(cap - 1) + { + } + + [[nodiscard]] auto data_ptr() const noexcept -> const std::byte * + { + return reinterpret_cast(this + 1); // NOLINT + } + + auto data_ptr() noexcept -> std::byte * + { + return reinterpret_cast(this + 1); // NOLINT + } + + std::size_t head_{ 0 }; + std::size_t tail_{ 0 }; + std::size_t capacity_; + std::size_t mask_; +}; + +using ring_buffer_ptr = std::unique_ptr; + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/session.cpp b/src/linyaps_box/utils/session.cpp new file mode 100644 index 0000000..0687738 --- /dev/null +++ b/src/linyaps_box/utils/session.cpp @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/utils/session.h" + +#include + +namespace linyaps_box::utils { + +auto setsid() -> pid_t +{ + auto ret = ::setsid(); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "setsid"); + } + + return ret; +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/session.h b/src/linyaps_box/utils/session.h new file mode 100644 index 0000000..4a4aaa2 --- /dev/null +++ b/src/linyaps_box/utils/session.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace linyaps_box::utils { + +auto setsid() -> pid_t; + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/signal.cpp b/src/linyaps_box/utils/signal.cpp new file mode 100644 index 0000000..a0ef0be --- /dev/null +++ b/src/linyaps_box/utils/signal.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "linyaps_box/utils/signal.h" + +#include + +#include + +namespace linyaps_box::utils { + +auto sigfillset(sigset_t &set) -> void +{ + auto ret = ::sigfillset(&set); + if (ret < 0) { + throw std::system_error(errno, std::system_category(), "sigfillset"); + } +} + +auto sigismember(const sigset_t &set, int signo) -> bool +{ + auto ret = ::sigismember(&set, signo); + if (ret < 0) { + const std::string msg{ "failed to check signal " + std::to_string(signo) }; + throw std::system_error(errno, std::system_category(), msg); + } + + return ret == 1; +} + +auto sigprocmask(int how, const sigset_t &new_set, sigset_t *old_set) -> void +{ + auto ret = ::sigprocmask(how, &new_set, old_set); + if (ret < 0) { + throw std::system_error(errno, std::system_category(), "sigprocmask"); + } +} + +auto sigaction(int sig, const struct sigaction &new_act, struct sigaction *old_act) -> void +{ + auto ret = ::sigaction(sig, &new_act, old_act); + if (ret < 0) { + throw std::system_error(errno, std::system_category(), "sigaction"); + } +} + +auto reset_signals(const sigset_t &set) -> void +{ + struct sigaction act{}; + act.sa_handler = SIG_DFL; + + for (int sig = 1; sig <= SIGRTMAX; ++sig) { + if (!sigismember(set, sig)) { + continue; + } + + if (sig == SIGKILL || sig == SIGSTOP) { + continue; + } + + auto ret = sigaction(sig, &act, nullptr); + if (ret < 0) { + const std::string msg{ "failed to reset signal " + std::to_string(sig) }; + throw std::system_error(errno, std::system_category(), msg); + } + } +} + +auto create_signalfd(sigset_t &set, bool nonblock) -> file_descriptor +{ + unsigned flags = SFD_CLOEXEC; + if (nonblock) { + flags |= SFD_NONBLOCK; + } + + auto ret = ::signalfd(-1, &set, static_cast(flags)); + if (ret < 0) { + throw std::system_error(errno, std::system_category(), "signalfd"); + } + + return file_descriptor(ret); +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/signal.h b/src/linyaps_box/utils/signal.h new file mode 100644 index 0000000..05d5744 --- /dev/null +++ b/src/linyaps_box/utils/signal.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/utils/file_describer.h" + +#include // IWYU pragma: keep + +namespace linyaps_box::utils { + +auto sigfillset(sigset_t &set) -> void; + +auto sigismember(const sigset_t &set, int signo) -> bool; + +auto sigprocmask(int how, const sigset_t &new_set, sigset_t *old_set) -> void; + +auto sigaction(int sig, const struct sigaction &new_act, struct sigaction *old_act) -> void; + +auto reset_signals(const sigset_t &set) -> void; + +auto create_signalfd(sigset_t &set, bool nonblock = true) -> file_descriptor; + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/socketpair.cpp b/src/linyaps_box/utils/socket.cpp similarity index 52% rename from src/linyaps_box/utils/socketpair.cpp rename to src/linyaps_box/utils/socket.cpp index 85dd311..e8bdfdd 100644 --- a/src/linyaps_box/utils/socketpair.cpp +++ b/src/linyaps_box/utils/socket.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later -#include "linyaps_box/utils/socketpair.h" +#include "linyaps_box/utils/socket.h" #include #include @@ -22,4 +22,24 @@ auto socketpair(int domain, int type, int protocol) -> std::pair file_descriptor +{ + auto fd = ::socket(domain, type, protocol); + if (fd == -1) { + throw std::system_error(errno, + std::system_category(), + "socket(" + std::to_string(domain) + ", " + std::to_string(type) + + ", " + std::to_string(protocol) + ")"); + } + + return file_descriptor(fd); +} + +auto connect(const file_descriptor &fd, struct sockaddr *addr, socklen_t addrlen) -> void +{ + if (::connect(fd.get(), addr, addrlen) == -1) { + throw std::system_error(errno, std::system_category(), "connect"); + } +} + } // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/socketpair.h b/src/linyaps_box/utils/socket.h similarity index 70% rename from src/linyaps_box/utils/socketpair.h rename to src/linyaps_box/utils/socket.h index 0dba48c..53a3dc8 100644 --- a/src/linyaps_box/utils/socketpair.h +++ b/src/linyaps_box/utils/socket.h @@ -11,4 +11,8 @@ namespace linyaps_box::utils { auto socketpair(int domain, int type, int protocol) -> std::pair; +auto socket(int domain, int type, int protocol) -> file_descriptor; + +auto connect(const file_descriptor &fd, struct sockaddr *addr, socklen_t addrlen) -> void; + } // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/span.h b/src/linyaps_box/utils/span.h new file mode 100644 index 0000000..1475a62 --- /dev/null +++ b/src/linyaps_box/utils/span.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include + +namespace linyaps_box::utils { + +template +class span +{ +public: + using element_type = T; + using pointer = T *; + using size_type = std::size_t; + + constexpr span() noexcept + : data_(nullptr) + , size_(0) + { + } + + constexpr span(pointer ptr, size_type count) noexcept + : data_(ptr) + , size_(count) + { + } + + [[nodiscard]] constexpr pointer data() const noexcept { return data_; } + + [[nodiscard]] constexpr size_type size() const noexcept { return size_; } + + [[nodiscard]] constexpr bool empty() const noexcept { return size_ == 0; } + + [[nodiscard]] constexpr span subspan(size_type offset, size_type count = -1) const noexcept + { + if (offset >= size_) { + return {}; + } + + size_type actual_count = (count == static_cast(-1)) ? (size_ - offset) : count; + return { data_ + offset, std::min(actual_count, size_ - offset) }; + } + +private: + pointer data_; + size_type size_; +}; + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/symlink.cpp b/src/linyaps_box/utils/symlink.cpp index e22eee0..5e9ce98 100644 --- a/src/linyaps_box/utils/symlink.cpp +++ b/src/linyaps_box/utils/symlink.cpp @@ -5,6 +5,7 @@ #include "linyaps_box/utils/symlink.h" #include "linyaps_box/utils/log.h" +#include "linyaps_box/utils/platform.h" #include @@ -49,8 +50,15 @@ std::filesystem::path linyaps_box::utils::readlink(const std::filesystem::path & std::filesystem::path linyaps_box::utils::readlinkat(const file_descriptor &dirfd, const std::filesystem::path &path) { - std::array buf{}; - auto ret = ::readlinkat(dirfd.get(), path.c_str(), buf.data(), PATH_MAX + 1); + auto parent = path.parent_path(); + if (path.is_relative()) { + parent = dirfd.current_path() / parent; + } + + auto buf_len = get_path_max(parent) + 1; + std::string buf(buf_len, '\0'); + + auto ret = ::readlinkat(dirfd.get(), path.c_str(), buf.data(), buf_len - 1); if (ret == -1) { throw std::system_error(errno, std::system_category(), "readlinkat"); } diff --git a/src/linyaps_box/utils/terminal.cpp b/src/linyaps_box/utils/terminal.cpp new file mode 100644 index 0000000..7449dce --- /dev/null +++ b/src/linyaps_box/utils/terminal.cpp @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "linyaps_box/utils/terminal.h" + +#include "linyaps_box/utils/platform.h" + +namespace linyaps_box::utils { + +auto ptsname(const file_descriptor &pts) -> std::filesystem::path +{ + // The most of linux distros has /dev/pts + auto buf_len = get_path_max("/dev/pts") + 1; + std::string buf(buf_len, '\0'); + + auto ret = ::ptsname_r(pts.get(), buf.data(), buf_len - 1); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "ptsname_r"); + } + + return std::filesystem::path{ buf.data() }; +} + +auto unlockpt(const file_descriptor &pt) -> void +{ + auto ret = ::unlockpt(pt.get()); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "unlockpt"); + } +} + +auto tcgetattr(const file_descriptor &fd, struct termios &termios) -> void +{ + auto ret = ::tcgetattr(fd.get(), &termios); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "tcgetattr"); + } +} + +auto tcsetattr(const file_descriptor &fd, int action, const struct termios &termios) -> void +{ + auto ret = ::tcsetattr(fd.get(), action, &termios); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "tcsetattr"); + } +} + +auto isatty(const file_descriptor &fd) -> bool +{ + auto ret = ::isatty(fd.get()); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "isatty"); + } + + return ret == 1; +} + +auto fileno(FILE *stream) -> int +{ + auto ret = ::fileno(stream); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "fileno"); + } + + return ret; +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/terminal.h b/src/linyaps_box/utils/terminal.h new file mode 100644 index 0000000..85efb5a --- /dev/null +++ b/src/linyaps_box/utils/terminal.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/utils/file_describer.h" + +// for struct winsize +#include "linyaps_box/utils/ioctl.h" // IWYU pragma: keep + +#include + +namespace linyaps_box::utils { + +auto ptsname(const file_descriptor &pts) -> std::filesystem::path; + +auto unlockpt(const file_descriptor &pt) -> void; + +auto tcgetattr(const file_descriptor &fd, struct termios &termios) -> void; + +auto tcsetattr(const file_descriptor &fd, int action, const struct termios &termios) -> void; + +auto isatty(const file_descriptor &fd) -> bool; + +auto fileno(FILE *stream) -> int; + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/touch.cpp b/src/linyaps_box/utils/touch.cpp deleted file mode 100644 index becc89e..0000000 --- a/src/linyaps_box/utils/touch.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022-2025 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: LGPL-3.0-or-later - -#include "linyaps_box/utils/touch.h" - -#include "linyaps_box/utils/inspect.h" -#include "linyaps_box/utils/log.h" - -#include - -auto linyaps_box::utils::touch(const file_descriptor &root, - const std::filesystem::path &path, - int flag, - mode_t mode) -> linyaps_box::utils::file_descriptor -{ - LINYAPS_BOX_DEBUG() << "touch " << path << " at " << inspect_fd(root.get()); - const auto fd = ::openat(root.get(), path.c_str(), flag, mode); - if (fd == -1) { - throw std::system_error(errno, - std::system_category(), - "openat: " + (root.current_path() / path.relative_path()).string()); - } - - return linyaps_box::utils::file_descriptor{ fd }; -} diff --git a/src/linyaps_box/utils/touch.h b/src/linyaps_box/utils/touch.h deleted file mode 100644 index f1ffb0f..0000000 --- a/src/linyaps_box/utils/touch.h +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2022-2025 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: LGPL-3.0-or-later - -#pragma once - -#include "linyaps_box/utils/file_describer.h" - -#include - -namespace linyaps_box::utils { - -auto touch(const file_descriptor &root, - const std::filesystem::path &path, - int flag, - mode_t mode = 0644) -> file_descriptor; - -} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/utils.h b/src/linyaps_box/utils/utils.h new file mode 100644 index 0000000..a4777e5 --- /dev/null +++ b/src/linyaps_box/utils/utils.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include + +#if defined(__GNUC__) || defined(__clang__) +#define LIKELY(x) __builtin_expect((x), 1) +#define UNLIKELY(x) __builtin_expect((x), 0) +#else +#define LIKELY(x) (x) +#define UNLIKELY(x) (x) +#endif + +namespace linyaps_box::utils { + +template +struct type_entity +{ + using type = T; +}; + +template +constexpr auto get_n_params_type([[maybe_unused]] R (*ptr)(Args...)) +{ + static_assert(N >= 1 && N <= sizeof...(Args), "index out of range"); + return type_entity>>{}; +} + +template +constexpr auto get_n_params_type([[maybe_unused]] R (*ptr)(Args..., ...)) +{ + static_assert(N >= 1 && N <= sizeof...(Args), "index out of range"); + return type_entity>>{}; +} + +template +struct Overload : T... +{ + using T::operator()...; +}; + +template +Overload(T...) -> Overload; + +} // namespace linyaps_box::utils diff --git a/tests/ll-box-st/ll-box-st b/tests/ll-box-st/ll-box-st index 6f2a8cd..ba76061 100755 --- a/tests/ll-box-st/ll-box-st +++ b/tests/ll-box-st/ll-box-st @@ -190,7 +190,7 @@ function run_test() { $base.linux end) } - ' "${BUNDLE_DIR}/config.json" "${TEST_FILE}" > "${TEST_CONFIG}" + ' "${BUNDLE_DIR}/config.json" "${TEST_FILE}" >"${TEST_CONFIG}" local CONTAINER_NAME CONTAINER_NAME="$(basename -- "${TEST_FILE_NAME}" .json)" @@ -202,7 +202,7 @@ function run_test() { log "[INFO] ==== COMMAND OUTPUT START ====" local REAL REAL="$( - LINYAPS_BOX_LOG_FORCE_STDERR=1 "${TEST_COMMAND[@]}" && : + LINYAPS_BOX_LOG_FORCE_STDERR=1 "${TEST_COMMAND[@]}"