diff --git a/src/api2/tape/media.rs b/src/api2/tape/media.rs index a7f10efd..c2833420 100644 --- a/src/api2/tape/media.rs +++ b/src/api2/tape/media.rs @@ -1,9 +1,11 @@ use std::path::Path; -use anyhow::Error; +use anyhow::{bail, format_err, Error}; -use proxmox::api::{api, Router, SubdirMap}; -use proxmox::list_subdirs_api_method; +use proxmox::{ + api::{api, Router, SubdirMap}, + list_subdirs_api_method, +}; use crate::{ config::{ @@ -11,6 +13,7 @@ use crate::{ }, api2::types::{ MEDIA_POOL_NAME_SCHEMA, + MEDIA_LABEL_SCHEMA, MediaPoolConfig, MediaListEntry, MediaStatus, @@ -154,7 +157,57 @@ pub async fn list_media(pool: Option) -> Result, Err Ok(list) } +#[api( + input: { + properties: { + "changer-id": { + schema: MEDIA_LABEL_SCHEMA, + }, + force: { + description: "Force removal (even if media is used in a media set).", + type: bool, + optional: true, + }, + }, + }, +)] +/// Destroy media (completely remove from database) +pub fn destroy_media(changer_id: String, force: Option,) -> Result<(), Error> { + + let force = force.unwrap_or(false); + + let status_path = Path::new(TAPE_STATUS_DIR); + let mut inventory = Inventory::load(status_path)?; + + let media_id = inventory.find_media_by_changer_id(&changer_id) + .ok_or_else(|| format_err!("no such media '{}'", changer_id))?; + + if !force { + if let Some(ref set) = media_id.media_set_label { + let is_empty = set.uuid.as_ref() == [0u8;16]; + if !is_empty { + bail!("media '{}' contains data (please use 'force' flag to remove.", changer_id); + } + } + } + + let uuid = media_id.label.uuid.clone(); + drop(media_id); + + inventory.remove_media(&uuid)?; + + let mut state_db = MediaStateDatabase::load(status_path)?; + state_db.remove_media(&uuid)?; + + Ok(()) +} + const SUBDIRS: SubdirMap = &[ + ( + "destroy", + &Router::new() + .get(&API_METHOD_DESTROY_MEDIA) + ), ( "list", &Router::new() diff --git a/src/bin/proxmox_tape/media.rs b/src/bin/proxmox_tape/media.rs index 783a4281..66d4408f 100644 --- a/src/bin/proxmox_tape/media.rs +++ b/src/bin/proxmox_tape/media.rs @@ -20,6 +20,7 @@ use proxmox_backup::{ MediaListEntry, }, }, + tape::complete_media_changer_id, config::{ media_pool::complete_pool_name, }, @@ -33,6 +34,12 @@ pub fn media_commands() -> CommandLineInterface { CliCommand::new(&API_METHOD_LIST_MEDIA) .completion_cb("pool", complete_pool_name) ) + .insert( + "destroy-media", + CliCommand::new(&api2::tape::media::API_METHOD_DESTROY_MEDIA) + .arg_param(&["changer-id"]) + .completion_cb("changer-id", complete_media_changer_id) + ) ; cmd_def.into() diff --git a/src/tape/inventory.rs b/src/tape/inventory.rs index 7ce1fb08..1bb9377e 100644 --- a/src/tape/inventory.rs +++ b/src/tape/inventory.rs @@ -251,6 +251,16 @@ impl Inventory { Ok(()) } + /// Remove a single media persistently + pub fn remove_media(&mut self, uuid: &Uuid) -> Result<(), Error> { + let _lock = self.lock()?; + self.map = Self::load_media_db(&self.inventory_path)?; + self.map.remove(uuid); + self.update_helpers(); + self.replace_file()?; + Ok(()) + } + /// Lookup media pub fn lookup_media(&self, uuid: &Uuid) -> Option<&MediaId> { self.map.get(uuid) diff --git a/src/tape/media_state_database.rs b/src/tape/media_state_database.rs index 270e2622..dafb66bb 100644 --- a/src/tape/media_state_database.rs +++ b/src/tape/media_state_database.rs @@ -201,6 +201,14 @@ impl MediaStateDatabase { self.store() } + /// Lock database, reload database, remove media, store database + pub fn remove_media(&mut self, uuid: &Uuid) -> Result<(), Error> { + let _lock = self.lock()?; + self.map = Self::load_media_db(&self.database_path)?; + self.map.remove(uuid); + self.store() + } + fn store(&self) -> Result<(), Error> { let mut list = Vec::new();