diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index d252d7a1..6b398117 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -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, + 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, + label: DriveLabel, + pool: Option, +) -> 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() diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs index b0f2cf59..a16b06ac 100644 --- a/src/bin/proxmox-tape.rs +++ b/src/bin/proxmox-tape.rs @@ -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()) diff --git a/src/tape/media_pool.rs b/src/tape/media_pool.rs index 4064d32e..60d55278 100644 --- a/src/tape/media_pool.rs +++ b/src/tape/media_pool.rs @@ -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. //! //!