diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..f8d29c5 --- /dev/null +++ b/src/image.rs @@ -0,0 +1,34 @@ +use std::fs; + +use crate::registry::{registry_base_dir, validate_image_name}; +use anyhow::Context; +use clap::Subcommand; + +#[derive(Subcommand, Debug)] +pub enum ImageCmd { + Ls, + Rm { image: String }, +} + +pub fn run(cmd: ImageCmd) -> anyhow::Result<()> { + match cmd { + ImageCmd::Ls => ls(), + ImageCmd::Rm { image } => rm(&image), + } +} + +fn rm(image: &str) -> Result<(), anyhow::Error> { + validate_image_name(image)?; + let image_dir = registry_base_dir()?.join(image); + fs::remove_dir_all(image_dir).context("Failed to remove image dir")?; + Ok(()) +} + +fn ls() -> anyhow::Result<()> { + let dir = fs::read_dir(registry_base_dir()?)?; + for entry in dir { + let entry = entry?.file_name(); + println!(" {}", entry.to_string_lossy()); + } + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 6ea0d18..8cfaa91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,13 @@ mod build; +mod image; mod qemu; mod registry; use anyhow::Result; use clap::{Parser, Subcommand}; +use crate::image::ImageCmd; + #[derive(Parser, Debug)] #[command(about = "Build bootable initrd VMs from container images")] struct Cli { @@ -27,6 +30,9 @@ enum Commands { /// Image tag / registry subdir name name: String, }, + /// List slim images in the registry + #[command(subcommand)] + Image(ImageCmd), } fn main() -> Result<()> { @@ -38,6 +44,9 @@ fn main() -> Result<()> { Commands::Run { name } => { qemu::run(&name)?; } + Commands::Image(cmd) => { + image::run(cmd)?; + } } Ok(()) } diff --git a/src/registry.rs b/src/registry.rs index 6af2ed1..5a8e6bf 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -1,8 +1,8 @@ //! Registry: local storage of built VM artifacts under //! `$XDG_DATA_HOME/slim-rs/registry//`. -use anyhow::{Context, Result, bail}; -use std::path::PathBuf; +use anyhow::{Context, Result, anyhow, bail}; +use std::{fs, io, path::PathBuf}; /// Validate a user-supplied image name before it is used in a registry path. /// @@ -26,30 +26,25 @@ pub(crate) fn validate_image_name(name: &str) -> Result<()> { Ok(()) } +pub(crate) fn registry_base_dir() -> Result { + let base = xdg::BaseDirectories::with_prefix("slim-rs"); + base.create_data_directory("registry") + .context("Failed to create XDG_DATA_HOME subdirectory") +} + pub(crate) fn registry_dir(image: &str) -> Result { validate_image_name(image)?; - let base = xdg::BaseDirectories::with_prefix("slim-rs"); - let initrd_path = base - .place_data_file(format!("registry/{image}/initrd")) - .context("Failed to write to XDG_DATA_HOME")?; - let reg_dir = initrd_path - .parent() - .expect("data file has a parent") - .to_owned(); + let base = registry_base_dir()?; + + let reg_dir = base.join(image); + fs::create_dir(®_dir) + .or_else(|e| { + (e.kind() == io::ErrorKind::AlreadyExists) + .then_some(()) + .ok_or(e) + }) + .with_context(|| anyhow!("Failed to create {reg_dir:?}"))?; - // Defense in depth: the constructed path must stay inside the registry. - let registry_root = base - .place_data_file("registry/.root-check") - .context("Failed to write to XDG_DATA_HOME")? - .parent() - .expect("data file has a parent") - .to_owned(); - if !reg_dir.starts_with(®istry_root) { - bail!( - "invalid image name '{image}': registry path escapes {}", - registry_root.display() - ); - } Ok(reg_dir) }