mod online_status_map;
pub use online_status_map::*;
+use proxmox_schema::ApiType;
use std::path::PathBuf;
use anyhow::{bail, Error};
-use proxmox::tools::fs::{CreateOptions, replace_file, file_read_optional_string};
+use proxmox_sys::fs::{file_read_optional_string, replace_file, CreateOptions};
-use pbs_api_types::{ScsiTapeChanger, LtoTapeDrive};
+use pbs_api_types::{ChangerOptions, LtoTapeDrive, ScsiTapeChanger};
-use pbs_tape::{sg_pt_changer, MtxStatus, ElementStatus};
+use pbs_tape::{linux_list_drives::open_lto_tape_device, sg_pt_changer, ElementStatus, MtxStatus};
+
+use crate::tape::drive::{LtoTapeHandle, TapeDriver};
/// Interface to SCSI changer devices
pub trait ScsiMediaChange {
-
fn status(&mut self, use_cache: bool) -> Result<MtxStatus, Error>;
fn load_slot(&mut self, from_slot: u64, drivenum: u64) -> Result<MtxStatus, Error>;
/// Interface to the media changer device for a single drive
pub trait MediaChange {
-
/// Drive number inside changer
fn drive_number(&self) -> u64;
/// slots. Also, you cannot load cleaning units with this
/// interface.
fn load_media(&mut self, label_text: &str) -> Result<MtxStatus, Error> {
-
if label_text.starts_with("CLN") {
- bail!("unable to load media '{}' (seems to be a cleaning unit)", label_text);
+ bail!(
+ "unable to load media '{}' (seems to be a cleaning unit)",
+ label_text
+ );
}
let mut status = self.status()?;
if let ElementStatus::VolumeTag(ref tag) = drive_status.status {
if *tag == label_text {
if i as u64 != self.drive_number() {
- bail!("unable to load media '{}' - media in wrong drive ({} != {})",
- label_text, i, self.drive_number());
+ bail!(
+ "unable to load media '{}' - media in wrong drive ({} != {})",
+ label_text,
+ i,
+ self.drive_number()
+ );
}
- return Ok(status) // already loaded
+ return Ok(status); // already loaded
}
}
if i as u64 == self.drive_number() {
match drive_status.status {
- ElementStatus::Empty => { /* OK */ },
+ ElementStatus::Empty => { /* OK */ }
_ => unload_drive = true,
- }
+ }
}
}
if let ElementStatus::VolumeTag(ref tag) = slot_info.status {
if tag == label_text {
if slot_info.import_export {
- bail!("unable to load media '{}' - inside import/export slot", label_text);
+ bail!(
+ "unable to load media '{}' - inside import/export slot",
+ label_text
+ );
}
- slot = Some(i+1);
+ slot = Some(i + 1);
break;
}
}
}
for slot_info in status.slots.iter() {
- if slot_info.import_export { continue; }
+ if slot_info.import_export {
+ continue;
+ }
if let ElementStatus::VolumeTag(ref tag) = slot_info.status {
- if tag.starts_with("CLN") { continue; }
+ if tag.starts_with("CLN") {
+ continue;
+ }
list.push(tag.clone());
}
}
// Unload drive first. Note: This also unloads a loaded cleaning tape
if let Some(drive_status) = status.drives.get(self.drive_number() as usize) {
match drive_status.status {
- ElementStatus::Empty => { /* OK */ },
- _ => { status = self.unload_to_free_slot(status)?; }
+ ElementStatus::Empty => { /* OK */ }
+ _ => {
+ status = self.unload_to_free_slot(status)?;
+ }
}
}
let mut cleaning_cartridge_slot = None;
for (i, slot_info) in status.slots.iter().enumerate() {
- if slot_info.import_export { continue; }
+ if slot_info.import_export {
+ continue;
+ }
if let ElementStatus::VolumeTag(ref tag) = slot_info.status {
if tag.starts_with("CLN") {
cleaning_cartridge_slot = Some(i + 1);
Some(cleaning_cartridge_slot) => cleaning_cartridge_slot as u64,
};
-
self.load_media_from_slot(cleaning_cartridge_slot)?;
self.unload_media(Some(cleaning_cartridge_slot))
for (i, slot_info) in status.slots.iter().enumerate() {
if slot_info.import_export {
- if to.is_some() { continue; }
+ if to.is_some() {
+ continue;
+ }
if let ElementStatus::Empty = slot_info.status {
to = Some(i as u64 + 1);
}
self.unload_media(Some(to))?;
Ok(Some(to))
}
- None => bail!("unable to find free export slot"),
+ None => bail!("unable to find free export slot"),
}
} else {
match (from, to) {
///
/// Note: This method consumes status - so please use returned status afterward.
fn unload_to_free_slot(&mut self, status: MtxStatus) -> Result<MtxStatus, 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(slot) = status.find_free_slot(false) {
self.unload_media(Some(slot))
} else {
- bail!("drive '{}' unload failure - no free slot", self.drive_name());
+ bail!(
+ "drive '{}' unload failure - no free slot",
+ self.drive_name()
+ );
}
}
}
const USE_MTX: bool = false;
impl ScsiMediaChange for ScsiTapeChanger {
-
- fn status(&mut self, use_cache: bool) -> Result<MtxStatus, Error> {
+ fn status(&mut self, use_cache: bool) -> Result<MtxStatus, Error> {
if use_cache {
if let Some(state) = load_changer_state_cache(&self.name)? {
return Ok(state);
}
let status = if USE_MTX {
- mtx::mtx_status(&self)
+ mtx::mtx_status(self)
} else {
- sg_pt_changer::status(&self)
+ sg_pt_changer::status(self)
};
match &status {
}
}
-fn save_changer_state_cache(
- changer: &str,
- state: &MtxStatus,
-) -> Result<(), Error> {
-
+fn save_changer_state_cache(changer: &str, state: &MtxStatus) -> Result<(), Error> {
let mut path = PathBuf::from(crate::tape::CHANGER_STATE_DIR);
path.push(changer);
.owner(backup_user.uid)
.group(backup_user.gid);
- replace_file(path, state.as_bytes(), options)
+ replace_file(path, state.as_bytes(), options, false)
}
fn delete_changer_state_cache(changer: &str) {
/// Implements MediaChange using 'mtx' linux cli tool
pub struct MtxMediaChanger {
- drive_name: String, // used for error messages
- drive_number: u64,
+ drive: LtoTapeDrive,
config: ScsiTapeChanger,
}
impl MtxMediaChanger {
-
pub fn with_drive_config(drive_config: &LtoTapeDrive) -> Result<Self, Error> {
let (config, _digest) = pbs_config::drive::config()?;
let changer_config: ScsiTapeChanger = match drive_config.changer {
};
Ok(Self {
- drive_name: drive_config.name.clone(),
- drive_number: drive_config.changer_drivenum.unwrap_or(0),
+ drive: drive_config.clone(),
config: changer_config,
})
}
}
impl MediaChange for MtxMediaChanger {
-
fn drive_number(&self) -> u64 {
- self.drive_number
+ self.drive.changer_drivenum.unwrap_or(0)
}
fn drive_name(&self) -> &str {
- &self.drive_name
+ &self.drive.name
}
fn status(&mut self) -> Result<MtxStatus, Error> {
}
fn load_media_from_slot(&mut self, slot: u64) -> Result<MtxStatus, Error> {
- self.config.load_slot(slot, self.drive_number)
+ self.config.load_slot(slot, self.drive_number())
}
fn unload_media(&mut self, target_slot: Option<u64>) -> Result<MtxStatus, Error> {
+ let options: ChangerOptions = serde_json::from_value(
+ ChangerOptions::API_SCHEMA
+ .parse_property_string(self.config.options.as_deref().unwrap_or_default())?,
+ )?;
+
+ if options.eject_before_unload {
+ let file = open_lto_tape_device(&self.drive.path)?;
+ let mut handle = LtoTapeHandle::new(file)?;
+
+ if handle.medium_present() {
+ handle.eject_media()?;
+ }
+ }
+
if let Some(target_slot) = target_slot {
- self.config.unload(target_slot, self.drive_number)
+ self.config.unload(target_slot, self.drive_number())
} else {
let status = self.status()?;
self.unload_to_free_slot(status)