From 04df41cec19d879906b70810f7fdfb36253d72a0 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Sun, 10 Jan 2021 15:32:52 +0100 Subject: [PATCH] tape: more MediaChange cleanups Try to provide generic implementation for complex operations: - unload_to_free_slot - load_media - export media - clean drive - online_media_changer_ids --- src/tape/changer/mod.rs | 185 +++++++++++++++++++++++++++++++-- src/tape/changer/mtx.rs | 178 +++---------------------------- src/tape/drive/virtual_tape.rs | 18 ++++ 3 files changed, 210 insertions(+), 171 deletions(-) diff --git a/src/tape/changer/mod.rs b/src/tape/changer/mod.rs index 954a879d..7bcf56ca 100644 --- a/src/tape/changer/mod.rs +++ b/src/tape/changer/mod.rs @@ -10,11 +10,17 @@ pub use mtx_wrapper::*; mod mtx; pub use mtx::*; -use anyhow::Error; +use anyhow::{bail, Error}; -/// Interface to media change devices +/// Interface to the media changer device for a single drive pub trait MediaChange { + /// Drive number inside changer + fn drive_number(&self) -> u64; + + /// Drive name (used for debug messages) + fn drive_name(&self) -> &str; + /// Returns the changer status fn status(&mut self) -> Result; @@ -30,8 +36,63 @@ pub trait MediaChange { /// /// This unloads first if the drive is already loaded with another media. /// - /// Note: This refuses to load media inside import/export slots. - fn load_media(&mut self, changer_id: &str) -> Result<(), Error>; + /// Note: This refuses to load media inside import/export + /// slots. Also, you cannot load cleaning units with this + /// interface. + fn load_media(&mut self, changer_id: &str) -> Result<(), Error> { + + if changer_id.starts_with("CLN") { + bail!("unable to load media '{}' (seems top be a a cleaning units)", changer_id); + } + + let mut status = self.status()?; + + let mut unload_drive = false; + + // already loaded? + for (i, drive_status) in status.drives.iter().enumerate() { + if let ElementStatus::VolumeTag(ref tag) = drive_status.status { + if *tag == changer_id { + if i as u64 != self.drive_number() { + bail!("unable to load media '{}' - media in wrong drive ({} != {})", + changer_id, i, self.drive_number()); + } + return Ok(()) // already loaded + } + } + if i as u64 == self.drive_number() { + match drive_status.status { + ElementStatus::Empty => { /* OK */ }, + _ => unload_drive = true, + } + } + } + + if unload_drive { + self.unload_to_free_slot(status)?; + status = self.status()?; + } + + let mut slot = None; + for (i, (import_export, element_status)) in status.slots.iter().enumerate() { + if let ElementStatus::VolumeTag(tag) = element_status { + if *tag == changer_id { + if *import_export { + bail!("unable to load media '{}' - inside import/export slot", changer_id); + } + slot = Some(i+1); + break; + } + } + } + + let slot = match slot { + None => bail!("unable to find media '{}' (offline?)", changer_id), + Some(slot) => slot, + }; + + self.load_media_from_slot(slot as u64) + } /// Unload media from drive /// @@ -73,12 +134,124 @@ pub trait MediaChange { /// /// This fail if there is no cleaning cartridge online. Any media /// inside the drive is automatically unloaded. - fn clean_drive(&mut self) -> Result<(), Error>; + fn clean_drive(&mut self) -> Result<(), Error> { + let status = self.status()?; + + let mut cleaning_cartridge_slot = None; + + for (i, (import_export, element_status)) in status.slots.iter().enumerate() { + if *import_export { continue; } + if let ElementStatus::VolumeTag(ref tag) = element_status { + if tag.starts_with("CLN") { + cleaning_cartridge_slot = Some(i + 1); + break; + } + } + } + + let cleaning_cartridge_slot = match cleaning_cartridge_slot { + None => bail!("clean failed - unable to find cleaning cartridge"), + Some(cleaning_cartridge_slot) => cleaning_cartridge_slot as u64, + }; + + if let Some(drive_status) = status.drives.get(self.drive_number() as usize) { + match drive_status.status { + ElementStatus::Empty => { /* OK */ }, + _ => self.unload_to_free_slot(status)?, + } + } + + self.load_media_from_slot(cleaning_cartridge_slot)?; + + self.unload_media(Some(cleaning_cartridge_slot))?; + + Ok(()) + } /// Export media /// /// By moving the media to an empty import-export slot. Returns /// Some(slot) if the media was exported. Returns None if the media is /// not online (already exported). - fn export_media(&mut self, changer_id: &str) -> Result, Error>; + fn export_media(&mut self, changer_id: &str) -> Result, Error> { + let status = self.status()?; + + let mut unload_from_drive = false; + if let Some(drive_status) = status.drives.get(self.drive_number() as usize) { + if let ElementStatus::VolumeTag(ref tag) = drive_status.status { + if tag == changer_id { + unload_from_drive = true; + } + } + } + + let mut from = None; + let mut to = None; + + for (i, (import_export, element_status)) in status.slots.iter().enumerate() { + if *import_export { + if to.is_some() { continue; } + if let ElementStatus::Empty = element_status { + to = Some(i as u64 + 1); + } + } else { + if let ElementStatus::VolumeTag(ref tag) = element_status { + if tag == changer_id { + from = Some(i as u64 + 1); + } + } + } + } + + if unload_from_drive { + match to { + Some(to) => { + self.unload_media(Some(to))?; + Ok(Some(to)) + } + None => bail!("unable to find free export slot"), + } + } else { + match (from, to) { + (Some(from), Some(to)) => { + self.transfer_media(from, to)?; + Ok(Some(to)) + } + (Some(_from), None) => bail!("unable to find free export slot"), + (None, _) => Ok(None), // not online + } + } + } + + /// Unload media to a free storage slot + /// + /// If posible to the slot it was previously loaded from. + /// + /// Note: This method consumes status - so please read again afterward. + fn unload_to_free_slot(&mut self, status: MtxStatus) -> Result<(), Error> { + + let drive_status = &status.drives[self.drive_number() as usize]; + if let Some(slot) = drive_status.loaded_slot { + // check if original slot is empty/usable + if let Some(info) = status.slots.get(slot as usize - 1) { + if let (_import_export, ElementStatus::Empty) = info { + return self.unload_media(Some(slot)); + } + } + } + + let mut free_slot = None; + for i in 0..status.slots.len() { + if status.slots[i].0 { continue; } // skip import/export slots + if let ElementStatus::Empty = status.slots[i].1 { + free_slot = Some((i+1) as u64); + break; + } + } + if let Some(slot) = free_slot { + self.unload_media(Some(slot)) + } else { + bail!("drive '{}' unload failure - no free slot", self.drive_name()); + } + } } diff --git a/src/tape/changer/mtx.rs b/src/tape/changer/mtx.rs index f4096429..79e12bb1 100644 --- a/src/tape/changer/mtx.rs +++ b/src/tape/changer/mtx.rs @@ -4,7 +4,6 @@ use crate::{ tape::changer::{ MediaChange, MtxStatus, - ElementStatus, mtx_status, mtx_transfer, mtx_load, @@ -19,7 +18,7 @@ use crate::{ /// Implements MediaChange using 'mtx' linux cli tool pub struct MtxMediaChanger { drive_name: String, // used for error messages - drivenum: u64, + drive_number: u64, config: ScsiTapeChanger, } @@ -34,41 +33,22 @@ impl MtxMediaChanger { Ok(Self { drive_name: drive_config.name.clone(), - drivenum: drive_config.changer_drive_id.unwrap_or(0), + drive_number: drive_config.changer_drive_id.unwrap_or(0), config: changer_config, }) } } -fn unload_to_free_slot(drive_name: &str, path: &str, status: &MtxStatus, drivenum: u64) -> Result<(), Error> { - - if drivenum >= status.drives.len() as u64 { - bail!("unload drive '{}' got unexpected drive number '{}' - changer only has '{}' drives", - drive_name, drivenum, status.drives.len()); - } - let drive_status = &status.drives[drivenum as usize]; - if let Some(slot) = drive_status.loaded_slot { - // fixme: check if slot is free - mtx_unload(path, slot, drivenum) - } else { - let mut free_slot = None; - for i in 0..status.slots.len() { - if status.slots[i].0 { continue; } // skip import/export slots - if let ElementStatus::Empty = status.slots[i].1 { - free_slot = Some((i+1) as u64); - break; - } - } - if let Some(slot) = free_slot { - mtx_unload(path, slot, drivenum) - } else { - bail!("drive '{}' unload failure - no free slot", drive_name); - } - } -} - impl MediaChange for MtxMediaChanger { + fn drive_number(&self) -> u64 { + self.drive_number + } + + fn drive_name(&self) -> &str { + &self.drive_name + } + fn status(&mut self) -> Result { mtx_status(&self.config) } @@ -78,151 +58,19 @@ impl MediaChange for MtxMediaChanger { } fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error> { - mtx_load(&self.config.path, slot, self.drivenum) - } - - fn load_media(&mut self, changer_id: &str) -> Result<(), Error> { - - if changer_id.starts_with("CLN") { - bail!("unable to load media '{}' (seems top be a a cleaning units)", changer_id); - } - - let status = self.status()?; - - // already loaded? - for (i, drive_status) in status.drives.iter().enumerate() { - if let ElementStatus::VolumeTag(ref tag) = drive_status.status { - if *tag == changer_id { - if i as u64 != self.drivenum { - bail!("unable to load media '{}' - media in wrong drive ({} != {})", - changer_id, i, self.drivenum); - } - return Ok(()) - } - } - if i as u64 == self.drivenum { - match drive_status.status { - ElementStatus::Empty => { /* OK */ }, - _ => unload_to_free_slot(&self.drive_name, &self.config.path, &status, self.drivenum)?, - } - } - } - - let mut slot = None; - for (i, (import_export, element_status)) in status.slots.iter().enumerate() { - if let ElementStatus::VolumeTag(tag) = element_status { - if *tag == changer_id { - if *import_export { - bail!("unable to load media '{}' - inside import/export slot", changer_id); - } - slot = Some(i+1); - break; - } - } - } - - let slot = match slot { - None => bail!("unable to find media '{}' (offline?)", changer_id), - Some(slot) => slot, - }; - - mtx_load(&self.config.path, slot as u64, self.drivenum) + mtx_load(&self.config.path, slot, self.drive_number) } fn unload_media(&mut self, target_slot: Option) -> Result<(), Error> { if let Some(target_slot) = target_slot { - mtx_unload(&self.config.path, target_slot, self.drivenum) + mtx_unload(&self.config.path, target_slot, self.drive_number) } else { let status = self.status()?; - unload_to_free_slot(&self.drive_name, &self.config.path, &status, self.drivenum) + self.unload_to_free_slot(status) } } fn eject_on_unload(&self) -> bool { true } - - fn clean_drive(&mut self) -> Result<(), Error> { - let status = self.status()?; - - let mut cleaning_cartridge_slot = None; - - for (i, (import_export, element_status)) in status.slots.iter().enumerate() { - if *import_export { continue; } - if let ElementStatus::VolumeTag(ref tag) = element_status { - if tag.starts_with("CLN") { - cleaning_cartridge_slot = Some(i + 1); - break; - } - } - } - - let cleaning_cartridge_slot = match cleaning_cartridge_slot { - None => bail!("clean failed - unable to find cleaning cartridge"), - Some(cleaning_cartridge_slot) => cleaning_cartridge_slot as u64, - }; - - if let Some(drive_status) = status.drives.get(self.drivenum as usize) { - match drive_status.status { - ElementStatus::Empty => { /* OK */ }, - _ => unload_to_free_slot(&self.drive_name, &self.config.path, &status, self.drivenum)?, - } - } - - mtx_load(&self.config.path, cleaning_cartridge_slot, self.drivenum)?; - - mtx_unload(&self.config.path, cleaning_cartridge_slot, self.drivenum)?; - - Ok(()) - } - - fn export_media(&mut self, changer_id: &str) -> Result, Error> { - let status = self.status()?; - - let mut from_drive = None; - if let Some(drive_status) = status.drives.get(self.drivenum as usize) { - if let ElementStatus::VolumeTag(ref tag) = drive_status.status { - if tag == changer_id { - from_drive = Some(self.drivenum); - } - } - } - - let mut from = None; - let mut to = None; - - for (i, (import_export, element_status)) in status.slots.iter().enumerate() { - if *import_export { - if to.is_some() { continue; } - if let ElementStatus::Empty = element_status { - to = Some(i as u64 + 1); - } - } else { - if let ElementStatus::VolumeTag(ref tag) = element_status { - if tag == changer_id { - from = Some(i as u64 + 1); - } - } - } - } - - if let Some(drivenum) = from_drive { - match to { - Some(to) => { - mtx_unload(&self.config.path, to, drivenum)?; - Ok(Some(to)) - } - None => bail!("unable to find free export slot"), - } - } else { - match (from, to) { - (Some(from), Some(to)) => { - self.transfer_media(from, to)?; - Ok(Some(to)) - } - (Some(_from), None) => bail!("unable to find free export slot"), - (None, _) => Ok(None), // not online - } - } - } } diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs index ce6fe7c8..2c4c121d 100644 --- a/src/tape/drive/virtual_tape.rs +++ b/src/tape/drive/virtual_tape.rs @@ -48,6 +48,7 @@ impl VirtualTapeDrive { Ok(VirtualTapeHandle { _lock: lock, + drive_name: self.name.clone(), max_size: self.max_size.unwrap_or(64*1024*1024), path: std::path::PathBuf::from(&self.path), }) @@ -71,6 +72,7 @@ struct TapeIndex { } pub struct VirtualTapeHandle { + drive_name: String, path: std::path::PathBuf, max_size: usize, _lock: File, @@ -362,6 +364,14 @@ impl TapeDriver for VirtualTapeHandle { impl MediaChange for VirtualTapeHandle { + fn drive_number(&self) -> u64 { + 0 + } + + fn drive_name(&self) -> &str { + &self.drive_name + } + fn status(&mut self) -> Result { let drive_status = self.load_status()?; @@ -455,6 +465,14 @@ impl MediaChange for VirtualTapeHandle { impl MediaChange for VirtualTapeDrive { + fn drive_number(&self) -> u64 { + 0 + } + + fn drive_name(&self) -> &str { + &self.name + } + fn status(&mut self) -> Result { let mut handle = self.open()?; handle.status()