Compare commits

...

19 Commits

Author SHA1 Message Date
ComixHe 5a4eaff29f fix: explicitly cast MS_NOUSER to unsigned int
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-21 17:22:44 +08:00
ComixHe e12e421143 chore: Begin development of 2.1.3
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-21 16:28:37 +08:00
ComixHe caf2cec0d2 refactor: Add conditional compilation protection for SIGCLD signal
SIGCLD is a legacy signal in System V UNIX.
Mapping it with #ifdef SIGCLD in str_to_signal function
to improve cross-platform compatibility and avoid compilation errors
on platforms where SIGCLD is not defined (e.g. musl).

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-20 16:40:05 +08:00
ComixHe 59ed24f214 refactor: add <sys/types.h> for mode_t
- fix inconsistencies between function parameter implementations and declared types.
- change the default permission of touched file.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-20 16:40:05 +08:00
ComixHe 27c0ae9643 refactor: remove __S_ISTYPE for improving portability
__S_ISTYPE is glibc internal macro.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-20 16:40:05 +08:00
ComixHe 8f1b042421 refactor: change the type of mount flag to unsigned int
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-20 16:40:05 +08:00
ComixHe 613cc27af6 refactor: standardize preserve_fds type for better portability
Change preserve_fds from uint to int type

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-20 16:40:05 +08:00
ComixHe 56bb0d1e6b build: CPM is disabled by default
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-15 15:35:07 +08:00
ComixHe 72ca241a20 chore: update CPM.cmake to 0.42.0
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-15 15:35:07 +08:00
ComixHe 11face2e94 chore: formatting codes
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-13 11:28:59 +08:00
ComixHe 04aface617 chore(tools): add clang-format script for formatting C++ sources
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-13 11:28:59 +08:00
ComixHe 08b790480d feat: allow cn.org.linyaps.runtime.ns_last_pid extension failed
if /proc/sys/kernel/ns_last_pid doesn't exist, skip this extension.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-13 11:28:59 +08:00
deepsource-autofix[bot] 53f0e1e835 style: format code with ClangFormat and Prettier
This commit fixes the style issues introduced in 881bcc9 according to the output
from ClangFormat and Prettier.

Details: None
2025-10-13 10:57:55 +08:00
ComixHe 881bcc93c0 fix: move the one of sockpairs to child container process
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-10-09 15:20:07 +08:00
ComixHe 0e5def0fe2 refactor: change the default open flag
make O_PATH as the default flag.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-09-15 14:32:46 +08:00
ComixHe 9204ffba0c fix: use O_PATH flag when masking paths to avoid permission issues
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-09-15 14:21:39 +08:00
ComixHe 3b94fbcf43 fix(deps): upgrade CLI11 to 2.5.0 to resolve bug in argument parsing
See 7ff65c16f2

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-09-11 17:59:04 +08:00
ComixHe 4ad30a7efb fix: correct variable name
Fixes #115

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-09-05 14:16:53 +08:00
ComixHe 24a6a99f98 feat: implement preserve_fds support and fix file descriptor handling
- Add --preserve-fds command line option for runtime
- Refactor container creation with unified options structure
- Improve file_descriptor class API

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-09-01 14:13:21 +08:00
22 changed files with 448 additions and 133 deletions

