Add CLI option parsing and implement layer unmounting

Replace the implicit single-argument invocation with getopt_long-based
flags: -m/--mount (existing mount flow, now explicit), -u/--umount
(unmounts a layer via containers-storage), -t/--test (stub),
-l/--log-level (runtime spdlog level), -h/--help, -V/--version.
--mount now also prints the top layer's ID so it can be passed to
--umount.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 09:03:58 +00:00
parent a301dfb44f
commit 7e1ee150f6
5 changed files with 143 additions and 6 deletions
+7
View File
@@ -62,3 +62,10 @@ std::optional<std::string> mount_layer(const std::string& layer_id) {
}
return path;
}
bool unmount_layer(const std::string& layer_id) {
std::vector<std::string> argv = {"containers-storage", "unmount", layer_id};
ProcessResult result = run_process(argv);
return result.exit_code == 0;
}
+3
View File
@@ -34,3 +34,6 @@ std::optional<std::string> import_layer(const std::filesystem::path& diff_file,
// Runs `containers-storage mount --storage-opt overlay.mount_program=<path> <layer_id>`.
// Returns the merged directory path (trimmed stdout) or nullopt on failure.
std::optional<std::string> mount_layer(const std::string& layer_id);
// Runs `containers-storage unmount <layer_id>`. Returns true on success.
bool unmount_layer(const std::string& layer_id);
+129 -4
View File
@@ -17,7 +17,9 @@
#include <array>
#include <cstdlib>
#include <filesystem>
#include <getopt.h>
#include <optional>
#include <string>
#include <string_view>
#include <unistd.h>
@@ -25,6 +27,7 @@
#include <spdlog/cfg/env.h>
#include <spdlog/spdlog.h>
#include "config.h"
#include "containers_storage.h"
#include "oci_image.h"
@@ -32,6 +35,63 @@ namespace {
constexpr std::array<std::string_view, 2> kRequiredTools = {"containers-storage", "bwrap"};
enum class Mode { kNone, kMount, kUnmount, kTest };
constexpr std::array<struct option, 7> 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 <image.tar>\n"
" {0} -u|--umount <layer-id>\n"
" {0} -t|--test\n"
" {0} -h|--help\n"
" {0} -V|--version\n"
"\n"
"options:\n"
" -m, --mount <image.tar> validate and mount an OCI Image Layout tar\n"
" -u, --umount <layer-id> 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 <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 <https://gnu.org/licenses/gpl-2.0.html>\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<std::string_view, 7> 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: {} <image.tar>", 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<char>(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;
}