diff --git a/CLAUDE.md b/CLAUDE.md index ca599a3..b2d2a38 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,9 @@ Build directory is `buildDir/` (already configured). - Configure (only needed if `buildDir/` is missing or deleted): `meson setup buildDir` - Build: `meson compile -C buildDir` (or `ninja -C buildDir`) -- Run the executable: `./buildDir/slocker_lite ` +- Run the executable: `./buildDir/slocker_lite -m ` (see `--help` for the + full flag list: `-m/--mount`, `-u/--umount`, `-t/--test`, `-l/--log-level`, + `-h/--help`, `-V/--version`) - Run tests: `meson test -C buildDir` ## Code style diff --git a/meson.build b/meson.build index e10605f..3750287 100644 --- a/meson.build +++ b/meson.build @@ -26,4 +26,4 @@ fixture_tar = custom_target('oci-fixture', output : 'fixture.tar', command : [find_program('python3'), files('tests/gen_fixture.py'), '@OUTPUT@']) -test('test', slocker_lite, args : [fixture_tar]) +test('test', slocker_lite, args : ['-m', fixture_tar]) diff --git a/src/containers_storage.cpp b/src/containers_storage.cpp index 9aa5c32..a91b13b 100644 --- a/src/containers_storage.cpp +++ b/src/containers_storage.cpp @@ -62,3 +62,10 @@ std::optional mount_layer(const std::string& layer_id) { } return path; } + +bool unmount_layer(const std::string& layer_id) { + std::vector argv = {"containers-storage", "unmount", layer_id}; + + ProcessResult result = run_process(argv); + return result.exit_code == 0; +} diff --git a/src/containers_storage.h b/src/containers_storage.h index dcc3698..80f07d1 100644 --- a/src/containers_storage.h +++ b/src/containers_storage.h @@ -34,3 +34,6 @@ std::optional import_layer(const std::filesystem::path& diff_file, // Runs `containers-storage mount --storage-opt overlay.mount_program= `. // Returns the merged directory path (trimmed stdout) or nullopt on failure. std::optional mount_layer(const std::string& layer_id); + +// Runs `containers-storage unmount `. Returns true on success. +bool unmount_layer(const std::string& layer_id); diff --git a/src/main.cpp b/src/main.cpp index 6557795..25099d9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,7 +17,9 @@ #include #include #include +#include #include +#include #include #include @@ -25,6 +27,7 @@ #include #include +#include "config.h" #include "containers_storage.h" #include "oci_image.h" @@ -32,6 +35,63 @@ namespace { constexpr std::array kRequiredTools = {"containers-storage", "bwrap"}; +enum class Mode { kNone, kMount, kUnmount, kTest }; + +constexpr std::array kLongOptions = {{ + {"help", no_argument, nullptr, 'h'}, + {"version", no_argument, nullptr, 'V'}, + {"test", no_argument, nullptr, 't'}, + {"log-level", required_argument, nullptr, 'l'}, + {"mount", required_argument, nullptr, 'm'}, + {"umount", required_argument, nullptr, 'u'}, + {nullptr, 0, nullptr, 0}, +}}; + +void print_usage(const char* prog) { + fmt::print( + "usage: {0} -m|--mount \n" + " {0} -u|--umount \n" + " {0} -t|--test\n" + " {0} -h|--help\n" + " {0} -V|--version\n" + "\n" + "options:\n" + " -m, --mount validate and mount an OCI Image Layout tar\n" + " -u, --umount unmount a previously mounted image layer (the\n" + " ID printed by --mount, or from\n" + " `containers-storage layers`)\n" + " -t, --test run the test suite\n" + " -l, --log-level set log verbosity (trace, debug, info, warn,\n" + " error, critical, off)\n" + " -h, --help print this help and exit\n" + " -V, --version print version information and exit\n", + prog); +} + +void print_version() { + fmt::print( + "{} {}\n" + "Licensed under GNU GPL version 2 or later \n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n", + PACKAGE, VERSION); +} + +bool apply_log_level(std::string_view name) { + constexpr std::array kValidLevels = { + "trace", "debug", "info", "warn", "error", "critical", "off"}; + for (auto level : kValidLevels) { + if (level == name) { + spdlog::set_level(spdlog::level::from_str(std::string(name))); + return true; + } + } + spdlog::error("invalid log level: {}", name); + return false; +} + +int run_tests() { return 0; } + bool is_executable_file(const std::filesystem::path& path) { std::error_code ec; return std::filesystem::is_regular_file(path, ec) && access(path.c_str(), X_OK) == 0; @@ -73,14 +133,79 @@ bool check_required_dependencies() { return all_found; } +int unmount_image(const std::string& layer_id) { + if (!check_required_dependencies()) { + return 1; + } + if (!unmount_layer(layer_id)) { + spdlog::error("failed to unmount layer {}", layer_id); + return 1; + } + fmt::print("unmounted layer {}\n", layer_id); + return 0; +} + int main(int argc, char* argv[]) { spdlog::cfg::load_env_levels(); - if (argc != 2) { - spdlog::error("usage: {} ", argv[0]); + Mode mode = Mode::kNone; + std::string mode_arg; + + opterr = 0; + int opt; + while ((opt = getopt_long(argc, argv, ":hVtl:m:u:", kLongOptions.data(), nullptr)) != -1) { + switch (opt) { + case 'h': + print_usage(argv[0]); + return 0; + case 'V': + print_version(); + return 0; + case 't': + case 'm': + case 'u': { + Mode requested = opt == 't' ? Mode::kTest : (opt == 'm' ? Mode::kMount : Mode::kUnmount); + if (mode != Mode::kNone && mode != requested) { + spdlog::error("multiple actions specified"); + print_usage(argv[0]); + return 1; + } + mode = requested; + if (optarg) { + mode_arg = optarg; + } + break; + } + case 'l': + if (!apply_log_level(optarg)) { + return 1; + } + break; + case ':': + spdlog::error("option requires an argument: -{}", static_cast(optopt)); + print_usage(argv[0]); + return 1; + case '?': + default: + spdlog::error("unrecognized option"); + print_usage(argv[0]); + return 1; + } + } + + if (mode == Mode::kNone || optind != argc) { + print_usage(argv[0]); return 1; } - const std::filesystem::path image_tar = argv[1]; + + if (mode == Mode::kTest) { + return run_tests(); + } + if (mode == Mode::kUnmount) { + return unmount_image(mode_arg); + } + + const std::filesystem::path image_tar = mode_arg; if (!check_required_dependencies()) { return 1; @@ -123,6 +248,6 @@ int main(int argc, char* argv[]) { return 1; } - fmt::print("mounted image at: {}\n", *merged); + fmt::print("mounted image at: {} (layer {})\n", *merged, parent_id); return 0; }