diff --git a/CLAUDE.md b/CLAUDE.md index 9124511..d3ff8c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,13 +17,15 @@ 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()`). `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 `: see `config_file.{h,cpp}` below for what a + `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()` 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`). - `oci_image.{h,cpp}` — validates/parses the OCI Image Layout tar (libarchive + nlohmann_json) and extracts layer blobs. `list_oci_images()` scans a directory @@ -152,8 +154,8 @@ Build directory is `buildDir/` (already configured). (see `priv_drop_helper.cpp` in "Project state") - 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`, `-t/--test`, - `--log-level`, `-h/--help`, `-V/--version`) + `-l/--list-images`, `-n/--no-nsenter`, `--user`, `--group`, `-v/--volume`, + `--list-volumes`, `-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 613553b..33fbd98 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ slocker-lite -u|--umount slocker-lite -c|--cleanup slocker-lite -l|--list-images slocker-lite -v|--volume +slocker-lite --list-volumes slocker-lite -t|--test slocker-lite -h|--help slocker-lite -V|--version @@ -74,6 +75,7 @@ slocker-lite -V|--version | `--group ` | With `--user`, use this group (name or numeric gid) instead of the user's primary group. | | `-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. | | `-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. | @@ -99,6 +101,9 @@ sudo ./buildDir/slocker-lite -r myimage.tar --user git # Create a named volume backed by a host directory ./buildDir/slocker-lite -v mydata ~/slocker-volumes/mydata + +# List all named volumes +./buildDir/slocker-lite --list-volumes ``` ## Configuration diff --git a/src/main.cpp b/src/main.cpp index 49c5da1..6799f22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,16 +41,18 @@ namespace { constexpr std::array kRequiredTools = {"containers-storage", "bwrap"}; -enum class Mode { kNone, kMount, kUnmount, kTest, kRun, kCleanup, kListImages, kVolume }; +enum class Mode { kNone, kMount, kUnmount, kTest, kRun, kCleanup, kListImages, kVolume, kListVolumes }; -// --log-level/--user/--group have no short form (--log-level's was freed up so -l -// could become --list-images; -u is already --umount), so they need long-option -// vals outside the printable-char range short options use. +// --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. constexpr int kLogLevelOpt = 256; constexpr int kUserOpt = 257; constexpr int kGroupOpt = 258; +constexpr int kListVolumesOpt = 259; -constexpr std::array kLongOptions = {{ +constexpr std::array kLongOptions = {{ {"help", no_argument, nullptr, 'h'}, {"version", no_argument, nullptr, 'V'}, {"test", no_argument, nullptr, 't'}, @@ -64,6 +66,7 @@ constexpr std::array kLongOptions = {{ {"user", required_argument, nullptr, kUserOpt}, {"group", required_argument, nullptr, kGroupOpt}, {"volume", required_argument, nullptr, 'v'}, + {"list-volumes", no_argument, nullptr, kListVolumesOpt}, {nullptr, 0, nullptr, 0}, }}; @@ -75,6 +78,7 @@ void print_usage(const char* prog) { " {0} -c|--cleanup \n" " {0} -l|--list-images \n" " {0} -v|--volume \n" + " {0} --list-volumes\n" " {0} -t|--test\n" " {0} -h|--help\n" " {0} -V|--version\n" @@ -109,6 +113,8 @@ void print_usage(const char* prog) { " -v, --volume create a named volume mapped to a host directory\n" " (created if missing), recorded in the config\n" " file's volumes section\n" + " --list-volumes list all named volumes (see -v/--volume) with\n" + " their host directory\n" " -t, --test run the test suite\n" " --log-level set log verbosity (trace, debug, info, warn,\n" " error, critical, off)\n" @@ -300,6 +306,25 @@ int create_volume_command(const std::string& name, const std::string& directory, return 0; } +int list_volumes_command(const AppConfig& config) { + size_t max_len = 0; + for (const auto& volume : config.volumes) { + max_len = std::max(max_len, volume.name.size()); + } + + // Same tab-alignment scheme as list_images_command(): pad each name to one + // 8-column tab stop past the longest one, however long that is. + constexpr size_t kTabWidth = 8; + size_t target_tabs = max_len / kTabWidth + 1; + + for (const auto& volume : config.volumes) { + size_t tabs_used = volume.name.size() / kTabWidth; + size_t tabs_needed = target_tabs > tabs_used ? target_tabs - tabs_used : 1; + fmt::print("{}{}{}\n", volume.name, std::string(tabs_needed, '\t'), volume.directory); + } + 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) { @@ -387,7 +412,8 @@ int main(int argc, char* argv[]) { case 'r': case 'c': case 'l': - case 'v': { + case 'v': + case kListVolumesOpt: { Mode requested; switch (opt) { case 't': @@ -408,9 +434,12 @@ int main(int argc, char* argv[]) { case 'l': requested = Mode::kListImages; break; - default: + case 'v': requested = Mode::kVolume; break; + default: + requested = Mode::kListVolumes; + break; } if (mode != Mode::kNone && mode != requested) { spdlog::error("multiple actions specified"); @@ -484,6 +513,9 @@ int main(int argc, char* argv[]) { if (mode == Mode::kVolume) { return create_volume_command(mode_arg, argv[optind], config_path, *config); } + if (mode == Mode::kListVolumes) { + return list_volumes_command(*config); + } if (mode == Mode::kRun) { std::vector command(argv + optind, argv + argc); // As root, containers-storage mount doesn't need to reexec into a private