Compare commits

...

35 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
ComixHe b1c7fa13d8 fix: handle broken symlinks during mount destination creation
When a destination file doesn't exist, the previous implementation couldn't
distinguish between creation failure due to existing files vs other reasons.
If both opening and creating fail, the destination is definitely a broken symlink.

- Add recursive symlink resolution with depth limit (32) in create_destination_file()
- Use O_NOFOLLOW to detect symlink during file creation
- When creation fails with ELOOP, read symlink target and recursively create it
- Replace filesystem functions with internal utils for consistent error handling
- Add proper broken symlink detection and resolution logic

This ensures mount destinations work correctly even when they point to
broken symlinks, by creating the missing target files in the symlink chain.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-08-29 17:51:36 +08:00
ComixHe 08c47fff3c refactor: resolving the most of compiler warnings
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-08-26 14:45:06 +08:00
ComixHe e62d7d53e4 fix: early return if an exception is thrown while command parsing
According the documentation of CLI11, this library will throw a exception
while encounter -h,--help or a parse error.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-08-25 10:44:11 +08:00
ComixHe 2b51656a6e chore: suppress compiler warning
If the log level is higher than debug level
the dump_mount_flags function will not be called.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-08-25 10:29:21 +08:00
taotieren 2a8c21b681 docs: Update linyaps-box documentation
1.  Update linyaps-box package status

docs: 更新 linyaps-box文档

1.  更新 linyaps-box 软件包状态
2025-08-20 15:27:24 +08:00
ComixHe 61d1b26eae feat: add ns_last_pid extension test and enhance test framework
- Add comprehensive test for ns_last_pid extension
- Improve test script JSON merging capabilities
- Update existing tests and CMake configuration

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-08-06 11:53:44 +08:00
ComixHe 53883d5bf4 improve: add system error details to file operation error handling
Replace std::runtime_error with std::system_error for better debugging.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-08-06 11:53:44 +08:00
ComixHe 55a147033b chore: bump version to 2.1.0
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-08-06 10:59:45 +08:00
ComixHe 308b80bacf feat: add rootfsPropagation support and improve error handling
- Add rootfsPropagation field to linux config (shared/slave/private/unbindable)
- Fix mount propagation flag handling (use |= instead of &=)
- Replace std::cerr with LINYAPS_BOX_ERR() for consistent logging
- Use _exit() instead of exit() in child processes
- Improve error messages with better context

Allows control over mount propagation for the root filesystem according
to OCI spec.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-08-05 15:43:50 +08:00
ComixHe 16f40a416a fix: resolve GCC enum conversion error in MountFlag
Use std::underlying_type_t to fix "int cannot be converted to unnamed enum"
compilation error on older GCC versions.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-07-23 13:47:39 +08:00
ComixHe 3951cfc9b2 fix: some warnings
- non-const global variable
- avoid array-to-pointer decay

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-07-23 13:23:47 +08:00
ComixHe 0bb8d07bb2 feat: add runtime extension 'cn.org.linyaps.runtime.ns_last_pid'
Add support for the 'cn.org.linyaps.runtime.ns_last_pid' runtime extension
that allows setting the last PID in the namespace during container startup.

Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-07-23 13:23:47 +08:00
ComixHe ee4e07a827 refactor: resolve some warnings from static check
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-07-23 11:38:07 +08:00
ComixHe e31f62f382 build: specifying the internal library type explicitly
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-07-21 10:17:01 +08:00
dengbo 000fa7ff73 chore: add deepsource config
Add deepsource config.
2025-07-18 14:50:46 +08:00
ComixHe 1fd78ccf1a chore: update CMake preset
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-07-18 11:15:47 +08:00
71 changed files with 1336 additions and 602 deletions

76
.deepsource.toml Normal file
View File

@ -0,0 +1,76 @@
# SPDX-FileCopyrightText: None
#
# SPDX-License-Identifier: CC0-1.0
version = 1
# 全局排除配置
exclude_patterns = [
"external/**",
"build/**",
"obj-*/**",
".cache/**",
".obs/**",
".tx/**",
"**/*.generated.*",
"**/CMakeCache.txt",
"**/compile_commands.json",
"debian/patches/**",
"po/*.po",
"po/*.pot",
"tools/openapi-c-libcurl-client/**",
]
# C++ 分析器配置
[[analyzers]]
name = "cxx"
enabled = true
[analyzers.meta]
misra_compliance = true
cyclomatic_complexity_threshold = "medium"
# Shell 脚本分析器配置
[[analyzers]]
name = "shell"
enabled = true
[analyzers.meta]
# 只保留官方支持的选项
dialect = "bash"
# 密钥检测分析器配置
[[analyzers]]
name = "secrets"
enabled = true
# secrets 分析器不支持任何 meta 配置
# 测试覆盖率分析器配置
[[analyzers]]
name = "test-coverage"
enabled = true
# 代码格式化器配置
[[transformers]]
name = "clang-format"
enabled = true
[transformers.meta]
exclude_patterns = ["external/**", "build/**", "obj-*/**", "**/*.generated.*"]
[[transformers]]
name = "prettier"
enabled = true
[transformers.meta]
include_patterns = ["**/*.md", "**/*.json", "**/*.yaml", "**/*.yml"]
exclude_patterns = [
"external/**",
"build/**",
"obj-*/**",
".cache/**",
"po/**",
"**/*.generated.*",
"package-lock.json",
"yarn.lock",
]