View File

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11.4) # for RHEL 8
project(
linyaps-box
VERSION 2.1.0
VERSION 2.1.3
DESCRIPTION "A simple OCI runtime for desktop applications"
HOMEPAGE_URL "https://github.com/OpenAtom-Linyaps/linyaps-box"
LANGUAGES CXX)
@ -91,7 +91,7 @@ set(linyaps-box_ACTIVE_LOG_LEVEL
)
set(linyaps-box_ENABLE_CPM
ON
OFF
CACHE BOOL "enable CPM")
if(CMAKE_VERSION VERSION_LESS "3.14")
@ -105,11 +105,6 @@ set(linyaps-box_CPM_LOCAL_PACKAGES_ONLY
OFF
CACHE BOOL "use local packages only")
if(linyaps-box_CPM_LOCAL_PACKAGES_ONLY)
message(STATUS "CPM is disabled")
set(linyaps-box_ENABLE_CPM OFF)
endif()
# ==============================================================================
set(linyaps-box_LIBRARY linyaps-box)
@ -228,6 +223,10 @@ if(linyaps-box_ENABLE_CPM)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(CPM)
if (linyaps-box_CPM_LOCAL_PACKAGES_ONLY)
set(CPM_USE_LOCAL_PACKAGES ON)
endif()
CPMFindPackage(
NAME nlohmann_json
VERSION 3.11.3
@ -237,7 +236,7 @@ if(linyaps-box_ENABLE_CPM)
OPTIONS "JSON_BuildTests OFF")
CPMFindPackage(
NAME CLI11
VERSION 2.4.1
VERSION 2.5.0
GITHUB_REPOSITORY CLIUtils/CLI11
GIT_TAG v2.5.0
EXCLUDE_FROM_ALL ON
@ -256,12 +255,12 @@ endif()
list(APPEND linyaps-box_LIBRARY_LINK_LIBRARIES PUBLIC
nlohmann_json::nlohmann_json)
find_package(CLI11 2.4.1 QUIET)
find_package(CLI11 2.5.0 QUIET)
if(NOT CLI11_FOUND)
add_subdirectory(external/CLI11)
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_CURRENT_SOURCE_DIR}/cmake.external/CLI11")
find_package(CLI11 2.4.1 REQUIRED)
find_package(CLI11 2.5.0 REQUIRED)
message(STATUS "use vendor CLI11 ${CLI11_VERSION}")
endif()

View File

@ -1,5 +1,5 @@
{
"version": 6,
"version": 10,
"cmakeMinimumRequired": {
"major": 3,
"minor": 25,
@ -21,7 +21,8 @@
"linyaps-box_CPACK_PACKAGING_INSTALL_PREFIX": "",
"linyaps-box_ENABLE_SMOKE_TESTS": true,
"linyaps-box_DEFAULT_LOG_LEVEL": "7",
"linyaps-box_ACTIVE_LOG_LEVEL": "7"
"linyaps-box_ACTIVE_LOG_LEVEL": "7",
"linyaps-box_ENABLE_CPM": "ON"
}
},
{
@ -47,7 +48,9 @@
"CMAKE_COLOR_DIAGNOSTICS": true,
"CMAKE_CXX_FLAGS": "-fno-asynchronous-unwind-tables -fdata-sections -ffunction-sections -flto=auto $env{CXXFLAGS}",
"CMAKE_EXE_LINKER_FLAGS_INIT": "-Wl,--gc-sections,--strip-all,--exclude-libs,ALL -flto=auto",
"linyaps-box_STATIC": true
"linyaps-box_STATIC": true,
"linyaps-box_ENABLE_CPACK": "ON",
"linyaps-box_MAKE_RELEASE": "ON"
}
},
{

View File

@ -45,7 +45,7 @@ endif()
if(DEFINED EXTRACTED_CPM_VERSION)
set(CURRENT_CPM_VERSION "${EXTRACTED_CPM_VERSION}${CPM_DEVELOPMENT}")
else()
set(CURRENT_CPM_VERSION 0.40.8)
set(CURRENT_CPM_VERSION 0.42.0)
endif()
get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH)
@ -202,6 +202,60 @@ function(cpm_package_name_from_git_uri URI RESULT)
endif()
endfunction()
# Find the shortest hash that can be used eg, if origin_hash is
# cccb77ae9609d2768ed80dd42cec54f77b1f1455 the following files will be checked, until one is found
# that is either empty (allowing us to assign origin_hash), or whose contents matches ${origin_hash}
#
# * .../cccb.hash
# * .../cccb77ae.hash
# * .../cccb77ae9609.hash
# * .../cccb77ae9609d276.hash
# * etc
#
# We will be able to use a shorter path with very high probability, but in the (rare) event that the
# first couple characters collide, we will check longer and longer substrings.
function(cpm_get_shortest_hash source_cache_dir origin_hash short_hash_output_var)
# for compatibility with caches populated by a previous version of CPM, check if a directory using
# the full hash already exists
if(EXISTS "${source_cache_dir}/${origin_hash}")
set(${short_hash_output_var}
"${origin_hash}"
PARENT_SCOPE
)
return()
endif()
foreach(len RANGE 4 40 4)
string(SUBSTRING "${origin_hash}" 0 ${len} short_hash)
set(hash_lock ${source_cache_dir}/${short_hash}.lock)
set(hash_fp ${source_cache_dir}/${short_hash}.hash)
# Take a lock, so we don't have a race condition with another instance of cmake. We will release
# this lock when we can, however, if there is an error, we want to ensure it gets released on
# it's own on exit from the function.
file(LOCK ${hash_lock} GUARD FUNCTION)
# Load the contents of .../${short_hash}.hash
file(TOUCH ${hash_fp})
file(READ ${hash_fp} hash_fp_contents)
if(hash_fp_contents STREQUAL "")
# Write the origin hash
file(WRITE ${hash_fp} ${origin_hash})
file(LOCK ${hash_lock} RELEASE)
break()
elseif(hash_fp_contents STREQUAL origin_hash)
file(LOCK ${hash_lock} RELEASE)
break()
else()
file(LOCK ${hash_lock} RELEASE)
endif()
endforeach()
set(${short_hash_output_var}
"${short_hash}"
PARENT_SCOPE
)
endfunction()
# Try to infer package name and version from a url
function(cpm_package_name_and_ver_from_url url outName outVer)
if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)")
@ -594,14 +648,6 @@ endfunction()
function(CPMAddPackage)
cpm_set_policies()
list(LENGTH ARGN argnLength)
if(argnLength EQUAL 1)
cpm_parse_add_package_single_arg("${ARGN}" ARGN)
# The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM
set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;")
endif()
set(oneValueArgs
NAME
FORCE
@ -624,10 +670,26 @@ function(CPMAddPackage)
set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND PATCHES)
list(LENGTH ARGN argnLength)
# Parse single shorthand argument
if(argnLength EQUAL 1)
cpm_parse_add_package_single_arg("${ARGN}" ARGN)
# The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM
set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;")
# Parse URI shorthand argument
elseif(argnLength GREATER 1 AND "${ARGV0}" STREQUAL "URI")
list(REMOVE_AT ARGN 0 1) # remove "URI gh:<...>@version#tag"
cpm_parse_add_package_single_arg("${ARGV1}" ARGV0)
set(ARGN "${ARGV0};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;${ARGN}")
endif()
cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
# Set default values for arguments
if(NOT DEFINED CPM_ARGS_VERSION)
if(DEFINED CPM_ARGS_GIT_TAG)
cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION)
@ -798,9 +860,19 @@ function(CPMAddPackage)
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${CPM_ARGS_CUSTOM_CACHE_KEY})
elseif(CPM_USE_NAMED_CACHE_DIRECTORIES)
string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG")
cpm_get_shortest_hash(
"${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory
"${origin_hash}" # Input hash
origin_hash # Computed hash
)
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME})
else()
string(SHA1 origin_hash "${origin_parameters}")
cpm_get_shortest_hash(
"${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory
"${origin_hash}" # Input hash
origin_hash # Computed hash
)
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash})
endif()
# Expand `download_directory` relative path. This is important because EXISTS doesn't work for

