proxmox-backup/src/tape/drive/mod.rs

388 lines
13 KiB
Rust
Raw Normal View History

2020-12-07 07:27:15 +00:00
mod virtual_tape;
mod linux_mtio;
2021-01-03 09:09:43 +00:00
mod tape_alert_flags;
pub use tape_alert_flags::*;
2021-01-05 11:58:18 +00:00
mod volume_statistics;
pub use volume_statistics::*;
mod encryption;
pub use encryption::*;
2020-12-22 09:42:22 +00:00
pub mod linux_tape;
2020-12-07 07:27:15 +00:00
mod mam;
pub use mam::*;
2020-12-07 07:27:15 +00:00
mod linux_list_drives;
pub use linux_list_drives::*;
use anyhow::{bail, format_err, Error};
use ::serde::{Deserialize};
2020-12-07 07:27:15 +00:00
use proxmox::tools::io::ReadExt;
use proxmox::api::section_config::SectionConfigData;
use crate::{
api2::types::{
VirtualTapeDrive,
LinuxTapeDrive,
},
2020-12-30 11:58:06 +00:00
server::WorkerTask,
2020-12-07 07:27:15 +00:00
tape::{
TapeWrite,
TapeRead,
MediaId,
MtxMediaChanger,
2020-12-07 07:27:15 +00:00
file_formats::{
2020-12-14 16:37:16 +00:00
PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
2020-12-07 07:27:15 +00:00
PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
2020-12-14 16:37:16 +00:00
MediaLabel,
2020-12-07 07:27:15 +00:00
MediaSetLabel,
MediaContentHeader,
},
changer::{
MediaChange,
send_load_media_email,
2020-12-07 07:27:15 +00:00
},
},
};
/// Tape driver interface
pub trait TapeDriver {
/// Flush all data to the tape
fn sync(&mut self) -> Result<(), Error>;
/// Rewind the tape
fn rewind(&mut self) -> Result<(), Error>;
/// Move to end of recorded data
///
/// We assume this flushes the tape write buffer.
fn move_to_eom(&mut self) -> Result<(), Error>;
/// Current file number
fn current_file_number(&mut self) -> Result<u64, Error>;
2020-12-07 07:27:15 +00:00
/// Completely erase the media
fn erase_media(&mut self, fast: bool) -> Result<(), Error>;
/// Read/Open the next file
fn read_next_file<'a>(&'a mut self) -> Result<Option<Box<dyn TapeRead + 'a>>, std::io::Error>;
/// Write/Append a new file
fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error>;
/// Write label to tape (erase tape content)
fn label_tape(&mut self, label: &MediaLabel) -> Result<(), Error> {
2020-12-07 07:27:15 +00:00
self.rewind()?;
self.erase_media(true)?;
let raw = serde_json::to_string_pretty(&serde_json::to_value(&label)?)?;
2020-12-14 16:37:16 +00:00
let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0, raw.len() as u32);
2020-12-07 07:27:15 +00:00
{
let mut writer = self.write_file()?;
writer.write_header(&header, raw.as_bytes())?;
writer.finish(false)?;
}
self.sync()?; // sync data to tape
Ok(())
2020-12-07 07:27:15 +00:00
}
/// Write the media set label to tape
fn write_media_set_label(&mut self, media_set_label: &MediaSetLabel) -> Result<(), Error>;
2020-12-07 07:27:15 +00:00
/// Read the media label
///
/// This tries to read both media labels (label and media_set_label).
fn read_label(&mut self) -> Result<Option<MediaId>, Error> {
2020-12-07 07:27:15 +00:00
self.rewind()?;
let label = {
2020-12-07 07:27:15 +00:00
let mut reader = match self.read_next_file()? {
None => return Ok(None), // tape is empty
Some(reader) => reader,
};
let header: MediaContentHeader = unsafe { reader.read_le_value()? };
2020-12-14 16:37:16 +00:00
header.check(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0, 1, 64*1024)?;
2020-12-07 07:27:15 +00:00
let data = reader.read_exact_allocated(header.size as usize)?;
2020-12-14 16:37:16 +00:00
let label: MediaLabel = serde_json::from_slice(&data)
2020-12-07 07:27:15 +00:00
.map_err(|err| format_err!("unable to parse drive label - {}", err))?;
// make sure we read the EOF marker
if reader.skip_to_end()? != 0 {
bail!("got unexpected data after label");
}
label
2020-12-07 07:27:15 +00:00
};
let mut media_id = MediaId { label, media_set_label: None };
2020-12-07 07:27:15 +00:00
// try to read MediaSet label
2020-12-07 07:27:15 +00:00
let mut reader = match self.read_next_file()? {
None => return Ok(Some(media_id)),
2020-12-07 07:27:15 +00:00
Some(reader) => reader,
};
let header: MediaContentHeader = unsafe { reader.read_le_value()? };
header.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, 1, 64*1024)?;
let data = reader.read_exact_allocated(header.size as usize)?;
let media_set_label: MediaSetLabel = serde_json::from_slice(&data)
.map_err(|err| format_err!("unable to parse media set label - {}", err))?;
// make sure we read the EOF marker
if reader.skip_to_end()? != 0 {
bail!("got unexpected data after media set label");
}
media_id.media_set_label = Some(media_set_label);
2020-12-07 07:27:15 +00:00
Ok(Some(media_id))
2020-12-07 07:27:15 +00:00
}
/// Eject media
fn eject_media(&mut self) -> Result<(), Error>;
/// Read Tape Alert Flags
///
/// This make only sense for real LTO drives. Virtual tape drives should
/// simply return empty flags (default).
fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
Ok(TapeAlertFlags::empty())
}
2020-12-07 07:27:15 +00:00
}
/// Get the media changer (MediaChange + name) associated with a tape drive.
2020-12-07 07:27:15 +00:00
///
/// Returns Ok(None) if the drive has no associated changer device.
2020-12-30 16:28:33 +00:00
///
/// Note: This may return the drive name as changer-name if the drive
/// implements some kind of internal changer (which is true for our
/// 'virtual' drive implementation).
2020-12-07 07:27:15 +00:00
pub fn media_changer(
config: &SectionConfigData,
drive: &str,
) -> Result<Option<(Box<dyn MediaChange>, String)>, Error> {
2020-12-07 07:27:15 +00:00
match config.sections.get(drive) {
Some((section_type_name, config)) => {
match section_type_name.as_ref() {
"virtual" => {
let tape = VirtualTapeDrive::deserialize(config)?;
Ok(Some((Box::new(tape), drive.to_string())))
2020-12-07 07:27:15 +00:00
}
"linux" => {
let drive_config = LinuxTapeDrive::deserialize(config)?;
match drive_config.changer {
2020-12-07 07:27:15 +00:00
Some(ref changer_name) => {
let changer = MtxMediaChanger::with_drive_config(&drive_config)?;
2020-12-07 07:27:15 +00:00
let changer_name = changer_name.to_string();
Ok(Some((Box::new(changer), changer_name)))
2020-12-07 07:27:15 +00:00
}
None => Ok(None),
2020-12-07 07:27:15 +00:00
}
}
_ => bail!("unknown drive type '{}' - internal error"),
2020-12-07 07:27:15 +00:00
}
}
None => {
bail!("no such drive '{}'", drive);
}
}
}
/// Get the media changer (MediaChange + name) associated with a tape drive.
///
/// This fail if the drive has no associated changer device.
pub fn required_media_changer(
config: &SectionConfigData,
drive: &str,
) -> Result<(Box<dyn MediaChange>, String), Error> {
match media_changer(config, drive) {
Ok(Some(result)) => {
return Ok(result);
}
Ok(None) => {
bail!("drive '{}' has no associated changer device", drive);
},
Err(err) => {
return Err(err);
}
}
}
2020-12-07 07:27:15 +00:00
pub fn open_drive(
config: &SectionConfigData,
drive: &str,
) -> Result<Box<dyn TapeDriver>, Error> {
match config.sections.get(drive) {
Some((section_type_name, config)) => {
match section_type_name.as_ref() {
"virtual" => {
let tape = VirtualTapeDrive::deserialize(config)?;
let handle = tape.open()
.map_err(|err| format_err!("open drive '{}' ({}) failed - {}", drive, tape.path, err))?;
Ok(Box::new(handle))
}
"linux" => {
let tape = LinuxTapeDrive::deserialize(config)?;
let handle = tape.open()
.map_err(|err| format_err!("open drive '{}' ({}) failed - {}", drive, tape.path, err))?;
Ok(Box::new(handle))
}
_ => bail!("unknown drive type '{}' - internal error"),
2020-12-07 07:27:15 +00:00
}
}
None => {
bail!("no such drive '{}'", drive);
}
}
}
/// Requests a specific 'media' to be inserted into 'drive'. Within a
/// loop, this then tries to read the media label and waits until it
/// finds the requested media.
///
/// Returns a handle to the opened drive and the media labels.
pub fn request_and_load_media(
2020-12-30 11:58:06 +00:00
worker: &WorkerTask,
2020-12-07 07:27:15 +00:00
config: &SectionConfigData,
drive: &str,
2020-12-14 16:37:16 +00:00
label: &MediaLabel,
2020-12-07 07:27:15 +00:00
) -> Result<(
Box<dyn TapeDriver>,
MediaId,
2020-12-07 07:27:15 +00:00
), Error> {
2020-12-30 11:58:06 +00:00
let check_label = |handle: &mut dyn TapeDriver, uuid: &proxmox::tools::Uuid| {
if let Ok(Some(media_id)) = handle.read_label() {
worker.log(format!(
"found media label {} ({})",
2021-01-13 12:26:59 +00:00
media_id.label.label_text,
media_id.label.uuid.to_string(),
));
2020-12-30 11:58:06 +00:00
if media_id.label.uuid == *uuid {
return Ok(media_id);
}
}
bail!("read label failed (please label all tapes first)");
};
2020-12-07 07:27:15 +00:00
match config.sections.get(drive) {
Some((section_type_name, config)) => {
match section_type_name.as_ref() {
"virtual" => {
2020-12-30 11:58:06 +00:00
let mut tape = VirtualTapeDrive::deserialize(config)?;
2020-12-07 07:27:15 +00:00
2021-01-13 12:26:59 +00:00
let label_text = label.label_text.clone();
2020-12-07 07:27:15 +00:00
2021-01-13 12:26:59 +00:00
tape.load_media(&label_text)?;
2020-12-07 07:27:15 +00:00
2020-12-30 11:58:06 +00:00
let mut handle: Box<dyn TapeDriver> = Box::new(tape.open()?);
2020-12-07 07:27:15 +00:00
2020-12-30 11:58:06 +00:00
let media_id = check_label(handle.as_mut(), &label.uuid)?;
return Ok((handle, media_id));
2020-12-07 07:27:15 +00:00
}
"linux" => {
let drive_config = LinuxTapeDrive::deserialize(config)?;
2020-12-30 11:58:06 +00:00
2021-01-13 12:26:59 +00:00
let label_text = label.label_text.clone();
2020-12-07 07:27:15 +00:00
if drive_config.changer.is_some() {
2020-12-07 07:27:15 +00:00
let mut changer = MtxMediaChanger::with_drive_config(&drive_config)?;
2021-01-13 12:26:59 +00:00
changer.load_media(&label_text)?;
2020-12-30 11:58:06 +00:00
let mut handle: Box<dyn TapeDriver> = Box::new(drive_config.open()?);
2020-12-30 11:58:06 +00:00
let media_id = check_label(handle.as_mut(), &label.uuid)?;
return Ok((handle, media_id));
}
2021-01-13 12:26:59 +00:00
worker.log(format!("Please insert media '{}' into drive '{}'", label_text, drive));
2020-12-30 11:58:06 +00:00
let to = "root@localhost"; // fixme
2021-01-13 12:26:59 +00:00
send_load_media_email(drive, &label_text, to)?;
2020-12-30 11:58:06 +00:00
let mut last_media_uuid = None;
2020-12-30 18:17:18 +00:00
let mut last_error = None;
2020-12-07 07:27:15 +00:00
loop {
let mut handle = match drive_config.open() {
2020-12-07 07:27:15 +00:00
Ok(handle) => handle,
2020-12-30 18:17:18 +00:00
Err(err) => {
let err = err.to_string();
if Some(err.clone()) != last_error {
worker.log(format!("tape open failed - {}", err));
last_error = Some(err);
}
2020-12-07 07:27:15 +00:00
std::thread::sleep(std::time::Duration::from_millis(5_000));
continue;
}
};
2020-12-30 11:58:06 +00:00
match handle.read_label() {
Ok(Some(media_id)) => {
if media_id.label.uuid == label.uuid {
worker.log(format!(
"found media label {} ({})",
2021-01-13 12:26:59 +00:00
media_id.label.label_text,
2020-12-30 11:58:06 +00:00
media_id.label.uuid.to_string(),
));
return Ok((Box::new(handle), media_id));
} else {
if Some(media_id.label.uuid.clone()) != last_media_uuid {
worker.log(format!(
"wrong media label {} ({})",
2021-01-13 12:26:59 +00:00
media_id.label.label_text,
2020-12-30 11:58:06 +00:00
media_id.label.uuid.to_string(),
));
last_media_uuid = Some(media_id.label.uuid);
}
}
}
Ok(None) => {
if last_media_uuid.is_some() {
worker.log(format!("found empty media without label (please label all tapes first)"));
last_media_uuid = None;
}
2020-12-07 07:27:15 +00:00
}
2020-12-30 18:17:18 +00:00
Err(err) => {
let err = err.to_string();
if Some(err.clone()) != last_error {
worker.log(format!("tape open failed - {}", err));
last_error = Some(err);
}
}
2020-12-07 07:27:15 +00:00
}
2020-12-30 11:58:06 +00:00
// eprintln!("read label failed - test again in 5 secs");
2020-12-07 07:27:15 +00:00
std::thread::sleep(std::time::Duration::from_millis(5_000));
}
}
_ => bail!("drive type '{}' not implemented!"),
}
}
None => {
bail!("no such drive '{}'", drive);
}
}
}