From abf268e9505051ace515c921291a7885ca5aa4bb Mon Sep 17 00:00:00 2001 From: Viorel Munteanu Date: Fri, 21 Aug 2026 13:21:46 +0000 Subject: [PATCH] Add --delete-volume and --delete-volume-full --delete-volume removes a volume's config entry only. --delete-volume-full also recursively deletes its host directory -- fails before touching the config if that deletion errors, so a failed directory delete never silently drops the config entry; warns instead of failing if the directory was already gone. Both are long-only, following the --list-volumes precedent, and slot into the existing multi-mode getopt dispatch the same way -v/--volume already does. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz --- CLAUDE.md | 23 +++++++++------ README.md | 10 +++++++ src/main.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 97 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d3ff8c2..32c9e4f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,16 +17,20 @@ status); this file stays the dense, file-by-file reference. Still early-stage. Source layout (all under `src/`): - `main.cpp` — CLI entry point, dependency checks, orchestration (`mount_image()`, `run_container()`, `cleanup_image()`, `unmount_image()`, `list_images_command()`, - `create_volume_command()`, `list_volumes_command()`). `run_container()` - unconditionally calls `read_oci_image_config()` and reuses the result for two - independent defaults: the command to run (`Entrypoint ++ Cmd`) when none is given - on the command line, and, when `--user` wasn't given, the sandboxed process's - user/group (`config.User`, split into `OciImageConfig::user`/`group`) — an - explicit `--user`/`--group` on the command line always takes precedence. + `create_volume_command()`, `list_volumes_command()`, `delete_volume_command()`). + `run_container()` unconditionally calls `read_oci_image_config()` and reuses the + result for two independent defaults: the command to run (`Entrypoint ++ Cmd`) when + none is given on the command line, and, when `--user` wasn't given, the sandboxed + process's user/group (`config.User`, split into `OciImageConfig::user`/`group`) — + an explicit `--user`/`--group` on the command line always takes precedence. `create_volume_command()` implements `-v/--volume `; `list_volumes_command()` implements `--list-volumes` (same tab-alignment scheme as - `list_images_command()`, reused as-is) — see `config_file.{h,cpp}` below for what a - "volume" means here (a distinct concept from `OciImageConfig::volumes`). + `list_images_command()`, reused as-is); `delete_volume_command()` implements both + `--delete-volume ` (config entry only) and `--delete-volume-full ` + (also `std::filesystem::remove_all()`s the host directory — errors out before + touching the config if that fails, warns instead of failing if the directory was + already gone) — see `config_file.{h,cpp}` below for what a "volume" means here (a + distinct concept from `OciImageConfig::volumes`). - `oci_image.{h,cpp}` — validates/parses the OCI Image Layout tar (libarchive + nlohmann_json) and extracts layer blobs. `list_oci_images()` scans a directory (non-recursively) for `*.tar`/`*.tar.*` files and, for each valid OCI archive, @@ -155,7 +159,8 @@ Build directory is `buildDir/` (already configured). - Run the executable: `./buildDir/slocker-lite -m ` (see `--help` for the full flag list: `-m/--mount`, `-r/--run`, `-u/--umount`, `-c/--cleanup`, `-l/--list-images`, `-n/--no-nsenter`, `--user`, `--group`, `-v/--volume`, - `--list-volumes`, `-t/--test`, `--log-level`, `-h/--help`, `-V/--version`) + `--list-volumes`, `--delete-volume`, `--delete-volume-full`, `-t/--test`, + `--log-level`, `-h/--help`, `-V/--version`) - Run tests: `meson test -C buildDir` ## Code style diff --git a/README.md b/README.md index 33fbd98..9a22710 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ slocker-lite -c|--cleanup slocker-lite -l|--list-images slocker-lite -v|--volume slocker-lite --list-volumes +slocker-lite --delete-volume +slocker-lite --delete-volume-full slocker-lite -t|--test slocker-lite -h|--help slocker-lite -V|--version @@ -76,6 +78,8 @@ slocker-lite -V|--version | `-l, --list-images ` | List OCI Image Layout tars (`*.tar`, `*.tar.*`) found directly in ``, with their `name:tag`. | | `-v, --volume ` | Create a named volume mapped to a host directory (created if missing), recorded in the config file's `volumes` section. Fails if the name or directory is already used by an existing volume. | | `--list-volumes` | List all named volumes (see `-v/--volume`) with their host directory. | +| `--delete-volume ` | Remove a named volume from the config. The host directory is left untouched. | +| `--delete-volume-full ` | Like `--delete-volume`, but also recursively deletes the volume's host directory. | | `-t, --test` | Print which `bwrap --unshare-xxx` namespaces the running kernel supports. | | `--log-level ` | Set log verbosity (`trace`, `debug`, `info`, `warn`, `error`, `critical`, `off`). | | `-h, --help` | Print usage and exit. | @@ -104,6 +108,12 @@ sudo ./buildDir/slocker-lite -r myimage.tar --user git # List all named volumes ./buildDir/slocker-lite --list-volumes + +# Remove a named volume (keeps its host directory) +./buildDir/slocker-lite --delete-volume mydata + +# Remove a named volume and delete its host directory too +./buildDir/slocker-lite --delete-volume-full mydata ``` ## Configuration diff --git a/src/main.cpp b/src/main.cpp index 6799f22..b8f4611 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,18 +41,32 @@ namespace { constexpr std::array kRequiredTools = {"containers-storage", "bwrap"}; -enum class Mode { kNone, kMount, kUnmount, kTest, kRun, kCleanup, kListImages, kVolume, kListVolumes }; +enum class Mode { + kNone, + kMount, + kUnmount, + kTest, + kRun, + kCleanup, + kListImages, + kVolume, + kListVolumes, + kDeleteVolume, + kDeleteVolumeFull +}; -// --log-level/--user/--group/--list-volumes have no short form (--log-level's was -// freed up so -l could become --list-images; -u is already --umount; --list-volumes -// has no natural free letter left), so they need long-option vals outside the -// printable-char range short options use. +// --log-level/--user/--group/--list-volumes/--delete-volume[-full] have no short +// form (--log-level's was freed up so -l could become --list-images; -u is already +// --umount; the rest have no natural free letter left, or don't need one), so they +// need long-option vals outside the printable-char range short options use. constexpr int kLogLevelOpt = 256; constexpr int kUserOpt = 257; constexpr int kGroupOpt = 258; constexpr int kListVolumesOpt = 259; +constexpr int kDeleteVolumeOpt = 260; +constexpr int kDeleteVolumeFullOpt = 261; -constexpr std::array kLongOptions = {{ +constexpr std::array kLongOptions = {{ {"help", no_argument, nullptr, 'h'}, {"version", no_argument, nullptr, 'V'}, {"test", no_argument, nullptr, 't'}, @@ -67,6 +81,8 @@ constexpr std::array kLongOptions = {{ {"group", required_argument, nullptr, kGroupOpt}, {"volume", required_argument, nullptr, 'v'}, {"list-volumes", no_argument, nullptr, kListVolumesOpt}, + {"delete-volume", required_argument, nullptr, kDeleteVolumeOpt}, + {"delete-volume-full", required_argument, nullptr, kDeleteVolumeFullOpt}, {nullptr, 0, nullptr, 0}, }}; @@ -79,6 +95,8 @@ void print_usage(const char* prog) { " {0} -l|--list-images \n" " {0} -v|--volume \n" " {0} --list-volumes\n" + " {0} --delete-volume \n" + " {0} --delete-volume-full \n" " {0} -t|--test\n" " {0} -h|--help\n" " {0} -V|--version\n" @@ -115,6 +133,12 @@ void print_usage(const char* prog) { " file's volumes section\n" " --list-volumes list all named volumes (see -v/--volume) with\n" " their host directory\n" + " --delete-volume \n" + " remove a named volume from the config (the host\n" + " directory is left untouched)\n" + " --delete-volume-full \n" + " like --delete-volume, but also recursively\n" + " deletes the volume's host directory\n" " -t, --test run the test suite\n" " --log-level set log verbosity (trace, debug, info, warn,\n" " error, critical, off)\n" @@ -325,6 +349,36 @@ int list_volumes_command(const AppConfig& config) { return 0; } +int delete_volume_command(const std::string& name, const std::filesystem::path& config_path, + AppConfig& config, bool delete_directory) { + auto it = std::find_if(config.volumes.begin(), config.volumes.end(), + [&](const VolumeEntry& volume) { return volume.name == name; }); + if (it == config.volumes.end()) { + spdlog::error("no volume named '{}' exists", name); + return 1; + } + + if (delete_directory) { + if (!std::filesystem::exists(it->directory)) { + spdlog::warn("directory {} does not exist, nothing to delete", it->directory); + } + std::error_code ec; + std::filesystem::remove_all(it->directory, ec); + if (ec) { + spdlog::error("failed to delete directory {}: {}", it->directory, ec.message()); + return 1; + } + } + + config.volumes.erase(it); + if (!write_config_file(config_path, config)) { + return 1; + } + + fmt::print("deleted volume '{}'\n", name); + return 0; +} + int run_container(const std::filesystem::path& image_tar, const std::vector& requested_command, bool use_nsenter, const std::optional& user, const std::optional& group) { @@ -413,7 +467,9 @@ int main(int argc, char* argv[]) { case 'c': case 'l': case 'v': - case kListVolumesOpt: { + case kListVolumesOpt: + case kDeleteVolumeOpt: + case kDeleteVolumeFullOpt: { Mode requested; switch (opt) { case 't': @@ -437,9 +493,15 @@ int main(int argc, char* argv[]) { case 'v': requested = Mode::kVolume; break; - default: + case kListVolumesOpt: requested = Mode::kListVolumes; break; + case kDeleteVolumeOpt: + requested = Mode::kDeleteVolume; + break; + default: + requested = Mode::kDeleteVolumeFull; + break; } if (mode != Mode::kNone && mode != requested) { spdlog::error("multiple actions specified"); @@ -516,6 +578,9 @@ int main(int argc, char* argv[]) { if (mode == Mode::kListVolumes) { return list_volumes_command(*config); } + if (mode == Mode::kDeleteVolume || mode == Mode::kDeleteVolumeFull) { + return delete_volume_command(mode_arg, config_path, *config, mode == Mode::kDeleteVolumeFull); + } if (mode == Mode::kRun) { std::vector command(argv + optind, argv + argc); // As root, containers-storage mount doesn't need to reexec into a private