View File

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11.4) # for RHEL 8
project(
linyaps-box
VERSION 2.0.3
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)
@ -118,7 +113,7 @@ set(linyaps-box_LIBRARY_SOURCE
src/linyaps_box/app.cpp
src/linyaps_box/app.h
src/linyaps_box/cgroup.h
src/linyaps_box/cgroup_manager.c
src/linyaps_box/cgroup_manager.cpp
src/linyaps_box/cgroup_manager.h
src/linyaps_box/command/exec.cpp
src/linyaps_box/command/exec.h
@ -195,8 +190,7 @@ if(NOT linyaps-box_MAKE_RELEASE)
COMMAND git rev-parse --short=7 HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
OUTPUT_STRIP_TRAILING_WHITESPACE)
set(LINYAPS_BOX_VERSION "${LINYAPS_BOX_VERSION}-dev-${GIT_COMMIT_HASH}")
endif()
@ -229,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
@ -238,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
@ -257,18 +255,18 @@ 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()
list(APPEND linyaps-box_LIBRARY_LINK_LIBRARIES PUBLIC CLI11::CLI11)
add_library("${linyaps-box_LIBRARY}" ${linyaps-box_LIBRARY_SOURCE})
add_library("${linyaps-box_LIBRARY}" STATIC ${linyaps-box_LIBRARY_SOURCE})
target_include_directories("${linyaps-box_LIBRARY}"
${linyaps-box_LIBRARY_INCLUDE_DIRS})
target_link_libraries("${linyaps-box_LIBRARY}"
@ -438,7 +436,8 @@ function(setup_linyaps_box_smoke_tests)
./tests/ll-box-st/09-check-rlimit.json
./tests/ll-box-st/10-check-oom.json
./tests/ll-box-st/11-output-to-null.json
./tests/ll-box-st/12-bind-host-dev.json)
./tests/ll-box-st/12-bind-host-dev.json
./tests/ll-box-st/13-pid-extension.json)
foreach(test ${linyaps-box_SMOKE_TESTS})
add_test(

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"
}
},
{
@ -33,7 +34,8 @@
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_COLOR_DIAGNOSTICS": true,
"CMAKE_EXPORT_COMPILE_COMMANDS": true,
"linyaps-box_ENABLE_CPACK": "ON"
"linyaps-box_ENABLE_CPACK": "ON",
"linyaps-box_MAKE_RELEASE": "ON"
}
},
{
@ -46,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

@ -2,6 +2,8 @@
\[ **en** | [zh_CN](./README.zh_CN.md) \]
[![Packaging status](https://repology.org/badge/vertical-allrepos/linyaps-box.svg)](https://repology.org/project/linyaps-box/versions)
This project is a simple [OCI runtime] mainly used by [linyaps],
which is a toolkit for Linux desktop application distributing.

View File

@ -2,6 +2,8 @@
\[ [en](./README.md) | **zh_CN** \]
[![Packaging status](https://repology.org/badge/vertical-allrepos/linyaps-box.svg)](https://repology.org/project/linyaps-box/versions)
这个项目是一个简单的[OCI运行时],主要由[玲珑]使用,
玲珑是一个用于Linux桌面应用程序分发的工具包。

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

@ -9,8 +9,7 @@
#include "linyaps_box/command/list.h"
#include "linyaps_box/command/run.h"
#include "linyaps_box/utils/log.h"
#include <iostream>
#include "utils/log.h"
namespace {
@ -32,19 +31,19 @@ namespace linyaps_box {
// Command line arguments are parsed according to
// https://github.com/opencontainers/runtime-tools/blob/v0.9.0/docs/command-line-interface.md
// Extended commands and options should be compatible with crun.
int main(int argc, char **argv) noexcept
auto main(int argc, char **argv) noexcept -> int
try {
LINYAPS_BOX_DEBUG() << "linyaps box called with" << [=]() -> std::string {
std::stringstream result;
for (int i = 0; i < argc; ++i) {
result << " \"";
for (char *c = argv[i]; *c; ++c) {
if (*c == '\\') {
for (const auto ch : std::string_view(argv[i])) { // NOLINT
if (ch == '\\') {
result << "\\\\";
} else if (*c == '"') {
} else if (ch == '"') {
result << "\\\"";
} else {
result << *c;
result << ch;
}
}
result << "\"";
@ -52,18 +51,18 @@ try {
return result.str();
}();
command::options options = command::parse(argc, argv);
if (options.global.return_code != 0) {
return options.global.return_code;
auto opts = command::parse(argc, argv);
if (opts.global.return_code != 0) {
return opts.global.return_code;
}
return std::visit(subCommand{ [](const command::list_options &options) {
command::list(options);
return 0;
},
[](const command::exec_options &options) {
[](const command::exec_options &options) -> int {
command::exec(options);
return 0;
__builtin_unreachable();
},
[](const command::kill_options &options) {
command::kill(options);
@ -72,15 +71,15 @@ try {
[](const command::run_options &options) {
return command::run(options);
},
[code = options.global.return_code](const std::monostate &) {
return code;
[](const std::monostate &) {
return 0;
} },
options.subcommand_opt);
opts.subcommand_opt);
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
LINYAPS_BOX_ERR() << "Error: " << e.what();
return -1;
} catch (...) {
std::cerr << "Error: unknown" << std::endl;
LINYAPS_BOX_ERR() << "unknown error";
return -1;
}

View File

@ -6,6 +6,6 @@
namespace linyaps_box {
int main(int argc, char **argv) noexcept;
auto main(int argc, char **argv) noexcept -> int;
} // namespace linyaps_box

View File

@ -10,47 +10,52 @@
namespace linyaps_box {
enum class cgroup_manager_t : std::uint16_t { disabled, systemd, cgroupfs };
enum class cgroup_manager_t : std::uint8_t { disabled, systemd, cgroupfs };
struct cgroup_options
{
std::unordered_map<std::string, std::string> annotations;
std::filesystem::path cgroup_path;
std::string id;
std::filesystem::path state_root;
std::string id;
pid_t pid;
// resources and so on...
};
struct cgroup_status
{
std::filesystem::path path;
std::string scope;
public:
[[nodiscard]] auto path() const noexcept -> std::filesystem::path { return path_; }
[[nodiscard]] cgroup_manager_t manager_type() const noexcept { return manager; }
[[nodiscard]] auto scope() const noexcept -> std::string_view { return scope_; }
[[nodiscard]] auto manager() const noexcept -> cgroup_manager_t { return manager_; }
private:
friend class cgroup_manager;
cgroup_manager_t manager;
std::filesystem::path path_;
std::string scope_;
cgroup_manager_t manager_;
};
inline std::ostream &operator<<(std::ostream &os, cgroup_manager_t manager)
inline auto operator<<(std::ostream &stream, cgroup_manager_t manager) -> std::ostream &
{
switch (manager) {
case cgroup_manager_t::disabled: {
os << "disabled";
stream << "disabled";
} break;
case cgroup_manager_t::systemd: {
os << "systemd";
stream << "systemd";
} break;
case cgroup_manager_t::cgroupfs: {
os << "cgroupfs";
stream << "cgroupfs";
} break;
default: {
stream << "unknown";
} break;
default:
os << "unknown";
}
return os;
return stream;
}
} // namespace linyaps_box

View File

@ -2,6 +2,10 @@
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
#include <linyaps_box/cgroup_manager.h>
namespace linyaps_box {
cgroup_manager::~cgroup_manager() = default;
} // namespace linyaps_box

View File

@ -13,9 +13,17 @@ namespace linyaps_box {
class cgroup_manager : public virtual interface
{
public:
[[nodiscard]] virtual cgroup_manager_t type() const = 0;
cgroup_manager() = default;
~cgroup_manager() override;
virtual cgroup_status create_cgroup(const cgroup_options &options) = 0;
cgroup_manager(const cgroup_manager &) = delete;
auto operator=(const cgroup_manager &) -> cgroup_manager & = delete;
cgroup_manager(cgroup_manager &&) = delete;
auto operator=(cgroup_manager &&) -> cgroup_manager & = delete;
[[nodiscard]] virtual auto type() const -> cgroup_manager_t = 0;
virtual auto create_cgroup(const cgroup_options &options) -> cgroup_status = 0;
virtual void precreate_cgroup(const cgroup_options &options, utils::file_descriptor &dirfd) = 0;
@ -24,9 +32,9 @@ public:
// TODO: support update resource
// virtual void update_resource(const cgroup_status &status, ) = 0;
protected:
static void set_manager_type(cgroup_status &status, cgroup_manager_t type) noexcept
static void set_manager(cgroup_status &status, cgroup_manager_t type) noexcept
{
status.manager = type;
status.manager_ = type;
}
};

View File

@ -11,7 +11,7 @@
void linyaps_box::command::exec(const struct exec_options &options)
{
std::unique_ptr<status_directory> dir =
std::make_unique<impl::status_directory>(options.global.get().root);
std::make_unique<impl::status_directory>(options.global_.get().root);
runtime_t runtime(std::move(dir));
auto container_refs = runtime.containers();

View File

@ -28,7 +28,7 @@ void linyaps_box::command::kill(const struct kill_options &options)
break;
}
auto status_dir = std::make_unique<impl::status_directory>(options.global.get().root);
auto status_dir = std::make_unique<impl::status_directory>(options.global_.get().root);
if (!status_dir) {
throw std::runtime_error("failed to create status directory");
}

View File

@ -13,7 +13,7 @@
void linyaps_box::command::list(const struct list_options &options)
{
auto status_dir = std::make_unique<impl::status_directory>(options.global.get().root);
auto status_dir = std::make_unique<impl::status_directory>(options.global_.get().root);
if (!status_dir) {
throw std::runtime_error("failed to create status directory");
}

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")
@ -111,6 +115,7 @@ linyaps_box::command::options linyaps_box::command::parse(int argc, char *argv[]
app.parse(argc, argv);
} catch (const CLI::ParseError &e) {
options.global.return_code = app.exit(e);
return options;
}
if (cmd_list->parsed()) {

View File

@ -22,30 +22,27 @@ struct global_options
struct list_options
{
// FIXME: if the underlying type of enum class is std::uint8_t,
// the mapping message of CLI11 transformer is incorrect
// use std::uint16_t for now
enum class output_format_t : std::uint16_t { table, json };
enum class output_format_t : std::uint8_t { table, json };
explicit list_options(global_options &global)
: global(global)
: global_(global)
{
}
output_format_t output_format{ output_format_t::table };
std::reference_wrapper<global_options> global;
std::reference_wrapper<global_options> global_;
};
struct exec_options
{
explicit exec_options(global_options &global)
: no_new_privs(false)
, global(global)
, global_(global)
{
}
bool no_new_privs;
std::reference_wrapper<global_options> global;
std::reference_wrapper<global_options> global_;
std::vector<std::string> command;
std::string user;
std::optional<std::vector<std::string>> caps;
@ -57,24 +54,25 @@ struct exec_options
struct run_options
{
explicit run_options(global_options &global)
: global(global)
: global_(global)
{
}
std::reference_wrapper<global_options> global;
std::reference_wrapper<global_options> global_;
std::string ID;
std::string bundle;
std::string config;
int preserve_fds;
};
struct kill_options
{
explicit kill_options(global_options &global)
: global(global)
: global_(global)
{
}
std::reference_wrapper<global_options> global;
std::reference_wrapper<global_options> global_;
std::string container;
std::string signal;
};

View File

@ -8,18 +8,17 @@
#include "linyaps_box/runtime.h"
#include "linyaps_box/status_directory.h"
int linyaps_box::command::run(const struct run_options &options)
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);
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

@ -8,6 +8,6 @@
namespace linyaps_box::command {
[[nodiscard]] int run(const run_options &options);
[[nodiscard]] auto run(const run_options &options) -> int;
} // namespace linyaps_box::command

View File

@ -11,17 +11,19 @@
namespace {
std::tuple<unsigned long, unsigned long, std::uint8_t, std::string>
parse_mount_options(const std::vector<std::string> &options)
// 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<std::string> &options)
-> std::tuple<unsigned long, unsigned long, std::uint8_t, std::string>
{
const static std::unordered_map<std::string_view, unsigned long> propagation_flags_map{
const std::unordered_map<std::string_view, unsigned long> propagation_flags_map{
{ "rprivate", MS_PRIVATE | MS_REC }, { "private", MS_PRIVATE },
{ "rslave", MS_SLAVE | MS_REC }, { "slave", MS_SLAVE },
{ "rshared", MS_SHARED | MS_REC }, { "shared", MS_SHARED },
{ "runbindable", MS_UNBINDABLE | MS_REC }, { "unbindable", MS_UNBINDABLE },
};
const static std::unordered_map<std::string_view, unsigned long> flags_map{
const std::unordered_map<std::string_view, unsigned long> flags_map{
{ "bind", MS_BIND },
{ "defaults", 0 },
{ "dirsync", MS_DIRSYNC },
@ -43,7 +45,7 @@ parse_mount_options(const std::vector<std::string> &options)
{ "sync", MS_SYNCHRONOUS },
};
const static std::unordered_map<std::string_view, unsigned long> unset_flags_map{
const std::unordered_map<std::string_view, unsigned long> unset_flags_map{
{ "async", MS_SYNCHRONOUS },
{ "atime", MS_NOATIME },
{ "dev", MS_NODEV },
@ -60,7 +62,7 @@ parse_mount_options(const std::vector<std::string> &options)
{ "symfollow", LINGYAPS_MS_NOSYMFOLLOW },
};
const static std::unordered_map<std::string_view, std::uint8_t> extra_flags_map{
const std::unordered_map<std::string_view, std::uint8_t> extra_flags_map{
{ "copy-symlink", linyaps_box::config::mount_t::COPY_SYMLINK }
};
@ -79,7 +81,7 @@ parse_mount_options(const std::vector<std::string> &options)
continue;
}
if (auto it = propagation_flags_map.find(opt); it != propagation_flags_map.end()) {
propagation_flags &= it->second;
propagation_flags |= it->second;
continue;
}
@ -162,8 +164,8 @@ linyaps_box::config::process_t::rlimits_t parse_rlimits(const nlohmann::json &ob
return ret;
}
linyaps_box::config::linux_t parse_linux(const nlohmann::json &obj,
const nlohmann::json::json_pointer &ptr)
auto parse_linux(const nlohmann::json &obj, const nlohmann::json::json_pointer &ptr)
-> linyaps_box::config::linux_t
{
auto linux = linyaps_box::config::linux_t{};
if (auto uid_ptr = ptr / "uidMappings"; obj.contains(uid_ptr)) {
@ -262,12 +264,27 @@ linyaps_box::config::linux_t parse_linux(const nlohmann::json &obj,
linux.readonly_paths = std::move(readonly_paths);
}
if (auto rootfs_propagation = ptr / "rootfsPropagation"; obj.contains(rootfs_propagation)) {
auto val = obj[rootfs_propagation].get<std::string>();
if (val == "shared") {
linux.rootfs_propagation = MS_SHARED;
} else if (val == "slave") {
linux.rootfs_propagation = MS_SLAVE;
} else if (val == "private") {
linux.rootfs_propagation = MS_PRIVATE;
} else if (val == "unbindable") {
linux.rootfs_propagation = MS_UNBINDABLE;
} else {
throw std::runtime_error("unsupported rootfs propagation: " + val);
}
}
return linux;
}
linyaps_box::config parse_1_2_0(const nlohmann::json &j)
auto parse_1_2_0(const nlohmann::json &j) -> linyaps_box::config
{
static const auto ptr = ""_json_pointer;
const auto ptr = ""_json_pointer;
auto semver = linyaps_box::utils::semver(j[ptr / "ociVersion"].get<std::string>());
if (!linyaps_box::utils::semver(linyaps_box::config::oci_version).is_compatible_with(semver)) {
@ -360,7 +377,7 @@ linyaps_box::config parse_1_2_0(const nlohmann::json &j)
}
if (h.contains("env")) {
std::map<std::string, std::string> env;
std::unordered_map<std::string, std::string> env;
for (const auto &e : h["env"].get<std::vector<std::string>>()) {
auto pos = e.find('=');
@ -433,6 +450,11 @@ linyaps_box::config parse_1_2_0(const nlohmann::json &j)
cfg.root.readonly = j[root / "readonly"].get<bool>();
}
auto annotations = ptr / "annotations";
if (j.contains(annotations)) {
cfg.annotations = j[annotations].get<std::unordered_map<std::string, std::string>>();
}
return cfg;
}

View File

@ -7,8 +7,8 @@
#include <sys/mount.h>
#include <filesystem>
#include <map>
#include <optional>
#include <unordered_map>
#include <vector>
#include <sys/resource.h>
@ -31,7 +31,7 @@ struct config
{
static constexpr auto oci_version = "1.2.0";
static config parse(std::istream &is);
static auto parse(std::istream &is) -> config;
struct process_t
{
@ -123,6 +123,7 @@ struct config
std::optional<std::vector<id_mapping_t>> gid_mappings;
std::optional<std::vector<std::filesystem::path>> masked_paths;
std::optional<std::vector<std::filesystem::path>> readonly_paths;
unsigned int rootfs_propagation{ 0 };
};
std::optional<linux_t> linux;
@ -133,7 +134,7 @@ struct config
{
std::filesystem::path path;
std::optional<std::vector<std::string>> args;
std::optional<std::map<std::string, std::string>> env;
std::optional<std::unordered_map<std::string, std::string>> env;
std::optional<int> timeout;
};
@ -169,6 +170,8 @@ struct config
};
root_t root;
std::optional<std::unordered_map<std::string, std::string>> annotations;
};
} // namespace linyaps_box

View File

@ -30,6 +30,7 @@
#include <csignal>
#include <fstream>
#include <iostream>
#include <limits>
#include <set>
#include <stdexcept>
#include <string>
@ -47,6 +48,7 @@
#endif
constexpr auto propagations_flag = (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE);
constexpr auto max_symlink_depth{ 32 };
namespace linyaps_box {
@ -54,6 +56,7 @@ struct container_data
{
bool deny_setgroups{ false };
bool mount_dev_from_host{ false };
unsigned int rootfs_propagation{ 0 };
};
container_data &get_private_data(const linyaps_box::container &c) noexcept
@ -110,96 +113,59 @@ std::ostream &operator<<(std::ostream &os, const sync_message message)
} break;
default: {
assert(false);
os << "UNKNOWN " << (uint8_t)message;
os << "UNKNOWN " << static_cast<uint8_t>(message);
} break;
}
return os;
}
std::string dump_mount_flags(uint flags) noexcept
struct MountFlag
{
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 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
{
std::stringstream ss;
ss << "[ ";
if ((flags & MS_RDONLY) != 0) {
ss << "MS_RDONLY ";
}
if ((flags & MS_NOSUID) != 0) {
ss << "MS_NOSUID ";
}
if ((flags & MS_NODEV) != 0) {
ss << "MS_NODEV ";
}
if ((flags & MS_NOEXEC) != 0) {
ss << "MS_NOEXEC ";
}
if ((flags & MS_SYNCHRONOUS) != 0) {
ss << "MS_SYNCHRONOUS ";
}
if ((flags & MS_REMOUNT) != 0) {
ss << "MS_REMOUNT ";
}
if ((flags & MS_MANDLOCK) != 0) {
ss << "MS_MANDLOCK ";
}
if ((flags & MS_DIRSYNC) != 0) {
ss << "MS_DIRSYNC ";
}
if ((flags & LINGYAPS_MS_NOSYMFOLLOW) != 0) {
ss << "MS_NOSYMFOLLOW ";
}
if ((flags & MS_NOATIME) != 0) {
ss << "MS_NOATIME ";
}
if ((flags & MS_NODIRATIME) != 0) {
ss << "MS_NODIRATIME ";
}
if ((flags & MS_BIND) != 0) {
ss << "MS_BIND ";
}
if ((flags & MS_MOVE) != 0) {
ss << "MS_MOVE ";
}
if ((flags & MS_REC) != 0) {
ss << "MS_REC ";
}
if ((flags & MS_SILENT) != 0) {
ss << "MS_SILENT ";
}
if ((flags & MS_POSIXACL) != 0) {
ss << "MS_POSIXACL ";
}
if ((flags & MS_UNBINDABLE) != 0) {
ss << "MS_UNBINDABLE ";
}
if ((flags & MS_PRIVATE) != 0) {
ss << "MS_PRIVATE ";
}
if ((flags & MS_SLAVE) != 0) {
ss << "MS_SLAVE ";
}
if ((flags & MS_SHARED) != 0) {
ss << "MS_SHARED ";
}
if ((flags & MS_RELATIME) != 0) {
ss << "MS_RELATIME ";
}
if ((flags & MS_KERNMOUNT) != 0) {
ss << "MS_KERNMOUNT ";
}
if ((flags & MS_I_VERSION) != 0) {
ss << "MS_I_VERSION ";
}
if ((flags & MS_STRICTATIME) != 0) {
ss << "MS_STRICTATIME ";
}
if ((flags & MS_LAZYTIME) != 0) {
ss << "MS_LAZYTIME ";
}
if ((flags & MS_ACTIVE) != 0) {
ss << "MS_ACTIVE ";
}
if ((flags & MS_NOUSER) != 0) {
ss << "MS_NOUSER ";
for (const auto &[flag, name] : mount_flags) {
if ((flags & flag) != 0) {
ss << name << " ";
}
}
ss << "]";
return ss.str();
@ -256,8 +222,16 @@ void execute_hook(const linyaps_box::config::hooks_t::hook_t &hook)
const_cast<char *const *>(c_args.data()), // NOLINT
const_cast<char *const *>(c_env.data())); // NOLINT
std::cerr << "execvp: " << strerror(errno) << " errno=" << errno << std::endl;
exit(1);
LINYAPS_BOX_ERR() << "execute hook " << [&bin, &c_args]() -> std::string {
std::stringstream stream;
stream << bin;
for (const auto &arg : c_args) {
stream << " " << arg;
}
return std::move(stream).str();
}() << " failed: "
<< strerror(errno);
_exit(EXIT_FAILURE);
}
int status = 0;
@ -314,10 +288,17 @@ void initialize_container(const linyaps_box::config &config,
std::ofstream ofs("/proc/self/oom_score_adj");
if (!ofs) {
throw std::runtime_error("failed to open /proc/self/oom_score_adj");
throw std::system_error(errno,
std::generic_category(),
"failed to open /proc/self/oom_score_adj");
}
ofs << score;
if (!ofs) {
throw std::system_error(errno,
std::generic_category(),
"failed to write to /proc/self/oom_score_adj");
}
}
}
@ -327,23 +308,24 @@ void syscall_mount(const char *_special_file,
unsigned long int _rwflag,
const void *_data)
{
constexpr decltype(auto) fd_prefix = "/proc/self/fd/";
constexpr std::string_view fd_prefix = "/proc/self/fd/";
LINYAPS_BOX_DEBUG() << "mount\n"
<< "\t_special_file = " << [_special_file]() -> std::string {
<< "\t_special_file = " << [_special_file, fd_prefix]() -> std::string {
if (_special_file == nullptr) {
return "nullptr";
}
if (auto str = std::string_view{ _special_file }; str.rfind(fd_prefix, 0) == 0) {
return linyaps_box::utils::inspect_fd(std::stoi(str.data() + sizeof(fd_prefix) - 1));
return linyaps_box::utils::inspect_fd(std::stoi(str.data() + fd_prefix.size()));
}
return _special_file;
}() << "\n\t_dir = "
<< [_dir]() -> std::string {
<< [_dir, fd_prefix]() -> std::string {
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() + sizeof(fd_prefix) - 1));
return linyaps_box::utils::inspect_fd(std::stoi(str.data() + fd_prefix.size()));
}
return _dir;
}() << "\n\t_fstype = "
@ -357,10 +339,10 @@ void syscall_mount(const char *_special_file,
if (_data == nullptr) {
return "nullptr";
}
return reinterpret_cast<const char *>(_data);
return static_cast<const char *>(_data);
}();
int ret = ::mount(_special_file, _dir, _fstype, _rwflag, _data);
auto ret = ::mount(_special_file, _dir, _fstype, _rwflag, _data);
if (ret < 0) {
throw std::system_error(errno, std::generic_category(), "mount");
}
@ -399,7 +381,9 @@ void do_remount(const remount_t &mount)
}
auto state = linyaps_box::utils::statfs(mount.destination_fd);
auto remount_flags = state.f_flags & (MS_NOSUID | MS_NODEV | MS_NOEXEC);
const auto dest_flag = static_cast<unsigned long>(state.f_flags);
auto remount_flags = dest_flag & (MS_NOSUID | MS_NODEV | MS_NOEXEC);
if ((remount_flags | mount.flags) != mount.flags) {
try {
syscall_mount(nullptr,
@ -416,21 +400,12 @@ void do_remount(const remount_t &mount)
}
}
if ((state.f_flags & MS_RDONLY) != 0) {
remount_flags = state.f_flags & (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RDONLY);
if ((dest_flag & MS_RDONLY) != 0) {
remount_flags = dest_flag & (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RDONLY);
syscall_mount(nullptr, destination.c_str(), nullptr, mount.flags | remount_flags, data_ptr);
}
}
[[nodiscard]] linyaps_box::utils::file_descriptor create_destination_file(
const linyaps_box::utils::file_descriptor &root, const std::filesystem::path &destination)
{
LINYAPS_BOX_DEBUG() << "Creating file " << destination.string() << " under "
<< linyaps_box::utils::inspect_path(root.get());
const auto &parent = linyaps_box::utils::mkdir(root, destination.parent_path());
return linyaps_box::utils::touch(parent, destination.filename());
}
[[nodiscard]] linyaps_box::utils::file_descriptor create_destination_directory(
const linyaps_box::utils::file_descriptor &root, const std::filesystem::path &destination)
{
@ -439,12 +414,40 @@ void do_remount(const remount_t &mount)
return linyaps_box::utils::mkdir(root, destination);
}
[[nodiscard]] linyaps_box::utils::file_descriptor
create_destination_file(const linyaps_box::utils::file_descriptor &root,
const std::filesystem::path &destination,
int max_depth)
{
if (max_depth < 0) {
throw std::system_error(ELOOP, std::system_category(), "failed to create file");
}
LINYAPS_BOX_DEBUG() << "Creating file " << destination.string() << " under "
<< linyaps_box::utils::inspect_path(root.get());
const auto &parent = create_destination_directory(root, destination.parent_path());
try {
auto ret = linyaps_box::utils::touch(parent,
destination.filename(),
O_CLOEXEC | O_CREAT | O_WRONLY | O_NOFOLLOW);
return ret;
} catch (std::system_error &e) {
if (e.code() != std::errc::too_many_symbolic_link_levels) {
throw;
}
auto target = linyaps_box::utils::readlinkat(parent, destination.filename());
return create_destination_file(root, target, max_depth - 1);
}
}
[[nodiscard]] linyaps_box::utils::file_descriptor
create_destination_symlink(const linyaps_box::utils::file_descriptor &root,
const std::filesystem::path &source,
std::filesystem::path destination)
{
auto ret = std::filesystem::read_symlink(source);
auto ret = linyaps_box::utils::readlink(source);
auto parent = linyaps_box::utils::mkdir(root, destination.parent_path());
LINYAPS_BOX_DEBUG() << "Creating symlink " << destination.string() << " under "
@ -470,13 +473,8 @@ create_destination_symlink(const linyaps_box::utils::file_descriptor &root,
+ " already exists and is not a symlink");
}
std::array<char, PATH_MAX + 1> buf{};
auto to = ::readlinkat(root.get(), destination.c_str(), buf.data(), buf.size());
if (to == -1) {
throw std::system_error(errno, std::system_category(), "readlinkat");
}
if (std::string_view{ buf.data(), static_cast<size_t>(to) } == ret) {
auto target = linyaps_box::utils::readlinkat(root, destination);
if (target == ret) {
return linyaps_box::utils::open_at(root, destination, O_PATH | O_NOFOLLOW | O_CLOEXEC);
}
@ -492,11 +490,9 @@ ensure_mount_destination(bool isDir,
const linyaps_box::config::mount_t &mount)
try {
assert(mount.destination.has_value());
auto open_flag = O_PATH;
if ((mount.flags & LINGYAPS_MS_NOSYMFOLLOW) != 0) {
open_flag |= O_NOFOLLOW;
}
auto open_flag = O_PATH | O_CLOEXEC;
LINYAPS_BOX_DEBUG() << "Opening " << (isDir ? "directory " : "file ")
<< mount.destination.value() << " under " << root.current_path();
return linyaps_box::utils::open_at(root, mount.destination.value(), open_flag);
} catch (const std::system_error &e) {
if (e.code().value() != ENOENT) {
@ -515,7 +511,7 @@ try {
return create_destination_directory(root, path);
}
return create_destination_file(root, path);
return create_destination_file(root, path, max_symlink_depth);
}
void do_propagation_mount(const linyaps_box::utils::file_descriptor &destination,
@ -555,7 +551,7 @@ void do_propagation_mount(const linyaps_box::utils::file_descriptor &destination
nullptr,
bind_flags,
nullptr);
} catch (const std::system_error &e) {
} catch ([[maybe_unused]] const std::system_error &e) {
// mounting sysfs with rootless/userns container will fail with EPERM
// TODO: try to bind mount /sys
throw;
@ -564,9 +560,9 @@ void do_propagation_mount(const linyaps_box::utils::file_descriptor &destination
return linyaps_box::utils::open_at(root, mount.destination.value(), open_flag);
}
void do_cgroup_mount([[maybe_unused]] const linyaps_box::utils::file_descriptor &root,
[[maybe_unused]] const linyaps_box::config::mount_t &mount,
[[maybe_unused]] std::string_view unified_cgroup_path)
[[noreturn]] void do_cgroup_mount([[maybe_unused]] const linyaps_box::utils::file_descriptor &root,
[[maybe_unused]] const linyaps_box::config::mount_t &mount,
[[maybe_unused]] std::string_view unified_cgroup_path)
{
// TODO: implement
throw std::runtime_error("mount cgroup: Not implemented");
@ -683,7 +679,7 @@ class mounter
try {
do_propagation_mount(rootfsfd, MS_PRIVATE);
return;
} catch (const std::system_error &e) {
} catch ([[maybe_unused]] const std::system_error &e) {
auto parent_fd = ::openat(rootfsfd.get(), "..", O_PATH | O_CLOEXEC);
if (parent_fd < 0) {
throw std::system_error(errno,
@ -700,10 +696,10 @@ class mounter
}
public:
explicit mounter(linyaps_box::utils::file_descriptor root,
explicit mounter(linyaps_box::utils::file_descriptor rootfd,
const linyaps_box::container &container)
: container(container)
, root(std::move(root))
, root(std::move(rootfd))
{
}
@ -728,20 +724,31 @@ public:
// we will pivot root later
LINYAPS_BOX_DEBUG() << "configure rootfs";
do_propagation_mount(linyaps_box::utils::open("/", O_PATH | O_CLOEXEC | O_DIRECTORY),
MS_REC | MS_PRIVATE);
auto flags = config.linux->rootfs_propagation;
if ((flags & propagations_flag) == 0) {
flags = MS_PRIVATE | MS_REC;
}
// make sure the parent mount of rootfs is private
// change the propagation type of rootfs mountpoint to configured type
// otherwise bind mount will inherit the propagation type of rootfs mountpoint
do_propagation_mount(linyaps_box::utils::open("/", O_PATH | O_CLOEXEC | O_DIRECTORY),
flags);
// make sure the parent mountpoint of new root is private
// pivot root will fail if it has shared propagation type
make_rootfs_private();
// pivot root will reset the propagation type of rootfs mountpoint
// we need to save the propagation type to make sure the parent mountpoint of new root is
// what we want
get_private_data(container).rootfs_propagation = flags;
LINYAPS_BOX_DEBUG() << "rebind container rootfs";
linyaps_box::config::mount_t mount;
mount.source = root.current_path();
mount.destination = ".";
mount.flags = MS_BIND | MS_REC;
// mount.propagation_flags = MS_PRIVATE | MS_REC;
mount.flags = MS_BIND | MS_REC | MS_PRIVATE;
auto ret = do_mount(container, root, mount);
assert(!ret);
@ -789,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 {
@ -835,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;
@ -1024,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");
}
@ -1047,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;
}
@ -1097,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();
@ -1130,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
@ -1233,16 +1238,19 @@ void configure_mounts(const linyaps_box::container &container, const std::filesy
LINYAPS_BOX_DEBUG() << "Execute container process:" << [&process]() -> std::string {
std::stringstream ss;
assert(!process.args.empty());
ss << " " << process.args[0];
for (size_t i = 1; i < process.args.size(); ++i) {
ss << " " << process.args[i];
}
std::for_each(process.args.cbegin() + 1, process.args.cend(), [&ss](const auto &arg) {
ss << " " << arg;
});
return ss.str();
}();
execvpe(c_args[0],
const_cast<char *const *>(c_args.data()),
const_cast<char *const *>(c_env.data()));
const_cast<char *const *>(c_args.data()), // NOLINT
const_cast<char *const *>(c_env.data())); // NOLINT
throw std::system_error(errno, std::generic_category(), "execvpe");
}
@ -1319,7 +1327,7 @@ void create_container_hooks(const linyaps_box::container &container,
LINYAPS_BOX_DEBUG() << "Sync message sent";
}
void do_pivot_root(const std::filesystem::path &rootfs)
void do_pivot_root(const linyaps_box::container &container, const std::filesystem::path &rootfs)
{
LINYAPS_BOX_DEBUG() << "start pivot root";
LINYAPS_BOX_DEBUG() << linyaps_box::utils::inspect_fds();
@ -1344,6 +1352,15 @@ void do_pivot_root(const std::filesystem::path &rootfs)
throw std::system_error(errno, std::generic_category(), "pivot_root");
}
ret = fchdir(old_root.get());
if (ret < 0) {
throw std::system_error(errno, std::generic_category(), "fchdir");
}
// make sure that umount event couldn't propagate to host
do_propagation_mount(old_root, MS_REC | MS_PRIVATE);
// umount old root
ret = umount2(".", MNT_DETACH);
if (ret < 0) {
throw std::system_error(errno, std::generic_category(), "umount2");
@ -1363,6 +1380,10 @@ void do_pivot_root(const std::filesystem::path &rootfs)
if (ret < 0) {
throw std::system_error(errno, std::generic_category(), "chdir");
}
// restore the propagation type of rootfs mountpoint
do_propagation_mount(linyaps_box::utils::open("/", O_PATH | O_CLOEXEC | O_DIRECTORY),
get_private_data(container).rootfs_propagation);
}
void set_umask(const std::optional<mode_t> &mask)
@ -1497,7 +1518,7 @@ void set_capabilities(const linyaps_box::container &container, int last_cap)
throw std::system_error(errno, std::generic_category(), "cap_ambient_clear_all");
}
std::for_each(capabilities.ambient.cend(), capabilities.ambient.cend(), [](cap_value_t cap) {
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");
@ -1559,6 +1580,68 @@ void close_other_fds(std::set<uint> except_fds)
}
}
void processing_extensions(const linyaps_box::container &container)
{
const auto &config = container.get_config();
if (!config.annotations) {
return;
}
LINYAPS_BOX_DEBUG() << "Processing container extensions";
// ext_ns_last_pid
// 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
try {
// Use std::stoll to handle larger ranges, then check if it fits in pid_t
const auto value = std::stoll(it->second);
if (value < 0 || value > std::numeric_limits<pid_t>::max()) {
throw std::runtime_error("ns_last_pid value out of range: " + it->second
+ " (must be between 0 and "
+ std::to_string(std::numeric_limits<pid_t>::max()) + ")");
}
} catch (const std::out_of_range &e) {
throw std::runtime_error("parse ns_last_pid " + it->second + " failed: " + e.what());
} catch (const std::invalid_argument &e) {
throw std::runtime_error("parse ns_last_pid " + it->second + " failed: " + e.what());
}
// 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(),
"failed to open /proc/sys/kernel/ns_last_pid");
}
ofs << it->second;
if (!ofs) {
throw std::system_error(errno,
std::generic_category(),
"failed to write to /proc/sys/kernel/ns_last_pid");
}
LINYAPS_BOX_DEBUG() << "Successfully set ns_last_pid to " << it->second;
break;
}
LINYAPS_BOX_DEBUG() << "Container extensions processing completed";
}
void signal_USR1_handler([[maybe_unused]] int sig)
{
LINYAPS_BOX_DEBUG() << "Signal USR1 received:" << sig;
@ -1589,8 +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, (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;
@ -1598,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);
}
@ -1608,16 +1700,18 @@ try {
wait_create_runtime_result(container, socket);
create_container_hooks(container, socket);
// TODO: selinux label/apparmor profile
do_pivot_root(rootfs);
do_pivot_root(container, rootfs);
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);
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
LINYAPS_BOX_ERR() << "clone failed: " << e.what();
return -1;
} catch (...) {
std::cerr << "unknown error" << std::endl;
LINYAPS_BOX_ERR() << "clone failed: unknown error";
return -1;
}
@ -1668,8 +1762,11 @@ namespace runtime_ns {
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("invalid namespace");
throw std::invalid_argument("unknown namespace type: " + std::to_string(ns.type));
}
}
@ -1711,17 +1808,16 @@ public:
return;
}
const auto code = errno;
std::cerr << "munmap: " << strerror(code) << std::endl;
LINYAPS_BOX_ERR() << "munmap child stack failed: " << strerror(errno);
assert(false);
}
[[nodiscard]] void *top() const noexcept
[[nodiscard]] auto top() const noexcept -> void *
{
if constexpr (LINYAPS_BOX_STACK_GROWTH_DOWN) {
return (char *)this->stack_low + LINYAPS_BOX_CLONE_CHILD_STACK_SIZE;
return static_cast<std::byte *>(this->stack_low) + LINYAPS_BOX_CLONE_CHILD_STACK_SIZE;
} else {
return (char *)this->stack_low - LINYAPS_BOX_CLONE_CHILD_STACK_SIZE;
return static_cast<std::byte *>(this->stack_low) - LINYAPS_BOX_CLONE_CHILD_STACK_SIZE;
}
}
@ -1764,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");
}
@ -1819,12 +1915,9 @@ std::tuple<int, linyaps_box::utils::file_descriptor> start_container_process(
}
c_args.push_back(nullptr);
auto ret = execvp(c_args[0], const_cast<char *const *>(c_args.data()));
if (ret < 0) {
exit(errno);
}
exit(0);
execvp(c_args[0], const_cast<char *const *>(c_args.data()));
LINYAPS_BOX_ERR() << "execute helper " << c_args[0] << " failed: " << strerror(errno);
_exit(EXIT_FAILURE);
}
int status = 0;
@ -2206,7 +2299,7 @@ void poststop_hooks(const linyaps_box::container &container) noexcept
try {
execute_hook(hook);
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
LINYAPS_BOX_ERR() << "execute poststop hook " << hook.path << " failed: " << e.what();
}
}
}
@ -2237,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;
}
@ -2270,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;
@ -2283,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,34 +13,47 @@ 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);
[[nodiscard]] const linyaps_box::config &get_config() const;
[[nodiscard]] const std::filesystem::path &get_bundle() const;
[[nodiscard]] int run(const config::process_t &process) const;
container(const container &) = delete;
auto operator=(const container &) -> container & = delete;
container(container &&) = delete;
auto operator=(container &&) -> container & = delete;
[[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;
// TODO:: support fully container capabilities, e.g. create, start, stop, delete...
friend container_data &get_private_data(const container &c) noexcept;
friend auto get_private_data(const container &c) noexcept -> container_data &;
~container() noexcept override;
[[nodiscard]] auto host_gid() const noexcept { return host_gid_; };
[[nodiscard]] auto host_gid() const noexcept { return host_gid_; }
[[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

@ -12,14 +12,16 @@
#include <unistd.h>
linyaps_box::container_ref::container_ref(const status_directory &status_dir, std::string id)
: id(std::move(id))
: id_(std::move(id))
, status_dir_(status_dir)
{
}
linyaps_box::container_ref::~container_ref() noexcept = default;
linyaps_box::container_status_t linyaps_box::container_ref::status() const
{
return this->status_dir_.read(this->id);
return this->status_dir_.read(this->id_);
}
void linyaps_box::container_ref::kill(int signal) const
@ -111,5 +113,5 @@ const linyaps_box::status_directory &linyaps_box::container_ref::status_dir() co
const std::string &linyaps_box::container_ref::get_id() const
{
return this->id;
return this->id_;
}

View File

@ -14,18 +14,23 @@ class container_ref
{
public:
container_ref(const status_directory &status_dir, std::string id);
virtual ~container_ref() noexcept = default;
virtual ~container_ref() noexcept;
[[nodiscard]] container_status_t status() const;
container_ref(const container_ref &) = delete;
auto operator=(const container_ref &) -> container_ref & = delete;
container_ref(container_ref &&) = delete;
auto operator=(container_ref &&) -> container_ref & = delete;
[[nodiscard]] auto status() const -> container_status_t;
void kill(int signal) const;
[[noreturn]] void exec(const config::process_t &process);
protected:
[[nodiscard]] const status_directory &status_dir() const;
[[nodiscard]] const std::string &get_id() const;
[[nodiscard]] auto status_dir() const -> const status_directory &;
[[nodiscard]] auto get_id() const -> const std::string &;
private:
std::string id;
std::string id_;
const status_directory &status_dir_;
};

View File

@ -5,7 +5,7 @@
#include "linyaps_box/container_status.h"
namespace linyaps_box {
std::string to_string(linyaps_box::container_status_t::runtime_status status)
auto to_string(linyaps_box::container_status_t::runtime_status status) -> std::string
{
switch (status) {
case linyaps_box::container_status_t::runtime_status::CREATING:
@ -16,12 +16,12 @@ std::string to_string(linyaps_box::container_status_t::runtime_status status)
return "running";
case linyaps_box::container_status_t::runtime_status::STOPPED:
return "stopped";
default:
throw std::logic_error("unknown status");
}
throw std::logic_error("unknown status");
}
linyaps_box::container_status_t::runtime_status from_string(std::string_view status)
auto from_string(std::string_view status) -> linyaps_box::container_status_t::runtime_status
{
if (status == "creating") {
return linyaps_box::container_status_t::runtime_status::CREATING;
@ -39,7 +39,7 @@ linyaps_box::container_status_t::runtime_status from_string(std::string_view sta
throw std::logic_error("unknown status");
}
nlohmann::json status_to_json(const linyaps_box::container_status_t &status)
auto status_to_json(const linyaps_box::container_status_t &status) -> nlohmann::json
{
return nlohmann::json::object({ { "id", status.ID },
{ "pid", status.PID },

View File

@ -26,8 +26,8 @@ struct container_status_t
std::unordered_map<std::string, std::string> annotations;
};
std::string to_string(linyaps_box::container_status_t::runtime_status status);
linyaps_box::container_status_t::runtime_status from_string(std::string_view status);
nlohmann::json status_to_json(const linyaps_box::container_status_t &status);
auto to_string(linyaps_box::container_status_t::runtime_status status) -> std::string;
auto from_string(std::string_view status) -> linyaps_box::container_status_t::runtime_status;
auto status_to_json(const linyaps_box::container_status_t &status) -> nlohmann::json;
} // namespace linyaps_box

View File

@ -5,15 +5,16 @@
#include "linyaps_box/impl/disabled_cgroup_manager.h"
namespace linyaps_box {
[[nodiscard]] cgroup_manager_t disabled_cgroup_manager::type() const
[[nodiscard]] auto disabled_cgroup_manager::type() const -> cgroup_manager_t
{
return cgroup_manager_t::disabled;
}
cgroup_status disabled_cgroup_manager::create_cgroup([[maybe_unused]] const cgroup_options &options)
auto disabled_cgroup_manager::create_cgroup([[maybe_unused]] const cgroup_options &options)
-> cgroup_status
{
cgroup_status status{};
set_manager_type(status, type());
set_manager(status, type());
return status;
}

View File

@ -11,9 +11,9 @@ namespace linyaps_box {
class disabled_cgroup_manager : public virtual cgroup_manager
{
public:
[[nodiscard]] cgroup_manager_t type() const override;
[[nodiscard]] auto type() const -> cgroup_manager_t override;
cgroup_status create_cgroup([[maybe_unused]] const cgroup_options &options) override;
auto create_cgroup([[maybe_unused]] const cgroup_options &options) -> cgroup_status override;
void precreate_cgroup([[maybe_unused]] const cgroup_options &options,
[[maybe_unused]] utils::file_descriptor &dirfd) override;

View File

@ -9,15 +9,15 @@
#include "linyaps_box/utils/log.h"
#include "nlohmann/json.hpp"
#include <csignal>
#include <cstdlib>
#include <csignal> // IWYU pragma: keep
#include <fstream>
#include <utility>
#include <unistd.h>
namespace {
linyaps_box::container_status_t read_status(const std::filesystem::path &path)
auto read_status(const std::filesystem::path &path) -> linyaps_box::container_status_t
{
nlohmann::json j;
{
@ -34,7 +34,7 @@ linyaps_box::container_status_t read_status(const std::filesystem::path &path)
ret.PID = j["pid"];
ret.ID = j["id"];
ret.status = linyaps_box::from_string(j["status"].get<std::string>());
if (kill(ret.PID, 0) != 0) {
if (::kill(ret.PID, 0) != 0) {
ret.status = linyaps_box::container_status_t::runtime_status::STOPPED;
}
ret.bundle = std::string(j["bundle"]);
@ -61,22 +61,22 @@ void linyaps_box::impl::status_directory::write(const container_status_t &status
utils::atomic_write(this->path / (status.ID + ".json"), j.dump());
}
linyaps_box::container_status_t
linyaps_box::impl::status_directory::read(const std::string &id) const
auto linyaps_box::impl::status_directory::read(const std::string &id) const
-> linyaps_box::container_status_t
{
return read_status(this->path / (id + ".json"));
}
void linyaps_box::impl::status_directory::remove(const std::string &id) const
{
auto path = this->path / (id + ".json");
LINYAPS_BOX_DEBUG() << "Remove " << path;
if (!std::filesystem::remove(path)) {
LINYAPS_BOX_WARNING() << "Failed to remove " << path;
auto file_path = this->path / (id + ".json");
LINYAPS_BOX_DEBUG() << "Remove " << file_path;
if (!std::filesystem::remove(file_path)) {
LINYAPS_BOX_WARNING() << "Failed to remove " << file_path;
}
}
std::vector<std::string> linyaps_box::impl::status_directory::list() const
auto linyaps_box::impl::status_directory::list() const -> std::vector<std::string>
{
std::vector<std::string> ret;
for (const auto &entry : std::filesystem::directory_iterator(this->path)) {
@ -95,8 +95,8 @@ std::vector<std::string> linyaps_box::impl::status_directory::list() const
return ret;
}
linyaps_box::impl::status_directory::status_directory(const std::filesystem::path &path)
: path(path)
linyaps_box::impl::status_directory::status_directory(std::filesystem::path dir)
: path(std::move(dir))
{
if (std::filesystem::is_directory(path) || std::filesystem::create_directories(path)) {
return;

View File

@ -14,11 +14,11 @@ class status_directory : public virtual linyaps_box::status_directory
{
public:
void write(const container_status_t &status) const override;
[[nodiscard]] container_status_t read(const std::string &id) const override;
[[nodiscard]] auto read(const std::string &id) const -> container_status_t override;
void remove(const std::string &id) const override;
[[nodiscard]] std::vector<std::string> list() const override;
[[nodiscard]] auto list() const -> std::vector<std::string> override;
explicit status_directory(const std::filesystem::path &path);
explicit status_directory(std::filesystem::path dir);
private:
std::filesystem::path path;

View File

@ -7,7 +7,7 @@
#include <iostream>
namespace {
std::string get_status_string(const linyaps_box::container_status_t::runtime_status &status)
auto get_status_string(const linyaps_box::container_status_t::runtime_status &status) -> std::string
{
switch (status) {
case linyaps_box::container_status_t::runtime_status::CREATING:
@ -18,17 +18,17 @@ std::string get_status_string(const linyaps_box::container_status_t::runtime_sta
return "running";
case linyaps_box::container_status_t::runtime_status::STOPPED:
return "stopped";
default:
throw std::logic_error("unknown status");
}
throw std::logic_error("unknown status");
}
} // namespace
void linyaps_box::impl::table_printer::print_statuses(const std::vector<container_status_t> &status)
{
size_t max_length = 4;
int max_length = 4;
for (const auto &s : status) {
max_length = std::max(max_length, s.ID.length());
max_length = std::max(max_length, static_cast<int>(s.ID.length()));
}
std::cout << std::left << std::setw(max_length + 1) << "NAME" << std::setw(10) << "PID"

View File

@ -3,3 +3,10 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "linyaps_box/interface.h"
namespace linyaps_box {
// ensure vtable only here
interface::~interface() = default;
} // namespace linyaps_box

View File

@ -11,10 +11,10 @@ class interface
{
public:
interface() = default;
virtual ~interface() = default;
virtual ~interface();
interface(const interface &) = delete;
interface &operator=(const interface &) = delete;
auto operator=(const interface &) -> interface & = delete;
interface(interface &&) = delete;
interface &operator=(interface &&) = delete;
auto operator=(interface &&) -> interface & = delete;
};
} // namespace linyaps_box

View File

@ -3,3 +3,9 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "linyaps_box/printer.h"
namespace linyaps_box {
printer::~printer() = default;
} // namespace linyaps_box

View File

@ -12,7 +12,17 @@
namespace linyaps_box {
class printer : public virtual interface
{
protected:
printer() = default;
public:
~printer() override;
printer(const printer &) = delete;
auto operator=(const printer &) -> printer & = delete;
printer(printer &&) = delete;
auto operator=(printer &&) -> printer & = delete;
virtual void print_status(const container_status_t &status) = 0;
virtual void print_statuses(const std::vector<container_status_t> &status) = 0;
};

View File

@ -5,27 +5,30 @@
#include "linyaps_box/runtime.h"
linyaps_box::runtime_t::runtime_t(std::unique_ptr<linyaps_box::status_directory> &&status_dir)
: status_dir{ std::move(status_dir) }
: status_dir_{ std::move(status_dir) }
{
if (!this->status_dir) {
if (!this->status_dir_) {
throw std::invalid_argument("status_dir is nullptr");
}
}
std::unordered_map<std::string, linyaps_box::container_ref> linyaps_box::runtime_t::containers()
auto linyaps_box::runtime_t::containers()
-> std::unordered_map<std::string, linyaps_box::container_ref>
{
auto container_ids = this->status_dir->list();
auto container_ids = this->status_dir_->list();
std::unordered_map<std::string, container_ref> containers;
for (const auto &container_id : container_ids) {
containers.emplace(container_id, container_ref(*this->status_dir, container_id));
containers.emplace(std::piecewise_construct,
std::forward_as_tuple(container_id),
std::forward_as_tuple(*this->status_dir_, container_id));
}
return containers;
}
linyaps_box::container linyaps_box::runtime_t::create_container(
const linyaps_box::runtime_t::create_container_options_t &options)
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

@ -17,20 +17,12 @@ class runtime_t
{
public:
explicit runtime_t(std::unique_ptr<status_directory> &&status_dir);
std::unordered_map<std::string, container_ref> containers();
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;
};
container create_container(const create_container_options_t &options);
auto create_container(const create_container_options_t &options) -> container;
private:
std::unique_ptr<status_directory> status_dir;
std::unique_ptr<status_directory> status_dir_;
};
} // namespace linyaps_box

View File

@ -3,3 +3,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "linyaps_box/status_directory.h"
namespace linyaps_box {
status_directory::~status_directory() = default;
} // namespace linyaps_box

View File

@ -12,10 +12,20 @@
namespace linyaps_box {
class status_directory : public virtual interface
{
protected:
status_directory() = default;
public:
~status_directory() override;
status_directory(const status_directory &) = delete;
auto operator=(const status_directory &) -> status_directory & = delete;
status_directory(status_directory &&) = delete;
auto operator=(status_directory &&) -> status_directory & = delete;
virtual void write(const container_status_t &status) const = 0;
[[nodiscard]] virtual container_status_t read(const std::string &id) const = 0;
[[nodiscard]] virtual auto read(const std::string &id) const -> container_status_t = 0;
virtual void remove(const std::string &id) const = 0;
[[nodiscard]] virtual std::vector<std::string> list() const = 0;
[[nodiscard]] virtual auto list() const -> std::vector<std::string> = 0;
};
} // namespace linyaps_box

View File

@ -11,7 +11,7 @@
constexpr auto cgroup_root = "/sys/fs/cgroups";
linyaps_box::utils::cgroup_t linyaps_box::utils::get_cgroup_type()
auto linyaps_box::utils::get_cgroup_type() -> linyaps_box::utils::cgroup_t
{
static auto cgroup_type = []() -> cgroup_t {
struct statfs stat{};

View File

@ -8,8 +8,8 @@
namespace linyaps_box::utils {
enum class cgroup_t : std::uint16_t { unified, legacy, hybrid };
enum class cgroup_t : std::uint8_t { unified, legacy, hybrid };
cgroup_t get_cgroup_type();
auto get_cgroup_type() -> cgroup_t;
} // namespace linyaps_box::utils

View File

@ -24,7 +24,7 @@ void syscall_close_range(uint fd, uint max_fd, int flags)
void close_range_fallback(uint first, uint last, int flags)
{
if ((flags & CLOSE_RANGE_UNSHARE) != 0) {
if ((static_cast<uint>(flags) & CLOSE_RANGE_UNSHARE) != 0) {
// not support CLOSE_RANGE_UNSHARE
// it request to create a new file descriptor table
// we can't do that in user space
@ -54,11 +54,12 @@ void close_range_fallback(uint first, uint last, int flags)
struct dirent *next{ nullptr };
while ((next = ::readdir(dir)) != nullptr) {
if (next->d_name[0] == '.') { // skip "." and ".."
const std::string name{ next->d_name[0] };
if (name == "." || name == "..") { // skip "." and ".."
continue;
}
auto fd = std::stoi(next->d_name);
auto fd = std::stoi(name);
if (fd == self_fd) {
continue;
}
@ -67,18 +68,15 @@ void close_range_fallback(uint first, uint last, int flags)
continue;
}
if ((flags & CLOSE_RANGE_CLOEXEC) != 0) {
if ((static_cast<uint>(flags) & CLOSE_RANGE_CLOEXEC) != 0) {
if (::fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
throw std::system_error(errno,
std::generic_category(),
std::string{ "failed to set up close-on-exec to " }
+ next->d_name);
"failed to set up close-on-exec to " + name);
}
} else {
if (::close(fd) < 0) {
throw std::system_error(errno,
std::generic_category(),
std::string{ "failed to close " } + next->d_name);
throw std::system_error(errno, std::generic_category(), "failed to close " + name);
}
}
}
@ -91,10 +89,10 @@ void linyaps_box::utils::close_range(uint first, uint last, int flags)
<< [flags]() -> std::string {
std::stringstream ss;
ss << '[';
if ((flags & CLOSE_RANGE_CLOEXEC) != 0) {
if ((static_cast<uint>(flags) & CLOSE_RANGE_CLOEXEC) != 0) {
ss << "CLOSE_RANGE_CLOEXEC ";
}
if ((flags & CLOSE_RANGE_UNSHARE) != 0) {
if ((static_cast<uint>(flags) & CLOSE_RANGE_UNSHARE) != 0) {
ss << "CLOSE_RANGE_UNSHARE ";
}
ss << ']';

View File

@ -26,16 +26,15 @@ template<typename Fn,
struct defer
{
explicit defer(Fn &&fn) noexcept
: fn(std::move(fn))
, cancelled(false)
: fn_(std::move(fn))
{
}
defer(const defer &) = delete;
defer &operator=(const defer &) = delete;
auto operator=(const defer &) -> defer & = delete;
defer(defer &&other) noexcept
: fn(std::move(other.fn))
: fn_(std::move(other.fn_))
, cancelled(other.cancelled)
{
other.cancelled = true;
@ -51,7 +50,7 @@ struct defer
execute();
}
fn = std::move(other.fn);
fn_ = std::move(other.fn_);
cancelled = other.cancelled;
other.cancelled = true;
@ -67,22 +66,22 @@ struct defer
void cancel() noexcept { cancelled = true; }
[[nodiscard]] bool is_cancelled() const noexcept { return cancelled; }
[[nodiscard]] auto is_cancelled() const noexcept -> bool { return cancelled; }
private:
void execute() noexcept
{
if constexpr (Policy == defer_policy::always) {
fn();
fn_();
} else if constexpr (Policy == defer_policy::on_error) {
if (std::uncaught_exceptions() > 0) {
fn();
fn_();
}
}
}
Fn fn;
bool cancelled;
Fn fn_;
bool cancelled{ false };
};
// Helper functions to create defer objects

View File

@ -4,7 +4,6 @@
#include "linyaps_box/utils/file_describer.h"
#include "linyaps_box/utils/fstat.h"
#include "linyaps_box/utils/log.h"
#include <cstring>
@ -16,19 +15,31 @@ linyaps_box::utils::file_descriptor_closed_exception::file_descriptor_closed_exc
{
}
linyaps_box::utils::file_descriptor_closed_exception::~file_descriptor_closed_exception() noexcept =
default;
linyaps_box::utils::file_descriptor_invalid_exception::file_descriptor_invalid_exception(
const std::string &message)
: std::runtime_error(message)
{
}
linyaps_box::utils::file_descriptor_invalid_exception::
~file_descriptor_invalid_exception() noexcept = default;
linyaps_box::utils::file_descriptor::file_descriptor(int fd)
: fd(fd)
: fd_(fd)
{
}
linyaps_box::utils::file_descriptor::~file_descriptor()
{
if (fd == -1) {
if (fd_ < 0) {
return;
}
if (close(fd) != 0) {
LINYAPS_BOX_ERR() << "close " << fd << " failed:" << ::strerror(errno);
if (close(fd_) != 0) {
LINYAPS_BOX_ERR() << "close " << fd_ << " failed:" << ::strerror(errno);
}
}
@ -37,33 +48,41 @@ linyaps_box::utils::file_descriptor::file_descriptor(file_descriptor &&other) no
*this = std::move(other);
}
int linyaps_box::utils::file_descriptor::release() && noexcept
auto linyaps_box::utils::file_descriptor::release() -> void
{
int ret = -1;
std::swap(ret, fd);
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);
}
}
int linyaps_box::utils::file_descriptor::get() const noexcept
auto linyaps_box::utils::file_descriptor::get() const noexcept -> int
{
return fd;
return fd_;
}
linyaps_box::utils::file_descriptor linyaps_box::utils::file_descriptor::duplicate() const
auto linyaps_box::utils::file_descriptor::duplicate() const -> linyaps_box::utils::file_descriptor
{
if (fd == -1) {
if (fd_ == -1) {
throw file_descriptor_closed_exception();
}
auto ret = dup(fd);
if (fd_ == AT_FDCWD) {
throw file_descriptor_invalid_exception("cannot duplicate AT_FDCWD");
}
auto ret = dup(fd_);
if (ret < 0) {
throw std::system_error(errno, std::generic_category(), "fcntl");
}
// 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);
auto flag = fcntl(fd_, F_GETFD);
if (flag < 0) {
throw std::system_error(errno, std::generic_category(), "fcntl");
}
@ -75,11 +94,11 @@ linyaps_box::utils::file_descriptor linyaps_box::utils::file_descriptor::duplica
return file_descriptor{ ret };
}
linyaps_box::utils::file_descriptor &
linyaps_box::utils::file_descriptor::operator<<(const std::byte &byte)
auto linyaps_box::utils::file_descriptor::operator<<(const std::byte &byte)
-> linyaps_box::utils::file_descriptor &
{
while (true) {
auto ret = write(fd, &byte, 1);
auto ret = write(fd_, &byte, 1);
if (ret == 1) {
return *this;
}
@ -90,18 +109,18 @@ linyaps_box::utils::file_descriptor::operator<<(const std::byte &byte)
}
}
linyaps_box::utils::file_descriptor &
linyaps_box::utils::file_descriptor::operator=(file_descriptor &&other) noexcept
auto linyaps_box::utils::file_descriptor::operator=(file_descriptor &&other) noexcept
-> linyaps_box::utils::file_descriptor &
{
std::swap(this->fd, other.fd);
std::swap(this->fd_, other.fd_);
return *this;
}
linyaps_box::utils::file_descriptor &
linyaps_box::utils::file_descriptor::operator>>(std::byte &byte)
auto linyaps_box::utils::file_descriptor::operator>>(std::byte &byte)
-> linyaps_box::utils::file_descriptor &
{
while (true) {
auto ret = read(fd, &byte, 1);
auto ret = read(fd_, &byte, 1);
if (ret == 1) {
return *this;
}
@ -115,13 +134,13 @@ linyaps_box::utils::file_descriptor::operator>>(std::byte &byte)
}
}
std::filesystem::path linyaps_box::utils::file_descriptor::proc_path() const
auto linyaps_box::utils::file_descriptor::proc_path() const -> std::filesystem::path
{
return std::filesystem::current_path().root_path() / "proc" / "self" / "fd"
/ std::to_string(fd);
/ std::to_string(fd_);
}
std::filesystem::path linyaps_box::utils::file_descriptor::current_path() const noexcept
auto linyaps_box::utils::file_descriptor::current_path() const noexcept -> std::filesystem::path
{
std::error_code ec;
auto p_path = proc_path();
@ -132,3 +151,8 @@ std::filesystem::path linyaps_box::utils::file_descriptor::current_path() const
return path;
}
auto linyaps_box::utils::file_descriptor::cwd() -> file_descriptor
{
return file_descriptor{ AT_FDCWD };
}

View File

@ -14,6 +14,26 @@ class file_descriptor_closed_exception : public std::runtime_error
{
public:
file_descriptor_closed_exception();
file_descriptor_closed_exception(const file_descriptor_closed_exception &) = default;
file_descriptor_closed_exception(file_descriptor_closed_exception &&) noexcept = default;
auto operator=(const file_descriptor_closed_exception &)
-> file_descriptor_closed_exception & = default;
auto operator=(file_descriptor_closed_exception &&) noexcept
-> file_descriptor_closed_exception & = default;
~file_descriptor_closed_exception() noexcept override;
};
class file_descriptor_invalid_exception : public std::runtime_error
{
public:
explicit file_descriptor_invalid_exception(const std::string &message);
file_descriptor_invalid_exception(const file_descriptor_invalid_exception &) = default;
file_descriptor_invalid_exception(file_descriptor_invalid_exception &&) noexcept = default;
auto operator=(const file_descriptor_invalid_exception &)
-> file_descriptor_invalid_exception & = default;
auto operator=(file_descriptor_invalid_exception &&) noexcept
-> file_descriptor_invalid_exception & = default;
~file_descriptor_invalid_exception() noexcept override;
};
class file_descriptor
@ -24,28 +44,29 @@ public:
~file_descriptor();
file_descriptor(const file_descriptor &) = delete;
file_descriptor &operator=(const file_descriptor &) = delete;
auto operator=(const file_descriptor &) -> file_descriptor & = delete;
file_descriptor(file_descriptor &&other) noexcept;
auto operator=(file_descriptor &&other) noexcept -> file_descriptor &;
file_descriptor &operator=(file_descriptor &&other) noexcept;
[[nodiscard]] auto get() const noexcept -> int;
[[nodiscard]] int get() const noexcept;
auto release() -> void;
int release() && noexcept;
[[nodiscard]] auto duplicate() const -> file_descriptor;
[[nodiscard]] file_descriptor duplicate() const;
auto operator<<(const std::byte &byte) -> file_descriptor &;
file_descriptor &operator<<(const std::byte &byte);
auto operator>>(std::byte &byte) -> file_descriptor &;
file_descriptor &operator>>(std::byte &byte);
[[nodiscard]] auto proc_path() const -> std::filesystem::path;
[[nodiscard]] std::filesystem::path proc_path() const;
[[nodiscard]] auto current_path() const noexcept -> std::filesystem::path;
[[nodiscard]] std::filesystem::path current_path() const noexcept;
static auto cwd() -> file_descriptor;
private:
int fd{ -1 };
int fd_{ -1 };
};
} // namespace linyaps_box::utils

View File

@ -3,3 +3,161 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
#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
{
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<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

@ -12,41 +12,14 @@
#include <sys/stat.h>
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;
inline struct stat fstatat(const file_descriptor &fd, std::filesystem::path path, int flag)
{
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;
}
inline struct stat fstatat(const file_descriptor &fd, const std::filesystem::path &path)
{
return linyaps_box::utils::fstatat(fd, path, AT_EMPTY_PATH);
}
inline struct stat lstatat(const file_descriptor &fd, const std::filesystem::path &path)
{
return linyaps_box::utils::fstatat(fd, path, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
}
inline struct statfs statfs(const file_descriptor &fd)
{
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;
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

@ -17,7 +17,7 @@
namespace {
std::string inspect_fdinfo(const std::filesystem::path &fdinfoPath)
auto inspect_fdinfo(const std::filesystem::path &fdinfoPath) -> std::string
{
std::ifstream fdinfo(fdinfoPath);
assert(fdinfo.is_open());
@ -42,7 +42,7 @@ std::string inspect_fdinfo(const std::filesystem::path &fdinfoPath)
return ss.str();
}
std::string inspect_fdinfo(int fd)
auto inspect_fdinfo(int fd) -> std::string
{
std::stringstream ss;
ss << linyaps_box::utils::inspect_path(fd) << " ";
@ -54,7 +54,7 @@ std::string inspect_fdinfo(int fd)
namespace linyaps_box::utils {
std::string inspect_fcntl_or_open_flags(size_t flags)
auto inspect_fcntl_or_open_flags(size_t flags) -> std::string
{
std::stringstream ss;
@ -126,12 +126,12 @@ std::string inspect_fcntl_or_open_flags(size_t flags)
return ss.str();
}
std::string inspect_fd(int fd)
auto inspect_fd(int fd) -> std::string
{
return inspect_fdinfo(fd);
}
std::string inspect_fds()
auto inspect_fds() -> std::string
{
std::stringstream ss;
@ -160,7 +160,7 @@ std::string inspect_fds()
return ss.str();
}
std::string inspect_permissions(int fd)
auto inspect_permissions(int fd) -> std::string
{
std::stringstream ss;
struct stat buf{};
@ -228,7 +228,7 @@ std::string inspect_permissions(int fd)
return ss.str();
}
std::filesystem::path inspect_path(int fd)
auto inspect_path(int fd) -> std::filesystem::path
{
std::error_code ec;
auto ret = std::filesystem::read_symlink("/proc/self/fd/" + std::to_string(fd), ec);

View File

@ -9,10 +9,10 @@
namespace linyaps_box::utils {
std::string inspect_fcntl_or_open_flags(size_t flags);
std::string inspect_fd(int fd);
std::string inspect_fds();
std::string inspect_permissions(int fd);
std::filesystem::path inspect_path(int fd);
auto inspect_fcntl_or_open_flags(size_t flags) -> std::string;
auto inspect_fd(int fd) -> std::string;
auto inspect_fds() -> std::string;
auto inspect_permissions(int fd) -> std::string;
auto inspect_path(int fd) -> std::filesystem::path;
} // namespace linyaps_box::utils

View File

@ -8,7 +8,6 @@
#include <array>
#include <chrono>
#include <cstring>
#include <iostream>
#include <sys/time.h>
@ -21,14 +20,14 @@
namespace linyaps_box::utils {
template<unsigned int level>
Logger<level>::~Logger()
Logger<level>::~Logger() noexcept
{
if (level > get_current_log_level()) {
return;
}
flush();
auto str = this->str();
ss.flush();
auto str = ss.str();
syslog(level, "%s", str.c_str());
@ -69,31 +68,32 @@ template class Logger<LOG_NOTICE>;
template class Logger<LOG_INFO>;
template class Logger<LOG_DEBUG>;
bool force_log_to_stderr()
auto force_log_to_stderr() -> bool
{
static auto *result = getenv("LINYAPS_BOX_LOG_FORCE_STDERR");
static const auto *result = getenv("LINYAPS_BOX_LOG_FORCE_STDERR");
return result != nullptr;
}
bool stderr_is_a_tty()
auto stderr_is_a_tty() -> bool
{
static bool result = isatty(fileno(stderr)) != 0;
static const bool result = isatty(fileno(stderr)) != 0;
return result;
}
namespace {
unsigned int get_current_log_level_from_env()
auto get_current_log_level_from_env() -> unsigned int
{
auto *env = getenv("LINYAPS_BOX_LOG_LEVEL");
if (env == nullptr) {
return LINYAPS_BOX_LOG_DEFAULT_LEVEL;
}
auto level = std::stoi(env);
if (level < 0) {
auto ret = std::stoi(env);
if (ret < 0) {
return LOG_ALERT;
}
auto level = static_cast<unsigned int>(ret);
if (level > LOG_DEBUG) {
return LOG_DEBUG;
}
@ -102,15 +102,15 @@ unsigned int get_current_log_level_from_env()
}
} // namespace
unsigned int get_current_log_level()
auto get_current_log_level() -> unsigned int
{
static unsigned int level = get_current_log_level_from_env();
static const unsigned int level = get_current_log_level_from_env();
return level;
}
std::string get_pid_namespace(int pid)
auto get_pid_namespace(int pid) -> std::string
{
std::string pidns_path = "/proc/" + ((pid != 0) ? std::to_string(pid) : "self") + "/ns/pid";
const auto &pidns_path = "/proc/" + ((pid != 0) ? std::to_string(pid) : "self") + "/ns/pid";
std::array<char, PATH_MAX + 1> buf{};
auto length = ::readlink(pidns_path.c_str(), buf.data(), PATH_MAX);
@ -118,16 +118,25 @@ std::string get_pid_namespace(int pid)
return "not available";
}
std::string result{ buf.begin(), buf.begin() + length };
if (result.rfind("pid:[", 0) != 0) {
std::abort();
const std::string_view result(buf.data(), static_cast<size_t>(length));
constexpr std::string_view prefix = "pid:[";
constexpr char suffix = ']';
constexpr auto prefix_len = prefix.size();
constexpr auto total_wrapper_len = prefix_len + 1; // "pid:[" + "]"
if (result.size() < total_wrapper_len) {
return "invalid format";
}
if (result.back() != ']') {
std::abort();
if (result.rfind(prefix, 0) != 0) {
return "invalid format";
}
return result.substr(sizeof("pid:[") - 1, result.size() - 6);
if (result.back() != suffix) {
return "invalid format";
}
return std::string{ result.substr(prefix_len, result.size() - total_wrapper_len) };
}
} // namespace linyaps_box::utils

View File

@ -13,18 +13,41 @@
// TODO: maybe fmt with spdlog
namespace linyaps_box::utils {
bool force_log_to_stderr();
bool stderr_is_a_tty();
unsigned int get_current_log_level();
std::string get_pid_namespace(int pid = 0);
std::string get_current_command();
auto force_log_to_stderr() -> bool;
auto stderr_is_a_tty() -> bool;
auto get_current_log_level() -> unsigned int;
auto get_pid_namespace(int pid = 0) -> std::string;
auto get_current_command() -> std::string;
template<unsigned int level>
class Logger : public std::stringstream
class Logger
{
public:
using std::stringstream::stringstream;
~Logger() override;
Logger() = default;
Logger(const Logger &) = delete;
auto operator=(const Logger &) -> Logger & = delete;
Logger(Logger &&) noexcept(std::is_nothrow_move_constructible_v<std::ostringstream>) = // NOLINT
default;
Logger &
operator=(Logger &&) noexcept(std::is_nothrow_move_assignable_v<std::ostringstream>) = // NOLINT
default;
~Logger() noexcept;
template<typename T>
auto operator<<(const T &value) -> Logger &
{
ss << value;
return *this;
}
auto operator<<(std::ostream &(*manipulator)(std::ostream &)) -> Logger &
{
manipulator(ss);
return *this;
}
private:
std::ostringstream ss;
};
extern template class Logger<LOG_EMERG>;

View File

@ -12,9 +12,8 @@
#include <sys/types.h>
#include <unistd.h>
linyaps_box::utils::file_descriptor linyaps_box::utils::mkdir(const file_descriptor &root,
std::filesystem::path path,
mode_t mode)
auto linyaps_box::utils::mkdir(const file_descriptor &root, std::filesystem::path path, mode_t mode)
-> linyaps_box::utils::file_descriptor
{
LINYAPS_BOX_DEBUG() << "mkdir " << path << " at " << inspect_fd(root.get());

View File

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

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

@ -24,19 +24,19 @@
#endif
namespace {
linyaps_box::utils::file_descriptor
open_at_fallback(const linyaps_box::utils::file_descriptor &root,
const std::filesystem::path &path,
int flag,
int mode)
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(flag) << "\n\t"
<< linyaps_box::utils::inspect_fd(root.get());
<< linyaps_box::utils::inspect_fcntl_or_open_flags(
static_cast<size_t>(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
auto file_path = path.relative_path();
int fd = ::openat(root.get(), file_path.c_str(), flag, mode);
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,
@ -47,8 +47,8 @@ open_at_fallback(const linyaps_box::utils::file_descriptor &root,
return linyaps_box::utils::file_descriptor{ fd };
}
linyaps_box::utils::file_descriptor
syscall_openat2(int dirfd, const char *path, uint64_t flag, uint64_t mode, uint64_t resolve)
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
{
@ -57,7 +57,7 @@ syscall_openat2(int dirfd, const char *path, uint64_t flag, uint64_t mode, uint6
uint64_t resolve;
} how{ flag, mode, resolve };
auto ret = syscall(__NR_openat2, dirfd, path, &how, sizeof(openat2_how), 0);
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");
}
@ -66,12 +66,12 @@ syscall_openat2(int dirfd, const char *path, uint64_t flag, uint64_t mode, uint6
}
} // namespace
linyaps_box::utils::file_descriptor linyaps_box::utils::open(const std::filesystem::path &path,
int flag,
mode_t mode)
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(flag);
int fd = ::open(path.c_str(), flag, mode);
LINYAPS_BOX_DEBUG() << "open " << path.c_str() << " with "
<< inspect_fcntl_or_open_flags(static_cast<size_t>(flag));
const auto fd = ::open(path.c_str(), flag, mode);
if (fd == -1) {
throw std::system_error(errno,
std::generic_category(),
@ -81,21 +81,25 @@ linyaps_box::utils::file_descriptor linyaps_box::utils::open(const std::filesyst
return linyaps_box::utils::file_descriptor{ fd };
}
linyaps_box::utils::file_descriptor
linyaps_box::utils::open_at(const linyaps_box::utils::file_descriptor &root,
const std::filesystem::path &path,
int flag,
mode_t mode)
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(flag) << "\n\t" << inspect_fd(root.get());
<< inspect_fcntl_or_open_flags(static_cast<size_t>(flag)) << "\n\t"
<< inspect_fd(root.get());
static bool support_openat2{ true };
while (support_openat2) {
try {
return syscall_openat2(root.get(), path.c_str(), flag, mode, RESOLVE_IN_ROOT);
return syscall_openat2(root.get(),
path.c_str(),
static_cast<uint64_t>(flag),
mode,
RESOLVE_IN_ROOT);
} catch (const std::system_error &e) {
auto code = e.code().value();
const auto code = e.code().value();
if (code == EINTR || code == EAGAIN) {
continue;
}

View File

@ -12,11 +12,12 @@
namespace linyaps_box::utils {
file_descriptor 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;
file_descriptor open_at(const file_descriptor &root,
const std::filesystem::path &path,
int flag = O_RDONLY,
mode_t mode = 0);
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

View File

@ -11,11 +11,11 @@
#include <sys/resource.h>
namespace linyaps_box::utils {
int str_to_signal(std::string_view str)
auto str_to_signal(std::string_view str) -> int
{
// Only support standard signals for now,
// TODO: support real-time signal in the future
static const std::unordered_map<std::string_view, int> sigMap{
const std::unordered_map<std::string_view, int> sigMap{
{ "SIGABRT", SIGABRT }, { "SIGALRM", SIGALRM }, { "SIGBUS", SIGBUS },
{ "SIGCHLD", SIGCHLD }, { "SIGCONT", SIGCONT }, { "SIGFPE", SIGFPE },
{ "SIGHUP", SIGHUP }, { "SIGILL", SIGILL }, { "SIGINT", SIGINT },
@ -26,7 +26,10 @@ int str_to_signal(std::string_view str)
{ "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);
@ -37,9 +40,9 @@ int str_to_signal(std::string_view str)
return it->second;
}
int str_to_rlimit(std::string_view str)
auto str_to_rlimit(std::string_view str) -> int
{
const static std::unordered_map<std::string_view, int> resources{
const std::unordered_map<std::string_view, int> resources{
{ "RLIMIT_AS", RLIMIT_AS },
{ "RLIMIT_CORE", RLIMIT_CORE },
{ "RLIMIT_CPU", RLIMIT_CPU },

View File

@ -6,6 +6,6 @@
#include <string_view>
namespace linyaps_box::utils {
int str_to_signal(std::string_view str);
int str_to_rlimit(std::string_view str);
auto str_to_signal(std::string_view str) -> int;
auto str_to_rlimit(std::string_view str) -> int;
} // namespace linyaps_box::utils

View File

@ -16,7 +16,11 @@ linyaps_box::utils::semver::semver(const std::string &str)
throw std::invalid_argument("invalid semver: " + str);
}
auto major = std::stoi(str.substr(begin, end));
auto ret = std::stoi(str.substr(begin, end));
if (ret < 0) {
throw std::invalid_argument("invalid semver: " + str);
}
const auto major{ static_cast<unsigned int>(ret) };
begin = end + 1;
end = str.find('.', begin);
@ -24,12 +28,20 @@ linyaps_box::utils::semver::semver(const std::string &str)
throw std::invalid_argument("invalid semver: " + str);
}
auto minor = std::stoi(str.substr(begin, end));
ret = std::stoi(str.substr(begin, end));
if (ret < 0) {
throw std::invalid_argument("invalid semver: " + str);
}
const auto minor{ static_cast<unsigned int>(ret) };
begin = end + 1;
end = str.find_first_of("-+", begin);
auto patch = std::stoi(str.substr(begin, end));
ret = std::stoi(str.substr(begin, end));
if (ret < 0) {
throw std::invalid_argument("invalid semver: " + str);
}
const auto patch{ static_cast<unsigned int>(ret) };
if (end == std::string::npos) {
this->major_ = major;

View File

@ -19,14 +19,14 @@ public:
explicit semver(const std::string &str);
[[nodiscard]] unsigned int major() const;
[[nodiscard]] unsigned int minor() const;
[[nodiscard]] unsigned int patch() const;
[[nodiscard]] const std::string &prerelease() const;
[[nodiscard]] const std::string &build() const;
[[nodiscard]] auto major() const -> unsigned int;
[[nodiscard]] auto minor() const -> unsigned int;
[[nodiscard]] auto patch() const -> unsigned int;
[[nodiscard]] auto prerelease() const -> const std::string &;
[[nodiscard]] auto build() const -> const std::string &;
[[nodiscard]] std::string to_string() const;
[[nodiscard]] bool is_compatible_with(const semver &other) const;
[[nodiscard]] auto to_string() const -> std::string;
[[nodiscard]] auto is_compatible_with(const semver &other) const -> bool;
private:
unsigned int major_;

View File

@ -3,3 +3,23 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "linyaps_box/utils/socketpair.h"
#include <array>
#include <system_error>
namespace linyaps_box::utils {
auto socketpair(int domain, int type, int protocol) -> std::pair<file_descriptor, file_descriptor>
{
std::array<int, 2> fds{};
if (::socketpair(domain, type, protocol, fds.data()) == -1) {
throw std::system_error(errno,
std::system_category(),
"socketpair(" + std::to_string(domain) + ", " + std::to_string(type)
+ ", " + std::to_string(protocol) + ")");
}
return std::make_pair(file_descriptor(fds[0]), file_descriptor(fds[1]));
}
} // namespace linyaps_box::utils

View File

@ -4,25 +4,11 @@
#include "linyaps_box/utils/file_describer.h"
#include <array>
#include <system_error>
#include <sys/socket.h>
#include <sys/un.h>
namespace linyaps_box::utils {
inline std::pair<file_descriptor, file_descriptor> socketpair(int domain, int type, int protocol)
{
std::array<int, 2> fds{};
if (::socketpair(domain, type, protocol, fds.data()) == -1) {
throw std::system_error(errno,
std::system_category(),
"socketpair(" + std::to_string(domain) + ", " + std::to_string(type)
+ ", " + std::to_string(protocol) + ")");
}
return std::make_pair(file_descriptor(fds[0]), file_descriptor(fds[1]));
}
auto socketpair(int domain, int type, int protocol) -> std::pair<file_descriptor, file_descriptor>;
} // namespace linyaps_box::utils

View File

@ -6,6 +6,10 @@
#include "linyaps_box/utils/log.h"
#include <linux/limits.h>
#include <array>
void linyaps_box::utils::symlink(const std::filesystem::path &target,
const std::filesystem::path &link_path)
{
@ -25,8 +29,31 @@ void linyaps_box::utils::symlink_at(const std::filesystem::path &target,
LINYAPS_BOX_DEBUG() << "Create symlink " << link_path << " which under " << dirfd.current_path()
<< " point to " << target;
auto ret = ::symlinkat(target.c_str(), dirfd.get(), link_path.c_str());
const auto ret = ::symlinkat(target.c_str(), dirfd.get(), link_path.c_str());
if (ret == -1) {
throw std::system_error(errno, std::system_category(), "symlinkat");
}
}
std::filesystem::path linyaps_box::utils::readlink(const std::filesystem::path &path)
{
std::error_code ec;
auto ret = std::filesystem::read_symlink(path, ec);
if (ec) {
throw std::system_error{ ec.value(), std::system_category(), ec.message() };
}
return ret;
}
std::filesystem::path linyaps_box::utils::readlinkat(const file_descriptor &dirfd,
const std::filesystem::path &path)
{
std::array<char, PATH_MAX + 1> buf{};
auto ret = ::readlinkat(dirfd.get(), path.c_str(), buf.data(), PATH_MAX + 1);
if (ret == -1) {
throw std::system_error(errno, std::system_category(), "readlinkat");
}
return std::filesystem::path{ buf.data() };
}

View File

@ -10,10 +10,14 @@
namespace linyaps_box::utils {
void symlink(const std::filesystem::path &source, const std::filesystem::path &target);
void symlink(const std::filesystem::path &target, const std::filesystem::path &link_path);
void symlink_at(const std::filesystem::path &target,
const file_descriptor &dirfd,
const std::filesystem::path &link_path);
const file_descriptor &dirfd,
const std::filesystem::path &link_path);
std::filesystem::path readlink(const std::filesystem::path &path);
std::filesystem::path readlinkat(const file_descriptor &dirfd, const std::filesystem::path &path);
} // namespace linyaps_box::utils

View File

@ -9,11 +9,13 @@
#include <fcntl.h>
linyaps_box::utils::file_descriptor linyaps_box::utils::touch(const file_descriptor &root,
const std::filesystem::path &path)
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());
int fd = ::openat(root.get(), path.c_str(), O_CREAT | O_WRONLY, 0666);
const auto fd = ::openat(root.get(), path.c_str(), flag, mode);
if (fd == -1) {
throw std::system_error(errno,
std::system_category(),

View File

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

View File

@ -5,7 +5,7 @@
"uid": 0,
"gid": 0
},
"args": ["/bin/env", "bash", "-c", "pwd"],
"args": ["true"],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm",

View File

@ -0,0 +1,29 @@
{
"name": "Set ns_last_pid",
"process": {
"user": {
"uid": 0,
"gid": 0
},
"args": ["/bin/env", "bash", "-c", "cat /proc/sys/kernel/ns_last_pid"],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm",
"HOME=/root"
],
"cwd": "/"
},
"annotations": {
"cn.org.linyaps.runtime.ns_last_pid": "100"
},
"linux": {
"readonlyPaths": [
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sysrq-trigger"
]
},
"expected": "100"
}

View File

@ -175,17 +175,22 @@ function run_test() {
mkdir -p -- "${TEST_DIR}"
local TEST_CONFIG="${TEST_DIR}/test_config.json"
# patch process from test file
"${JQ}" >"${TEST_CONFIG}" \
".process |= $("${JQ}" -r '.process' "${TEST_FILE}" || true)" \
"${BUNDLE_DIR}/config.json"
# patch mount from test file if test file has mount field
if "${JQ}" -e '.mounts' "${TEST_FILE}" &>/dev/null; then
"${JQ}" >"${TEST_CONFIG}" \
".mounts |= $("${JQ}" -r '.mounts' "${TEST_FILE}" || true)" \
"${BUNDLE_DIR}/config.json"
fi
# Create a copy of the original config and apply all patches at once
"${JQ}" -s '
.[0] as $base |
.[1] as $patch |
$base * {
process: ($patch.process // $base.process),
mounts: ($patch.mounts // $base.mounts),
annotations: ($patch.annotations // $base.annotations),
linux: (if $patch.linux then
$base.linux * $patch.linux
else
$base.linux
end)
}
' "${BUNDLE_DIR}/config.json" "${TEST_FILE}" > "${TEST_CONFIG}"
local CONTAINER_NAME
CONTAINER_NAME="$(basename -- "${TEST_FILE_NAME}" .json)"

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."