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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user