tape: implement label command
This commit is contained in:
		| @ -1,25 +1,45 @@ | ||||
| use std::path::Path; | ||||
| use anyhow::{bail, Error}; | ||||
| use serde_json::Value; | ||||
|  | ||||
| use proxmox::api::{api, Router, SubdirMap}; | ||||
| use proxmox::{sortable, identity, list_subdirs_api_method}; | ||||
| use proxmox::{ | ||||
|     sortable, | ||||
|     identity, | ||||
|     list_subdirs_api_method, | ||||
|     tools::Uuid, | ||||
|     sys::error::SysError, | ||||
|     api::{ | ||||
|         api, | ||||
|         Router, | ||||
|         SubdirMap, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| use crate::{ | ||||
|     config, | ||||
|     api2::types::{ | ||||
|         DRIVE_ID_SCHEMA, | ||||
|         MEDIA_LABEL_SCHEMA, | ||||
|         MEDIA_POOL_NAME_SCHEMA, | ||||
|         LinuxTapeDrive, | ||||
|         ScsiTapeChanger, | ||||
|         TapeDeviceInfo, | ||||
|     }, | ||||
|     tape::{ | ||||
|         TAPE_STATUS_DIR, | ||||
|         TapeDriver, | ||||
|         MediaChange, | ||||
|         Inventory, | ||||
|         MediaId, | ||||
|         mtx_load, | ||||
|         mtx_unload, | ||||
|         linux_tape_device_list, | ||||
|         open_drive, | ||||
|         media_changer, | ||||
|         file_formats::{ | ||||
|             DriveLabel, | ||||
|             MediaSetLabel, | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| @ -216,6 +236,128 @@ pub fn eject_media(drive: String) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[api( | ||||
|     input: { | ||||
|         properties: { | ||||
|             drive: { | ||||
|                 schema: DRIVE_ID_SCHEMA, | ||||
|             }, | ||||
|             "changer-id": { | ||||
|                 schema: MEDIA_LABEL_SCHEMA, | ||||
|             }, | ||||
|             pool: { | ||||
|                 schema: MEDIA_POOL_NAME_SCHEMA, | ||||
|                 optional: true, | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
| )] | ||||
| /// Label media | ||||
| /// | ||||
| /// Write a new media label to the media in 'drive'. The media is | ||||
| /// assigned to the specified 'pool', or else to the free media pool. | ||||
| /// | ||||
| /// Note: The media need to be empty (you may want to erase it first). | ||||
| pub fn label_media( | ||||
|     drive: String, | ||||
|     pool: Option<String>, | ||||
|     changer_id: String, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     if let Some(ref pool) = pool { | ||||
|         let (pool_config, _digest) = config::media_pool::config()?; | ||||
|  | ||||
|         if pool_config.sections.get(pool).is_none() { | ||||
|             bail!("no such pool ('{}')", pool); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let (config, _digest) = config::drive::config()?; | ||||
|  | ||||
|     let mut drive = open_drive(&config, &drive)?; | ||||
|  | ||||
|     drive.rewind()?; | ||||
|  | ||||
|     match drive.read_next_file() { | ||||
|         Ok(Some(_file)) => bail!("media is not empty (erase first)"), | ||||
|         Ok(None) => { /* EOF mark at BOT, assume tape is empty */ }, | ||||
|         Err(err) => { | ||||
|             if err.is_errno(nix::errno::Errno::ENOSPC) || err.is_errno(nix::errno::Errno::EIO) { | ||||
|                 /* assume tape is empty */ | ||||
|             } else { | ||||
|                 bail!("media read error - {}", err); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let ctime = proxmox::tools::time::epoch_i64(); | ||||
|     let label = DriveLabel { | ||||
|         changer_id: changer_id.to_string(), | ||||
|         uuid: Uuid::generate(), | ||||
|         ctime, | ||||
|     }; | ||||
|  | ||||
|     write_media_label(&mut drive, label, pool) | ||||
| } | ||||
|  | ||||
| fn write_media_label( | ||||
|     drive: &mut Box<dyn TapeDriver>, | ||||
|     label: DriveLabel, | ||||
|     pool: Option<String>, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     drive.label_tape(&label)?; | ||||
|  | ||||
|     let mut media_set_label = None; | ||||
|  | ||||
|     if let Some(ref pool) = pool { | ||||
|         // assign media to pool by writing special media set label | ||||
|         println!("Label media '{}' for pool '{}'", label.changer_id, pool); | ||||
|         let set = MediaSetLabel::with_data(&pool, [0u8; 16].into(), 0, label.ctime); | ||||
|  | ||||
|         drive.write_media_set_label(&set)?; | ||||
|         media_set_label = Some(set); | ||||
|     } else { | ||||
|         println!("Label media '{}' (no pool assignment)", label.changer_id); | ||||
|     } | ||||
|  | ||||
|     let media_id = MediaId { label, media_set_label }; | ||||
|  | ||||
|     let mut inventory = Inventory::load(Path::new(TAPE_STATUS_DIR))?; | ||||
|     inventory.store(media_id.clone())?; | ||||
|  | ||||
|     drive.rewind()?; | ||||
|  | ||||
|     match drive.read_label() { | ||||
|         Ok(Some(info)) => { | ||||
|             if info.label.uuid != media_id.label.uuid { | ||||
|                 bail!("verify label failed - got wrong label uuid"); | ||||
|             } | ||||
|             if let Some(ref pool) = pool { | ||||
|                 match info.media_set_label { | ||||
|                     Some((set, _)) => { | ||||
|                         if set.uuid != [0u8; 16].into() { | ||||
|                             bail!("verify media set label failed - got wrong set uuid"); | ||||
|                         } | ||||
|                         if &set.pool != pool { | ||||
|                             bail!("verify media set label failed - got wrong pool"); | ||||
|                         } | ||||
|                     } | ||||
|                     None => { | ||||
|                         bail!("verify media set label failed (missing set label)"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         Ok(None) => bail!("verify label failed (got empty media)"), | ||||
|         Err(err) => bail!("verify label failed - {}", err), | ||||
|     }; | ||||
|  | ||||
|     drive.rewind()?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[sortable] | ||||
| pub const SUBDIRS: SubdirMap = &sorted!([ | ||||
|     ( | ||||
| @ -228,6 +370,11 @@ pub const SUBDIRS: SubdirMap = &sorted!([ | ||||
|         &Router::new() | ||||
|             .put(&API_METHOD_ERASE_MEDIA) | ||||
|     ), | ||||
|     ( | ||||
|         "label-media", | ||||
|         &Router::new() | ||||
|             .put(&API_METHOD_LABEL_MEDIA) | ||||
|     ), | ||||
|     ( | ||||
|         "load-slot", | ||||
|         &Router::new() | ||||
|  | ||||
| @ -17,11 +17,13 @@ use proxmox_backup::{ | ||||
|         types::{ | ||||
|             DRIVE_ID_SCHEMA, | ||||
|             MEDIA_LABEL_SCHEMA, | ||||
|             MEDIA_POOL_NAME_SCHEMA, | ||||
|         }, | ||||
|     }, | ||||
|     config::{ | ||||
|         self, | ||||
|         drive::complete_drive_name, | ||||
|         media_pool::complete_pool_name, | ||||
|     }, | ||||
|     tape::{ | ||||
|         complete_media_changer_id, | ||||
| @ -190,6 +192,43 @@ fn load_media( | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[api( | ||||
|     input: { | ||||
|         properties: { | ||||
|             pool: { | ||||
|                 schema: MEDIA_POOL_NAME_SCHEMA, | ||||
|                 optional: true, | ||||
|             }, | ||||
|             drive: { | ||||
|                 schema: DRIVE_ID_SCHEMA, | ||||
|                 optional: true, | ||||
|             }, | ||||
|             "changer-id": { | ||||
|                 schema: MEDIA_LABEL_SCHEMA, | ||||
|             }, | ||||
|        }, | ||||
|     }, | ||||
| )] | ||||
| /// Label media | ||||
| fn label_media( | ||||
|     mut param: Value, | ||||
|     rpcenv: &mut dyn RpcEnvironment, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     let (config, _digest) = config::drive::config()?; | ||||
|  | ||||
|     param["drive"] = lookup_drive_name(¶m, &config)?.into(); | ||||
|  | ||||
|     let info = &api2::tape::drive::API_METHOD_LABEL_MEDIA; | ||||
|  | ||||
|     match info.handler { | ||||
|         ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
|  | ||||
|     let cmd_def = CliCommandMap::new() | ||||
| @ -208,6 +247,13 @@ fn main() { | ||||
|             CliCommand::new(&API_METHOD_EJECT_MEDIA) | ||||
|                 .completion_cb("drive", complete_drive_name) | ||||
|         ) | ||||
|         .insert( | ||||
|             "label", | ||||
|             CliCommand::new(&API_METHOD_LABEL_MEDIA) | ||||
|                 .completion_cb("drive", complete_drive_name) | ||||
|                 .completion_cb("pool", complete_pool_name) | ||||
|  | ||||
|         ) | ||||
|         .insert("changer", changer_commands()) | ||||
|         .insert("drive", drive_commands()) | ||||
|         .insert("pool", pool_commands()) | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| //! A set of backup medias. | ||||
| //! | ||||
| //! This struct manages backup media state during backup. The main | ||||
| //! purpose ist to allocate media sets and assing new tapes to it. | ||||
| //! purpose is to allocate media sets and assing new tapes to it. | ||||
| //! | ||||
| //! | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user