View File

@ -61,6 +61,10 @@ linyaps_box::command::options linyaps_box::command::parse(int argc, char *argv[]
cmd_run->add_option("-b,--bundle", run_opt.bundle, "Path to the OCI bundle")->default_val(".");
cmd_run->add_option("-f,--config", run_opt.config, "Override the configuration file to use")
->default_val("config.json");
cmd_run->add_option("--preserve-fds",
run_opt.preserve_fds,
"Pass N additional file descriptors to the container")
->default_val(0);
exec_options exec_opt{ options.global };
auto *cmd_exec = app.add_subcommand("exec", "Exec a command in a running container")

View File

@ -62,6 +62,7 @@ struct run_options
std::string ID;
std::string bundle;
std::string config;
int preserve_fds;
};
struct kill_options

View File

@ -13,13 +13,12 @@ auto linyaps_box::command::run(const struct run_options &options) -> int
std::unique_ptr<status_directory> dir =
std::make_unique<impl::status_directory>(options.global_.get().root);
runtime_t runtime(std::move(dir));
runtime_t::create_container_options_t create_container_options;
create_container_options.bundle = options.bundle;
create_container_options.config = options.config;
create_container_options.ID = options.ID;
create_container_options.manager = options.global_.get().manager;
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);
}

View File

@ -121,37 +121,42 @@ std::ostream &operator<<(std::ostream &os, const sync_message message)
struct MountFlag
{
std::underlying_type_t<decltype(MS_RDONLY)> flag;
unsigned int flag;
std::string_view name;
};
constexpr std::array<MountFlag, 27> mount_flags{ MountFlag{ MS_RDONLY, "MS_RDONLY" },
{ MS_NOSUID, "MS_NOSUID" },
{ MS_NODEV, "MS_NODEV" },
{ MS_NOEXEC, "MS_NOEXEC" },
{ MS_SYNCHRONOUS, "MS_SYNCHRONOUS" },
{ MS_REMOUNT, "MS_REMOUNT" },
{ MS_MANDLOCK, "MS_MANDLOCK" },
{ MS_DIRSYNC, "MS_DIRSYNC" },
{ LINGYAPS_MS_NOSYMFOLLOW, "MS_NOSYMFOLLOW" },
{ MS_NOATIME, "MS_NOATIME" },
{ MS_NODIRATIME, "MS_NODIRATIME" },
{ MS_BIND, "MS_BIND" },
{ MS_MOVE, "MS_MOVE" },
{ MS_REC, "MS_REC" },
{ MS_SILENT, "MS_SILENT" },
{ MS_POSIXACL, "MS_POSIXACL" },
{ MS_UNBINDABLE, "MS_UNBINDABLE" },
{ MS_PRIVATE, "MS_PRIVATE" },
{ MS_SLAVE, "MS_SLAVE" },
{ MS_SHARED, "MS_SHARED" },
{ MS_RELATIME, "MS_RELATIME" },
{ MS_KERNMOUNT, "MS_KERNMOUNT" },
{ MS_I_VERSION, "MS_I_VERSION" },
{ MS_STRICTATIME, "MS_STRICTATIME" },
{ MS_LAZYTIME, "MS_LAZYTIME" },
{ MS_ACTIVE, "MS_ACTIVE" },
{ MS_NOUSER, "MS_NOUSER" } };
constexpr std::array<MountFlag, 27> mount_flags{
MountFlag{ MS_RDONLY, "MS_RDONLY" },
{ MS_NOSUID, "MS_NOSUID" },
{ MS_NODEV, "MS_NODEV" },
{ MS_NOEXEC, "MS_NOEXEC" },
{ MS_SYNCHRONOUS, "MS_SYNCHRONOUS" },
{ MS_REMOUNT, "MS_REMOUNT" },
{ MS_MANDLOCK, "MS_MANDLOCK" },
{ MS_DIRSYNC, "MS_DIRSYNC" },
{ LINGYAPS_MS_NOSYMFOLLOW, "MS_NOSYMFOLLOW" },
{ MS_NOATIME, "MS_NOATIME" },
{ MS_NODIRATIME, "MS_NODIRATIME" },
{ MS_BIND, "MS_BIND" },
{ MS_MOVE, "MS_MOVE" },
{ MS_REC, "MS_REC" },
{ MS_SILENT, "MS_SILENT" },
{ MS_POSIXACL, "MS_POSIXACL" },
{ MS_UNBINDABLE, "MS_UNBINDABLE" },
{ MS_PRIVATE, "MS_PRIVATE" },
{ MS_SLAVE, "MS_SLAVE" },
{ MS_SHARED, "MS_SHARED" },
{ MS_RELATIME, "MS_RELATIME" },
{ MS_KERNMOUNT, "MS_KERNMOUNT" },
{ MS_I_VERSION, "MS_I_VERSION" },
{ MS_STRICTATIME, "MS_STRICTATIME" },
{ MS_LAZYTIME, "MS_LAZYTIME" },
{ MS_ACTIVE, "MS_ACTIVE" },
// MS_NOUSER will be overflowed before 2.42.9000
// refer:
// https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=3263675250cbcbbcc76ede4f7c660418bd345a11;hp=cd335350021fd0b7ac533c83717ee38832fd9887
{ static_cast<unsigned int>(MS_NOUSER), "MS_NOUSER" }
};
[[maybe_unused]] auto dump_mount_flags(unsigned long flags) noexcept -> std::string
{
@ -318,6 +323,7 @@ void syscall_mount(const char *_special_file,
if (_dir == nullptr) {
return "nullptr";
}
if (auto str = std::string_view{ _dir }; str.rfind(fd_prefix, 0) == 0) {
return linyaps_box::utils::inspect_fd(std::stoi(str.data() + fd_prefix.size()));
}
@ -790,6 +796,8 @@ public:
return;
}
LINYAPS_BOX_DEBUG() << "make readonly paths";
for (const auto &path : *linux->readonly_paths) {
linyaps_box::utils::file_descriptor dst;
try {
@ -836,10 +844,14 @@ public:
return;
}
LINYAPS_BOX_DEBUG() << "make masked paths";
for (const auto &path : *linux->masked_paths) {
linyaps_box::utils::file_descriptor dst;
try {
dst = linyaps_box::utils::open_at(root, path);
// we only need to open a fd to refer to the path
// so O_PATH is sufficient.
dst = linyaps_box::utils::open_at(root, path, O_PATH | O_CLOEXEC);
} catch (const std::system_error &e) {
if (auto err = e.code().value(); err == ENOENT || err == EACCES) {
continue;
@ -1025,14 +1037,16 @@ private:
void configure_device(const std::filesystem::path &destination,
mode_t mode,
int type,
std::filesystem::file_type type,
dev_t dev,
uid_t uid,
gid_t gid)
{
assert(destination.is_absolute());
if (type != S_IFCHR && type != S_IFBLK && type != S_IFIFO) {
if (type != std::filesystem::file_type::character
&& type != std::filesystem::file_type::block
&& type != std::filesystem::file_type::fifo) {
throw std::runtime_error("unsupported device type");
}
@ -1048,27 +1062,13 @@ private:
if (destination_fd.has_value()) {
// if already exists, check if it is a required device
auto stat = linyaps_box::utils::lstatat(*destination_fd, "");
auto cur_type = linyaps_box::utils::to_fs_file_type(stat.st_mode);
bool satisfied{ true };
if (__S_ISTYPE(stat.st_mode, type)) {
auto dump_mode = [](mode_t mode) {
if (S_ISCHR(mode)) {
return "Character";
}
if (S_ISBLK(mode)) {
return "Block";
}
if (S_ISFIFO(mode)) {
return "FIFO";
}
return "unknown";
};
if (linyaps_box::utils::is_type(stat.st_mode, type)) {
LINYAPS_BOX_DEBUG()
<< "the type of existing device: " << destination << " is not required\n"
<< "expect " << dump_mode(mode) << ", got " << dump_mode(stat.st_mode);
<< "expect " << linyaps_box::utils::to_string(type) << ", got "
<< linyaps_box::utils::to_string(cur_type);
satisfied = false;
}
@ -1098,7 +1098,9 @@ private:
try {
auto path = destination.relative_path();
linyaps_box::utils::mknodat(root, path, mode | type, dev);
auto f_type = static_cast<unsigned int>(linyaps_box::utils::to_linux_file_type(type));
linyaps_box::utils::mknodat(root, path, mode | f_type, dev);
auto new_dev = linyaps_box::utils::open_at(root, path, O_PATH);
path = new_dev.proc_path();
@ -1131,14 +1133,16 @@ private:
LINYAPS_BOX_DEBUG() << "Configure default devices";
constexpr auto default_mode = 0666;
constexpr auto default_type = std::filesystem::file_type::character;
auto uid = container.get_config().process.user.uid;
auto gid = container.get_config().process.user.gid;
this->configure_device("/dev/null", default_mode, S_IFCHR, makedev(1, 3), uid, gid);
this->configure_device("/dev/zero", default_mode, S_IFCHR, makedev(1, 5), uid, gid);
this->configure_device("/dev/full", default_mode, S_IFCHR, makedev(1, 7), uid, gid);
this->configure_device("/dev/random", default_mode, S_IFCHR, makedev(1, 8), uid, gid);
this->configure_device("/dev/urandom", default_mode, S_IFCHR, makedev(1, 9), uid, gid);
this->configure_device("/dev/tty", default_mode, S_IFCHR, makedev(5, 0), uid, gid);
this->configure_device("/dev/null", default_mode, default_type, makedev(1, 3), uid, gid);
this->configure_device("/dev/zero", default_mode, default_type, makedev(1, 5), uid, gid);
this->configure_device("/dev/full", default_mode, default_type, makedev(1, 7), uid, gid);
this->configure_device("/dev/random", default_mode, default_type, makedev(1, 8), uid, gid);
this->configure_device("/dev/urandom", default_mode, default_type, makedev(1, 9), uid, gid);
this->configure_device("/dev/tty", default_mode, default_type, makedev(5, 0), uid, gid);
// bind mount /dev/pts/ptmx to /dev/ptmx
// https://docs.kernel.org/filesystems/devpts.html
@ -1587,8 +1591,13 @@ void processing_extensions(const linyaps_box::container &container)
LINYAPS_BOX_DEBUG() << "Processing container extensions";
// ext_ns_last_pid
if (auto it = config.annotations->find("cn.org.linyaps.runtime.ns_last_pid");
it != config.annotations->end()) {
// This file may not exist if the kernel config CONFIG_CHECKPOINT_RESTORE is not enabled
// and this feature originally was used for userspace checkpoint/restore
// we use this feature for avoiding two process has the same pid.
// e.g some application will register a tray through dbus and use the pid as the part of
// dbus object path, if two process has the same pid, the dbus object path will conflict
auto it = config.annotations->find("cn.org.linyaps.runtime.ns_last_pid");
while (it != config.annotations->end()) {
LINYAPS_BOX_DEBUG() << "Processing ns_last_pid extension: " << it->second;
// Validate input is a valid pid_t number
@ -1606,7 +1615,13 @@ void processing_extensions(const linyaps_box::container &container)
throw std::runtime_error("parse ns_last_pid " + it->second + " failed: " + e.what());
}
std::ofstream ofs("/proc/sys/kernel/ns_last_pid");
// ignore ns_last_pid if the file does not exist
auto ns_last_pid = std::filesystem::path{ "/proc/sys/kernel/ns_last_pid" };
if (!std::filesystem::exists(ns_last_pid)) {
break;
}
std::ofstream ofs(ns_last_pid);
if (!ofs) {
throw std::system_error(errno,
std::generic_category(),
@ -1621,6 +1636,7 @@ void processing_extensions(const linyaps_box::container &container)
}
LINYAPS_BOX_DEBUG() << "Successfully set ns_last_pid to " << it->second;
break;
}
LINYAPS_BOX_DEBUG() << "Container extensions processing completed";
@ -1656,10 +1672,16 @@ try {
auto &args = *static_cast<clone_fn_args *>(data);
assert(args.socket.get() >= 0);
close_other_fds({ STDIN_FILENO,
STDOUT_FILENO,
STDERR_FILENO,
static_cast<unsigned int>(args.socket.get()) });
std::set<uint> except_fds{
STDIN_FILENO,
STDOUT_FILENO,
STDERR_FILENO,
};
for (auto fd = 0; fd < args.container->preserve_fds(); ++fd) {
except_fds.insert(fd + 3);
}
except_fds.insert(static_cast<unsigned int>(args.socket.get()));
close_other_fds(std::move(except_fds));
const auto &container = *args.container;
const auto &process = *args.process;
@ -1667,6 +1689,7 @@ try {
auto rootfs = container.get_config().root.path;
if (rootfs.is_relative()) {
LINYAPS_BOX_DEBUG() << "rootfs is relative based on bundle path:" << container.get_bundle();
rootfs = std::filesystem::canonical(container.get_bundle() / rootfs);
}
@ -1837,14 +1860,14 @@ std::tuple<int, linyaps_box::utils::file_descriptor> start_container_process(
namespaces = config.linux->namespaces;
}
int clone_flag = runtime_ns::generate_clone_flag(namespaces);
const int clone_flag = runtime_ns::generate_clone_flag(namespaces);
clone_fn_args args = { &container, &process, std::move(sockets.second) };
LINYAPS_BOX_DEBUG() << "OCI runtime in runtime namespace: PID=" << getpid()
<< " PIDNS=" << linyaps_box::utils::get_pid_namespace();
child_stack stack;
int child_pid = clone(container_ns::clone_fn, stack.top(), clone_flag, (void *)&args);
const child_stack stack;
const int child_pid = clone(container_ns::clone_fn, stack.top(), clone_flag, (void *)&args);
if (child_pid < 0) {
throw std::runtime_error("clone failed");
}
@ -2307,14 +2330,13 @@ void poststop_hooks(const linyaps_box::container &container) noexcept
} // namespace
linyaps_box::container::container(const status_directory &status_dir,
const std::string &id,
const std::filesystem::path &bundle,
std::filesystem::path config,
cgroup_manager_t manager)
: container_ref(status_dir, id)
, bundle(bundle)
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)
{
auto config = options.config;
if (config.is_relative()) {
config = bundle / config;
}
@ -2340,7 +2362,7 @@ linyaps_box::container::container(const status_directory &status_dir,
{
container_status_t status;
status.oci_version = linyaps_box::config::oci_version;
status.ID = id;
status.ID = options.ID;
status.PID = getpid();
status.status = container_status_t::runtime_status::CREATING;
status.bundle = bundle;
@ -2353,7 +2375,7 @@ linyaps_box::container::container(const status_directory &status_dir,
this->status_dir().write(status);
}
switch (manager) {
switch (options.manager) {
case cgroup_manager_t::disabled: {
this->manager = std::make_unique<disabled_cgroup_manager>();
} break;

View File

@ -13,14 +13,19 @@ namespace linyaps_box {
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;
};
class container final : public container_ref
{
public:
container(const status_directory &status_dir,
const std::string &id,
const std::filesystem::path &bundle,
std::filesystem::path config,
cgroup_manager_t manager);
container(const status_directory &status_dir, const create_container_options_t &options);
container(const container &) = delete;
auto operator=(const container &) -> container & = delete;
@ -38,14 +43,17 @@ public:
[[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);
std::filesystem::path bundle;
linyaps_box::config config;
std::unique_ptr<cgroup_manager> manager;
int preserve_fds_;
gid_t host_gid_;
uid_t host_uid_;
container_data *data{ nullptr };
std::filesystem::path bundle;
std::unique_ptr<cgroup_manager> manager;
linyaps_box::config config;
};
} // namespace linyaps_box

View File

@ -27,8 +27,8 @@ auto linyaps_box::runtime_t::containers()
return containers;
}
auto linyaps_box::runtime_t::create_container(
const linyaps_box::runtime_t::create_container_options_t &options) -> linyaps_box::container
auto linyaps_box::runtime_t::create_container(const create_container_options_t &options)
-> linyaps_box::container
{
return { *this->status_dir_, options.ID, options.bundle, options.config, options.manager };
return { *this->status_dir_, options };
}

View File

@ -19,14 +19,6 @@ public:
explicit runtime_t(std::unique_ptr<status_directory> &&status_dir);
auto containers() -> std::unordered_map<std::string, container_ref>;
struct create_container_options_t
{
std::filesystem::path bundle;
std::filesystem::path config;
std::string ID;
cgroup_manager_t manager;
};
auto create_container(const create_container_options_t &options) -> container;
private:

View File

@ -48,12 +48,16 @@ linyaps_box::utils::file_descriptor::file_descriptor(file_descriptor &&other) no
*this = std::move(other);
}
auto linyaps_box::utils::file_descriptor::release() && noexcept -> int
auto linyaps_box::utils::file_descriptor::release() -> void
{
int ret = -1;
std::swap(ret, fd_);
return ret;
if (::close(ret) < 0) {
auto msg{ "failed to close file descriptor " + std::to_string(ret) + ": "
+ ::strerror(errno) };
throw file_descriptor_invalid_exception(msg);
}
}
auto linyaps_box::utils::file_descriptor::get() const noexcept -> int

View File

@ -47,12 +47,11 @@ public:
auto operator=(const file_descriptor &) -> file_descriptor & = delete;
file_descriptor(file_descriptor &&other) noexcept;
auto operator=(file_descriptor &&other) noexcept -> file_descriptor &;
[[nodiscard]] auto get() const noexcept -> int;
auto release() && noexcept -> int;
auto release() -> void;
[[nodiscard]] auto duplicate() const -> file_descriptor;

View File

@ -4,6 +4,10 @@
#include "linyaps_box/utils/fstat.h"
#include "linyaps_box/utils/log.h"
#include <cassert>
namespace linyaps_box::utils {
auto fstatat(const file_descriptor &fd, std::filesystem::path path, int flag) -> struct stat
@ -46,5 +50,114 @@ auto statfs(const file_descriptor &fd) -> struct 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<int>(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

View File

@ -16,4 +16,10 @@ auto fstatat(const file_descriptor &fd, std::filesystem::path path, int flag) ->
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

View File

@ -6,6 +6,8 @@
#include "linyaps_box/utils/file_describer.h"
#include <sys/types.h>
namespace linyaps_box::utils {
auto mkdir(const file_descriptor &root, std::filesystem::path path, mode_t mode = 0755)

View File

@ -8,6 +8,8 @@
#include <filesystem>
#include <sys/types.h>
namespace linyaps_box::utils {
void mknodat(const file_descriptor &root,
const std::filesystem::path &path,

View File

@ -27,7 +27,7 @@ namespace {
auto open_at_fallback(const linyaps_box::utils::file_descriptor &root,
const std::filesystem::path &path,
int flag,
uint mode) -> linyaps_box::utils::file_descriptor
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(

View File

@ -12,12 +12,12 @@
namespace linyaps_box::utils {
auto open(const std::filesystem::path &path, int flag = O_RDONLY, mode_t mode = 0)
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_RDONLY,
int flag = O_PATH | O_CLOEXEC,
mode_t mode = 0) -> file_descriptor;
} // namespace linyaps_box::utils

View File

@ -26,7 +26,10 @@ auto str_to_signal(std::string_view str) -> int
{ "SIGTTIN", SIGTTIN }, { "SIGTTOU", SIGTTOU }, { "SIGURG", SIGURG },
{ "SIGUSR1", SIGUSR1 }, { "SIGUSR2", SIGUSR2 }, { "SIGVTALRM", SIGVTALRM },
{ "SIGWINCH", SIGWINCH }, { "SIGXCPU", SIGXCPU }, { "SIGXFSZ", SIGXFSZ },
{ "SIGIO", SIGIO }, { "SIGIOT", SIGIOT }, { "SIGCLD", SIGCLD },
{ "SIGIO", SIGIO }, { "SIGIOT", SIGIOT },
#ifdef SIGCLD
{ "SIGCLD", SIGCLD },
#endif
};
auto it = sigMap.find(str);

View File

@ -6,11 +6,13 @@
#include "linyaps_box/utils/file_describer.h"
#include <sys/types.h>
namespace linyaps_box::utils {
auto touch(const file_descriptor &root,
const std::filesystem::path &path,
int flag,
mode_t mode = 0700) -> file_descriptor;
mode_t mode = 0644) -> file_descriptor;
} // namespace linyaps_box::utils

View File

@ -22,6 +22,7 @@
#pragma GCC diagnostic pop
#endif
TEST(LINYAPS_BOX, Placeholder1) {
EXPECT_EQ(1, 1);
TEST(LINYAPS_BOX, Placeholder1)
{
EXPECT_EQ(1, 1);
}

83
tools/format.sh Executable file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2022-2025 UnionTech Software Technology Co., Ltd.
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# tools/format.sh
# Format all C/C++ source files in the project using clang-format.
#
# Usage:
# ./tools/format.sh # Use system default clang-format
# ./tools/format.sh clang-format-17 # Use a specific clang-format binary
#
# The script automatically skips common build and third-party directories.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
CLANG_FORMAT="${1:-clang-format}"
# Check clang-format availability
if ! command -v "${CLANG_FORMAT}" >/dev/null 2>&1; then
echo "Error: ${CLANG_FORMAT} not found. Please install clang-format." >&2
exit 1
fi
# --- Project root validation ---
# Criteria for identifying a valid project root:
# 1. Has CMakeLists.txt, or
# 2. Has .clang-format, or
# 3. Contains app/, src/, or tests/ directories
VALID_ROOT=false
if [[ -f "${ROOT_DIR}/CMakeLists.txt" ]]; then
VALID_ROOT=true
elif [[ -f "${ROOT_DIR}/.clang-format" ]]; then
VALID_ROOT=true
else
for d in app src tests; do
if [ -d "${ROOT_DIR}/${d}" ]; then
VALID_ROOT=true
break
fi
done
fi
if [[ ${VALID_ROOT} != true ]]; then
echo "Error: This script must be run inside a valid C++ project root."
echo "Expected to find one of the following in ${ROOT_DIR}:"
echo " - CMakeLists.txt"
echo " - .clang-format or _clang-format"
echo " - app/, src/, or tests/ directories"
exit 1
fi
# --- End project root validation ---
CLANG_FORMAT_VERSION=$("${CLANG_FORMAT}" --version | head -n 1 | cut -d ' ' -f 3)
echo "Using clang-format: ${CLANG_FORMAT_VERSION}"
echo "Project root: ${ROOT_DIR}"
# Only search for .cpp and .h files under app, src, and tests directories
dirs=()
for d in app src tests; do
if [[ -d "${ROOT_DIR}/${d}" ]]; then
dirs+=("${ROOT_DIR}/${d}")
fi
done
if [[ ${#dirs[@]} -eq 0 ]]; then
echo "Warning: No app/, src/, or tests/ directories found under ${ROOT_DIR}."
exit 0
fi
find "${dirs[@]}" \
\( -name "*.cpp" -o -name "*.h" \) \
-type f -print |
while read -r file; do
echo "Formatting: ${file}"
"${CLANG_FORMAT}" -i "${file}"
done
echo "Formatting completed."