pbs tape: rust fmt

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2022-04-06 17:00:29 +02:00
parent a527b54f84
commit b23adfd4ee
17 changed files with 863 additions and 811 deletions

View File

@ -12,46 +12,39 @@
/// - support tape alert flags /// - support tape alert flags
/// - support volume statistics /// - support volume statistics
/// - read cartridge memory /// - read cartridge memory
use std::convert::TryInto; use std::convert::TryInto;
use anyhow::{bail, Error}; use anyhow::{bail, Error};
use serde_json::Value; use serde_json::Value;
use proxmox_schema::{api, ArraySchema, IntegerSchema, Schema, StringSchema};
use proxmox_router::cli::*; use proxmox_router::cli::*;
use proxmox_router::RpcEnvironment; use proxmox_router::RpcEnvironment;
use proxmox_schema::{api, ArraySchema, IntegerSchema, Schema, StringSchema};
use pbs_api_types::{ use pbs_api_types::{LtoTapeDrive, DRIVE_NAME_SCHEMA, LTO_DRIVE_PATH_SCHEMA};
LTO_DRIVE_PATH_SCHEMA, DRIVE_NAME_SCHEMA, LtoTapeDrive,
};
use pbs_config::drive::complete_drive_name; use pbs_config::drive::complete_drive_name;
use pbs_tape::{ use pbs_tape::{
sg_tape::SgTape,
linux_list_drives::{complete_drive_path, lto_tape_device_list, open_lto_tape_device}, linux_list_drives::{complete_drive_path, lto_tape_device_list, open_lto_tape_device},
sg_tape::SgTape,
}; };
pub const FILE_MARK_COUNT_SCHEMA: Schema = pub const FILE_MARK_COUNT_SCHEMA: Schema = IntegerSchema::new("File mark count.")
IntegerSchema::new("File mark count.")
.minimum(1) .minimum(1)
.maximum(i32::MAX as isize) .maximum(i32::MAX as isize)
.schema(); .schema();
pub const FILE_MARK_POSITION_SCHEMA: Schema = pub const FILE_MARK_POSITION_SCHEMA: Schema = IntegerSchema::new("File mark position (0 is BOT).")
IntegerSchema::new("File mark position (0 is BOT).")
.minimum(0) .minimum(0)
.maximum(i32::MAX as isize) .maximum(i32::MAX as isize)
.schema(); .schema();
pub const RECORD_COUNT_SCHEMA: Schema = pub const RECORD_COUNT_SCHEMA: Schema = IntegerSchema::new("Record count.")
IntegerSchema::new("Record count.")
.minimum(1) .minimum(1)
.maximum(i32::MAX as isize) .maximum(i32::MAX as isize)
.schema(); .schema();
pub const DRIVE_OPTION_SCHEMA: Schema = StringSchema::new( pub const DRIVE_OPTION_SCHEMA: Schema =
"Lto Tape Driver Option, either numeric value or option name.") StringSchema::new("Lto Tape Driver Option, either numeric value or option name.").schema();
.schema();
pub const DRIVE_OPTION_LIST_SCHEMA: Schema = pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
ArraySchema::new("Drive Option List.", &DRIVE_OPTION_SCHEMA) ArraySchema::new("Drive Option List.", &DRIVE_OPTION_SCHEMA)
@ -59,7 +52,6 @@ pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
.schema(); .schema();
fn get_tape_handle(param: &Value) -> Result<SgTape, Error> { fn get_tape_handle(param: &Value) -> Result<SgTape, Error> {
if let Some(name) = param["drive"].as_str() { if let Some(name) = param["drive"].as_str() {
let (config, _digest) = pbs_config::drive::config()?; let (config, _digest) = pbs_config::drive::config()?;
let drive: LtoTapeDrive = config.lookup("lto", name)?; let drive: LtoTapeDrive = config.lookup("lto", name)?;
@ -88,7 +80,9 @@ fn get_tape_handle(param: &Value) -> Result<SgTape, Error> {
let mut drive_names = Vec::new(); let mut drive_names = Vec::new();
for (name, (section_type, _)) in config.sections.iter() { for (name, (section_type, _)) in config.sections.iter() {
if section_type != "lto" { continue; } if section_type != "lto" {
continue;
}
drive_names.push(name); drive_names.push(name);
} }
@ -122,7 +116,6 @@ fn get_tape_handle(param: &Value) -> Result<SgTape, Error> {
/// Position the tape at the beginning of the count file (after /// Position the tape at the beginning of the count file (after
/// filemark count) /// filemark count)
fn asf(count: u64, param: Value) -> Result<(), Error> { fn asf(count: u64, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.locate_file(count)?; handle.locate_file(count)?;
@ -130,7 +123,6 @@ fn asf(count: u64, param: Value) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -152,7 +144,6 @@ fn asf(count: u64, param: Value) -> Result<(), Error> {
/// ///
/// The tape is positioned on the last block of the previous file. /// The tape is positioned on the last block of the previous file.
fn bsf(count: usize, param: Value) -> Result<(), Error> { fn bsf(count: usize, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.space_filemarks(-count.try_into()?)?; handle.space_filemarks(-count.try_into()?)?;
@ -160,7 +151,6 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -183,7 +173,6 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> {
/// This leaves the tape positioned at the first block of the file /// This leaves the tape positioned at the first block of the file
/// that is count - 1 files before the current file. /// that is count - 1 files before the current file.
fn bsfm(count: usize, param: Value) -> Result<(), Error> { fn bsfm(count: usize, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.space_filemarks(-count.try_into()?)?; handle.space_filemarks(-count.try_into()?)?;
@ -192,7 +181,6 @@ fn bsfm(count: usize, param: Value) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -212,7 +200,6 @@ fn bsfm(count: usize, param: Value) -> Result<(), Error> {
)] )]
/// Backward space records. /// Backward space records.
fn bsr(count: usize, param: Value) -> Result<(), Error> { fn bsr(count: usize, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.space_blocks(-count.try_into()?)?; handle.space_blocks(-count.try_into()?)?;
@ -220,7 +207,6 @@ fn bsr(count: usize, param: Value) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -241,7 +227,6 @@ fn bsr(count: usize, param: Value) -> Result<(), Error> {
)] )]
/// Read Cartridge Memory /// Read Cartridge Memory
fn cartridge_memory(param: Value) -> Result<(), Error> { fn cartridge_memory(param: Value) -> Result<(), Error> {
let output_format = get_output_format(&param); let output_format = get_output_format(&param);
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
@ -292,11 +277,11 @@ fn cartridge_memory(param: Value) -> Result<(), Error> {
)] )]
/// Read Tape Alert Flags /// Read Tape Alert Flags
fn tape_alert_flags(param: Value) -> Result<(), Error> { fn tape_alert_flags(param: Value) -> Result<(), Error> {
let output_format = get_output_format(&param); let output_format = get_output_format(&param);
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
let result = handle.tape_alert_flags() let result = handle
.tape_alert_flags()
.map(|flags| format!("{:?}", flags)); .map(|flags| format!("{:?}", flags));
if output_format == "json-pretty" { if output_format == "json-pretty" {
@ -337,14 +322,12 @@ fn tape_alert_flags(param: Value) -> Result<(), Error> {
)] )]
/// Eject drive media /// Eject drive media
fn eject(param: Value) -> Result<(), Error> { fn eject(param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.eject()?; handle.eject()?;
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -361,14 +344,12 @@ fn eject(param: Value) -> Result<(), Error> {
)] )]
/// Move to end of media /// Move to end of media
fn eod(param: Value) -> Result<(), Error> { fn eod(param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.move_to_eom(false)?; handle.move_to_eom(false)?;
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -391,7 +372,6 @@ fn eod(param: Value) -> Result<(), Error> {
)] )]
/// Erase media (from current position) /// Erase media (from current position)
fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> { fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.erase_media(fast.unwrap_or(true))?; handle.erase_media(fast.unwrap_or(true))?;
@ -420,7 +400,6 @@ fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
)] )]
/// Format media, single partition /// Format media, single partition
fn format(fast: Option<bool>, param: Value) -> Result<(), Error> { fn format(fast: Option<bool>, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.format_media(fast.unwrap_or(true))?; handle.format_media(fast.unwrap_or(true))?;
@ -448,7 +427,6 @@ fn format(fast: Option<bool>, param: Value) -> Result<(), Error> {
/// ///
/// The tape is positioned on the first block of the next file. /// The tape is positioned on the first block of the next file.
fn fsf(count: usize, param: Value) -> Result<(), Error> { fn fsf(count: usize, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.space_filemarks(count.try_into()?)?; handle.space_filemarks(count.try_into()?)?;
@ -478,7 +456,6 @@ fn fsf(count: usize, param: Value) -> Result<(), Error> {
/// This leaves the tape positioned at the last block of the file that /// This leaves the tape positioned at the last block of the file that
/// is count - 1 files past the current file. /// is count - 1 files past the current file.
fn fsfm(count: usize, param: Value) -> Result<(), Error> { fn fsfm(count: usize, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.space_filemarks(count.try_into()?)?; handle.space_filemarks(count.try_into()?)?;
@ -487,7 +464,6 @@ fn fsfm(count: usize, param: Value) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -507,7 +483,6 @@ fn fsfm(count: usize, param: Value) -> Result<(), Error> {
)] )]
/// Forward space records. /// Forward space records.
fn fsr(count: usize, param: Value) -> Result<(), Error> { fn fsr(count: usize, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.space_blocks(count.try_into()?)?; handle.space_blocks(count.try_into()?)?;
@ -515,7 +490,6 @@ fn fsr(count: usize, param: Value) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -532,14 +506,12 @@ fn fsr(count: usize, param: Value) -> Result<(), Error> {
)] )]
/// Load media /// Load media
fn load(param: Value) -> Result<(), Error> { fn load(param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.load()?; handle.load()?;
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -556,7 +528,6 @@ fn load(param: Value) -> Result<(), Error> {
)] )]
/// Lock the tape drive door /// Lock the tape drive door
fn lock(param: Value) -> Result<(), Error> { fn lock(param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.set_medium_removal(false)?; handle.set_medium_removal(false)?;
@ -564,7 +535,6 @@ fn lock(param: Value) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -581,14 +551,12 @@ fn lock(param: Value) -> Result<(), Error> {
)] )]
/// Rewind the tape /// Rewind the tape
fn rewind(param: Value) -> Result<(), Error> { fn rewind(param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.rewind()?; handle.rewind()?;
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -601,7 +569,6 @@ fn rewind(param: Value) -> Result<(), Error> {
)] )]
/// Scan for existing tape changer devices /// Scan for existing tape changer devices
fn scan(param: Value) -> Result<(), Error> { fn scan(param: Value) -> Result<(), Error> {
let output_format = get_output_format(&param); let output_format = get_output_format(&param);
let list = lto_tape_device_list(); let list = lto_tape_device_list();
@ -621,7 +588,10 @@ fn scan(param: Value) -> Result<(), Error> {
} }
for item in list.iter() { for item in list.iter() {
println!("{} ({}/{}/{})", item.path, item.vendor, item.model, item.serial); println!(
"{} ({}/{}/{})",
item.path, item.vendor, item.model, item.serial
);
} }
Ok(()) Ok(())
@ -647,7 +617,6 @@ fn scan(param: Value) -> Result<(), Error> {
)] )]
/// Drive Status /// Drive Status
fn status(param: Value) -> Result<(), Error> { fn status(param: Value) -> Result<(), Error> {
let output_format = get_output_format(&param); let output_format = get_output_format(&param);
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
@ -677,7 +646,6 @@ fn status(param: Value) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -694,7 +662,6 @@ fn status(param: Value) -> Result<(), Error> {
)] )]
/// Unlock the tape drive door /// Unlock the tape drive door
fn unlock(param: Value) -> Result<(), Error> { fn unlock(param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
handle.set_medium_removal(true)?; handle.set_medium_removal(true)?;
@ -702,7 +669,6 @@ fn unlock(param: Value) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api( #[api(
input: { input: {
properties: { properties: {
@ -723,7 +689,6 @@ fn unlock(param: Value) -> Result<(), Error> {
)] )]
/// Volume Statistics /// Volume Statistics
fn volume_statistics(param: Value) -> Result<(), Error> { fn volume_statistics(param: Value) -> Result<(), Error> {
let output_format = get_output_format(&param); let output_format = get_output_format(&param);
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
@ -772,7 +737,6 @@ fn volume_statistics(param: Value) -> Result<(), Error> {
)] )]
/// Write count (default 1) EOF marks at current position. /// Write count (default 1) EOF marks at current position.
fn weof(count: Option<usize>, param: Value) -> Result<(), Error> { fn weof(count: Option<usize>, param: Value) -> Result<(), Error> {
let count = count.unwrap_or(1); let count = count.unwrap_or(1);
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
@ -825,7 +789,6 @@ fn options(
defaults: Option<bool>, defaults: Option<bool>,
param: Value, param: Value,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut handle = get_tape_handle(&param)?; let mut handle = get_tape_handle(&param)?;
if let Some(true) = defaults { if let Some(true) = defaults {
@ -838,7 +801,6 @@ fn options(
} }
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
let uid = nix::unistd::Uid::current(); let uid = nix::unistd::Uid::current();
let username = match nix::unistd::User::from_uid(uid)? { let username = match nix::unistd::User::from_uid(uid)? {
@ -875,8 +837,7 @@ fn main() -> Result<(), Error> {
.insert("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS)) .insert("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS))
.insert("unlock", std_cmd(&API_METHOD_UNLOCK)) .insert("unlock", std_cmd(&API_METHOD_UNLOCK))
.insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS)) .insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
.insert("weof", std_cmd(&API_METHOD_WEOF).arg_param(&["count"])) .insert("weof", std_cmd(&API_METHOD_WEOF).arg_param(&["count"]));
;
let mut rpcenv = CliEnvironment::new(); let mut rpcenv = CliEnvironment::new();
rpcenv.set_auth_id(Some(format!("{}@pam", username))); rpcenv.set_auth_id(Some(format!("{}@pam", username)));

View File

@ -11,29 +11,25 @@
/// ///
/// - list serial number for attached drives, so that it is possible /// - list serial number for attached drives, so that it is possible
/// to associate drive numbers with drives. /// to associate drive numbers with drives.
use std::fs::File; use std::fs::File;
use anyhow::{bail, Error}; use anyhow::{bail, Error};
use serde_json::Value; use serde_json::Value;
use proxmox_schema::api;
use proxmox_router::cli::*; use proxmox_router::cli::*;
use proxmox_router::RpcEnvironment; use proxmox_router::RpcEnvironment;
use proxmox_schema::api;
use pbs_api_types::{LtoTapeDrive, ScsiTapeChanger, CHANGER_NAME_SCHEMA, SCSI_CHANGER_PATH_SCHEMA};
use pbs_config::drive::complete_changer_name; use pbs_config::drive::complete_changer_name;
use pbs_api_types::{
SCSI_CHANGER_PATH_SCHEMA, CHANGER_NAME_SCHEMA, ScsiTapeChanger, LtoTapeDrive,
};
use pbs_tape::{ use pbs_tape::{
linux_list_drives::{complete_changer_path, linux_tape_changer_list},
sg_pt_changer,
sgutils2::scsi_inquiry, sgutils2::scsi_inquiry,
ElementStatus, ElementStatus,
sg_pt_changer,
linux_list_drives::{complete_changer_path, linux_tape_changer_list},
}; };
fn get_changer_handle(param: &Value) -> Result<File, Error> { fn get_changer_handle(param: &Value) -> Result<File, Error> {
if let Some(name) = param["changer"].as_str() { if let Some(name) = param["changer"].as_str() {
let (config, _digest) = pbs_config::drive::config()?; let (config, _digest) = pbs_config::drive::config()?;
let changer_config: ScsiTapeChanger = config.lookup("changer", name)?; let changer_config: ScsiTapeChanger = config.lookup("changer", name)?;
@ -83,10 +79,7 @@ fn get_changer_handle(param: &Value) -> Result<File, Error> {
}, },
)] )]
/// Inquiry /// Inquiry
fn inquiry( fn inquiry(param: Value) -> Result<(), Error> {
param: Value,
) -> Result<(), Error> {
let output_format = get_output_format(&param); let output_format = get_output_format(&param);
let result: Result<_, Error> = proxmox_lang::try_block!({ let result: Result<_, Error> = proxmox_lang::try_block!({
@ -113,7 +106,10 @@ fn inquiry(
let info = result?; let info = result?;
println!("Type: {} ({})", info.peripheral_type_text, info.peripheral_type); println!(
"Type: {} ({})",
info.peripheral_type_text, info.peripheral_type
);
println!("Vendor: {}", info.vendor); println!("Vendor: {}", info.vendor);
println!("Product: {}", info.product); println!("Product: {}", info.product);
println!("Revision: {}", info.revision); println!("Revision: {}", info.revision);
@ -136,10 +132,7 @@ fn inquiry(
}, },
)] )]
/// Inventory /// Inventory
fn inventory( fn inventory(param: Value) -> Result<(), Error> {
param: Value,
) -> Result<(), Error> {
let mut file = get_changer_handle(&param)?; let mut file = get_changer_handle(&param)?;
sg_pt_changer::initialize_element_status(&mut file)?; sg_pt_changer::initialize_element_status(&mut file)?;
@ -170,12 +163,7 @@ fn inventory(
}, },
)] )]
/// Load /// Load
fn load( fn load(param: Value, slot: u64, drivenum: Option<u64>) -> Result<(), Error> {
param: Value,
slot: u64,
drivenum: Option<u64>,
) -> Result<(), Error> {
let mut file = get_changer_handle(&param)?; let mut file = get_changer_handle(&param)?;
let drivenum = drivenum.unwrap_or(0); let drivenum = drivenum.unwrap_or(0);
@ -210,12 +198,7 @@ fn load(
}, },
)] )]
/// Unload /// Unload
fn unload( fn unload(param: Value, slot: Option<u64>, drivenum: Option<u64>) -> Result<(), Error> {
param: Value,
slot: Option<u64>,
drivenum: Option<u64>,
) -> Result<(), Error> {
let mut file = get_changer_handle(&param)?; let mut file = get_changer_handle(&param)?;
let drivenum = drivenum.unwrap_or(0); let drivenum = drivenum.unwrap_or(0);
@ -271,10 +254,7 @@ fn unload(
}, },
)] )]
/// Changer Status /// Changer Status
fn status( fn status(param: Value) -> Result<(), Error> {
param: Value,
) -> Result<(), Error> {
let output_format = get_output_format(&param); let output_format = get_output_format(&param);
let result: Result<_, Error> = proxmox_lang::try_block!({ let result: Result<_, Error> = proxmox_lang::try_block!({
@ -302,7 +282,10 @@ fn status(
let status = result?; let status = result?;
for (i, transport) in status.transports.iter().enumerate() { for (i, transport) in status.transports.iter().enumerate() {
println!("Transport Element (Griper) {:>3}: {:?}",i, transport.status); println!(
"Transport Element (Griper) {:>3}: {:?}",
i, transport.status
);
} }
for (i, drive) in status.drives.iter().enumerate() { for (i, drive) in status.drives.iter().enumerate() {
@ -323,9 +306,9 @@ fn status(
for (i, slot) in status.slots.iter().enumerate() { for (i, slot) in status.slots.iter().enumerate() {
if slot.import_export { if slot.import_export {
println!(" Import/Export {:>3}: {:?}", i+1, slot.status); println!(" Import/Export {:>3}: {:?}", i + 1, slot.status);
} else { } else {
println!(" Storage Element {:>3}: {:?}", i+1, slot.status); println!(" Storage Element {:>3}: {:?}", i + 1, slot.status);
} }
} }
@ -355,12 +338,7 @@ fn status(
}, },
)] )]
/// Transfer /// Transfer
fn transfer( fn transfer(param: Value, from: u64, to: u64) -> Result<(), Error> {
param: Value,
from: u64,
to: u64,
) -> Result<(), Error> {
let mut file = get_changer_handle(&param)?; let mut file = get_changer_handle(&param)?;
sg_pt_changer::transfer_medium(&mut file, from, to)?; sg_pt_changer::transfer_medium(&mut file, from, to)?;
@ -380,7 +358,6 @@ fn transfer(
)] )]
/// Scan for existing tape changer devices /// Scan for existing tape changer devices
fn scan(param: Value) -> Result<(), Error> { fn scan(param: Value) -> Result<(), Error> {
let output_format = get_output_format(&param); let output_format = get_output_format(&param);
let list = linux_tape_changer_list(); let list = linux_tape_changer_list();
@ -400,14 +377,16 @@ fn scan(param: Value) -> Result<(), Error> {
} }
for item in list.iter() { for item in list.iter() {
println!("{} ({}/{}/{})", item.path, item.vendor, item.model, item.serial); println!(
"{} ({}/{}/{})",
item.path, item.vendor, item.model, item.serial
);
} }
Ok(()) Ok(())
} }
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
let uid = nix::unistd::Uid::current(); let uid = nix::unistd::Uid::current();
let username = match nix::unistd::User::from_uid(uid)? { let username = match nix::unistd::User::from_uid(uid)? {
@ -415,49 +394,47 @@ fn main() -> Result<(), Error> {
None => bail!("unable to get user name"), None => bail!("unable to get user name"),
}; };
let cmd_def = CliCommandMap::new() let cmd_def = CliCommandMap::new()
.usage_skip_options(&["device", "changer", "output-format"]) .usage_skip_options(&["device", "changer", "output-format"])
.insert( .insert(
"inquiry", "inquiry",
CliCommand::new(&API_METHOD_INQUIRY) CliCommand::new(&API_METHOD_INQUIRY)
.completion_cb("changer", complete_changer_name) .completion_cb("changer", complete_changer_name)
.completion_cb("device", complete_changer_path) .completion_cb("device", complete_changer_path),
) )
.insert( .insert(
"inventory", "inventory",
CliCommand::new(&API_METHOD_INVENTORY) CliCommand::new(&API_METHOD_INVENTORY)
.completion_cb("changer", complete_changer_name) .completion_cb("changer", complete_changer_name)
.completion_cb("device", complete_changer_path) .completion_cb("device", complete_changer_path),
) )
.insert( .insert(
"load", "load",
CliCommand::new(&API_METHOD_LOAD) CliCommand::new(&API_METHOD_LOAD)
.arg_param(&["slot"]) .arg_param(&["slot"])
.completion_cb("changer", complete_changer_name) .completion_cb("changer", complete_changer_name)
.completion_cb("device", complete_changer_path) .completion_cb("device", complete_changer_path),
) )
.insert( .insert(
"unload", "unload",
CliCommand::new(&API_METHOD_UNLOAD) CliCommand::new(&API_METHOD_UNLOAD)
.completion_cb("changer", complete_changer_name) .completion_cb("changer", complete_changer_name)
.completion_cb("device", complete_changer_path) .completion_cb("device", complete_changer_path),
) )
.insert("scan", CliCommand::new(&API_METHOD_SCAN)) .insert("scan", CliCommand::new(&API_METHOD_SCAN))
.insert( .insert(
"status", "status",
CliCommand::new(&API_METHOD_STATUS) CliCommand::new(&API_METHOD_STATUS)
.completion_cb("changer", complete_changer_name) .completion_cb("changer", complete_changer_name)
.completion_cb("device", complete_changer_path) .completion_cb("device", complete_changer_path),
) )
.insert( .insert(
"transfer", "transfer",
CliCommand::new(&API_METHOD_TRANSFER) CliCommand::new(&API_METHOD_TRANSFER)
.arg_param(&["from", "to"]) .arg_param(&["from", "to"])
.completion_cb("changer", complete_changer_name) .completion_cb("changer", complete_changer_name)
.completion_cb("device", complete_changer_path) .completion_cb("device", complete_changer_path),
) );
;
let mut rpcenv = CliEnvironment::new(); let mut rpcenv = CliEnvironment::new();
rpcenv.set_auth_id(Some(format!("{}@pam", username))); rpcenv.set_auth_id(Some(format!("{}@pam", username)));

View File

@ -1,12 +1,8 @@
use std::io::Read; use std::io::Read;
use crate::{ use crate::{
TapeRead, BlockHeader, BlockHeaderFlags, BlockRead, BlockReadError, TapeRead,
BlockRead,
BlockReadError,
PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0, PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0,
BlockHeader,
BlockHeaderFlags,
}; };
/// Read a block stream generated by 'BlockWriter'. /// Read a block stream generated by 'BlockWriter'.
@ -31,14 +27,12 @@ pub struct BlockedReader<R> {
read_pos: usize, read_pos: usize,
} }
impl <R: BlockRead> BlockedReader<R> { impl<R: BlockRead> BlockedReader<R> {
/// Create a new BlockedReader instance. /// Create a new BlockedReader instance.
/// ///
/// This tries to read the first block. Please inspect the error /// This tries to read the first block. Please inspect the error
/// to detect EOF and EOT. /// to detect EOF and EOT.
pub fn open(mut reader: R) -> Result<Self, BlockReadError> { pub fn open(mut reader: R) -> Result<Self, BlockReadError> {
let mut buffer = BlockHeader::new(); let mut buffer = BlockHeader::new();
Self::read_block_frame(&mut buffer, &mut reader)?; Self::read_block_frame(&mut buffer, &mut reader)?;
@ -67,32 +61,37 @@ impl <R: BlockRead> BlockedReader<R> {
} }
fn check_buffer(buffer: &BlockHeader, seq_nr: u32) -> Result<(usize, bool), std::io::Error> { fn check_buffer(buffer: &BlockHeader, seq_nr: u32) -> Result<(usize, bool), std::io::Error> {
if buffer.magic != PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0 { if buffer.magic != PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0 {
proxmox_lang::io_bail!("detected tape block with wrong magic number - not written by proxmox tape"); proxmox_lang::io_bail!(
"detected tape block with wrong magic number - not written by proxmox tape"
);
} }
if seq_nr != buffer.seq_nr() { if seq_nr != buffer.seq_nr() {
proxmox_lang::io_bail!( proxmox_lang::io_bail!(
"detected tape block with wrong sequence number ({} != {})", "detected tape block with wrong sequence number ({} != {})",
seq_nr, buffer.seq_nr()) seq_nr,
buffer.seq_nr()
)
} }
let size = buffer.size(); let size = buffer.size();
let found_end_marker = buffer.flags.contains(BlockHeaderFlags::END_OF_STREAM); let found_end_marker = buffer.flags.contains(BlockHeaderFlags::END_OF_STREAM);
if size > buffer.payload.len() { if size > buffer.payload.len() {
proxmox_lang::io_bail!("detected tape block with wrong payload size ({} > {}", size, buffer.payload.len()); proxmox_lang::io_bail!(
"detected tape block with wrong payload size ({} > {}",
size,
buffer.payload.len()
);
} else if size == 0 && !found_end_marker { } else if size == 0 && !found_end_marker {
proxmox_lang::io_bail!("detected tape block with zero payload size"); proxmox_lang::io_bail!("detected tape block with zero payload size");
} }
Ok((size, found_end_marker)) Ok((size, found_end_marker))
} }
fn read_block_frame(buffer: &mut BlockHeader, reader: &mut R) -> Result<(), BlockReadError> { fn read_block_frame(buffer: &mut BlockHeader, reader: &mut R) -> Result<(), BlockReadError> {
let data = unsafe { let data = unsafe {
std::slice::from_raw_parts_mut( std::slice::from_raw_parts_mut(
(buffer as *mut BlockHeader) as *mut u8, (buffer as *mut BlockHeader) as *mut u8,
@ -115,20 +114,15 @@ impl <R: BlockRead> BlockedReader<R> {
Ok(_) => { Ok(_) => {
proxmox_lang::io_bail!("detected tape block after block-stream end marker"); proxmox_lang::io_bail!("detected tape block after block-stream end marker");
} }
Err(BlockReadError::EndOfFile) => { Err(BlockReadError::EndOfFile) => Ok(()),
Ok(())
}
Err(BlockReadError::EndOfStream) => { Err(BlockReadError::EndOfStream) => {
proxmox_lang::io_bail!("got unexpected end of tape"); proxmox_lang::io_bail!("got unexpected end of tape");
} }
Err(BlockReadError::Error(err)) => { Err(BlockReadError::Error(err)) => Err(err),
Err(err)
}
} }
} }
fn read_block(&mut self, check_end_marker: bool) -> Result<usize, std::io::Error> { fn read_block(&mut self, check_end_marker: bool) -> Result<usize, std::io::Error> {
match Self::read_block_frame(&mut self.buffer, &mut self.reader) { match Self::read_block_frame(&mut self.buffer, &mut self.reader) {
Ok(()) => { /* ok */ } Ok(()) => { /* ok */ }
Err(BlockReadError::EndOfFile) => { Err(BlockReadError::EndOfFile) => {
@ -150,7 +144,8 @@ impl <R: BlockRead> BlockedReader<R> {
let (size, found_end_marker) = Self::check_buffer(&self.buffer, self.seq_nr)?; let (size, found_end_marker) = Self::check_buffer(&self.buffer, self.seq_nr)?;
self.seq_nr += 1; self.seq_nr += 1;
if found_end_marker { // consume EOF mark if found_end_marker {
// consume EOF mark
self.found_end_marker = true; self.found_end_marker = true;
self.incomplete = self.buffer.flags.contains(BlockHeaderFlags::INCOMPLETE); self.incomplete = self.buffer.flags.contains(BlockHeaderFlags::INCOMPLETE);
Self::consume_eof_marker(&mut self.reader)?; Self::consume_eof_marker(&mut self.reader)?;
@ -163,8 +158,7 @@ impl <R: BlockRead> BlockedReader<R> {
} }
} }
impl <R: BlockRead> TapeRead for BlockedReader<R> { impl<R: BlockRead> TapeRead for BlockedReader<R> {
fn is_incomplete(&self) -> Result<bool, std::io::Error> { fn is_incomplete(&self) -> Result<bool, std::io::Error> {
if !self.got_eod { if !self.got_eod {
proxmox_lang::io_bail!("is_incomplete failed: EOD not reached"); proxmox_lang::io_bail!("is_incomplete failed: EOD not reached");
@ -202,10 +196,8 @@ impl <R: BlockRead> TapeRead for BlockedReader<R> {
} }
} }
impl <R: BlockRead> Read for BlockedReader<R> { impl<R: BlockRead> Read for BlockedReader<R> {
fn read(&mut self, buffer: &mut [u8]) -> Result<usize, std::io::Error> { fn read(&mut self, buffer: &mut [u8]) -> Result<usize, std::io::Error> {
if self.read_error { if self.read_error {
proxmox_lang::io_bail!("detected read after error - internal error"); proxmox_lang::io_bail!("detected read after error - internal error");
} }
@ -213,7 +205,8 @@ impl <R: BlockRead> Read for BlockedReader<R> {
let mut buffer_size = self.buffer.size(); let mut buffer_size = self.buffer.size();
let mut rest = (buffer_size as isize) - (self.read_pos as isize); let mut rest = (buffer_size as isize) - (self.read_pos as isize);
if rest <= 0 && !self.got_eod { // try to refill buffer if rest <= 0 && !self.got_eod {
// try to refill buffer
buffer_size = match self.read_block(true) { buffer_size = match self.read_block(true) {
Ok(len) => len, Ok(len) => len,
err => { err => {
@ -232,8 +225,8 @@ impl <R: BlockRead> Read for BlockedReader<R> {
} else { } else {
rest as usize rest as usize
}; };
buffer[..copy_len].copy_from_slice( buffer[..copy_len]
&self.buffer.payload[self.read_pos..(self.read_pos + copy_len)]); .copy_from_slice(&self.buffer.payload[self.read_pos..(self.read_pos + copy_len)]);
self.read_pos += copy_len; self.read_pos += copy_len;
Ok(copy_len) Ok(copy_len)
} }
@ -242,24 +235,18 @@ impl <R: BlockRead> Read for BlockedReader<R> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::io::Read;
use anyhow::{bail, Error};
use crate::{ use crate::{
TapeWrite, BlockReadError, BlockedReader, BlockedWriter, EmulateTapeReader, EmulateTapeWriter,
BlockReadError, TapeWrite, PROXMOX_TAPE_BLOCK_SIZE,
EmulateTapeReader,
EmulateTapeWriter,
PROXMOX_TAPE_BLOCK_SIZE,
BlockedReader,
BlockedWriter,
}; };
use anyhow::{bail, Error};
use std::io::Read;
fn write_and_verify(data: &[u8]) -> Result<(), Error> { fn write_and_verify(data: &[u8]) -> Result<(), Error> {
let mut tape_data = Vec::new(); let mut tape_data = Vec::new();
{ {
let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024*10); let writer = EmulateTapeWriter::new(&mut tape_data, 1024 * 1024 * 10);
let mut writer = BlockedWriter::new(writer); let mut writer = BlockedWriter::new(writer);
writer.write_all(data)?; writer.write_all(data)?;
@ -269,8 +256,8 @@ mod test {
assert_eq!( assert_eq!(
tape_data.len(), tape_data.len(),
((data.len() + PROXMOX_TAPE_BLOCK_SIZE)/PROXMOX_TAPE_BLOCK_SIZE) ((data.len() + PROXMOX_TAPE_BLOCK_SIZE) / PROXMOX_TAPE_BLOCK_SIZE)
*PROXMOX_TAPE_BLOCK_SIZE * PROXMOX_TAPE_BLOCK_SIZE
); );
let reader = &mut &tape_data[..]; let reader = &mut &tape_data[..];
@ -299,7 +286,7 @@ mod test {
#[test] #[test]
fn large_data() -> Result<(), Error> { fn large_data() -> Result<(), Error> {
let data = proxmox_sys::linux::random_data(1024*1024*5)?; let data = proxmox_sys::linux::random_data(1024 * 1024 * 5)?;
write_and_verify(&data) write_and_verify(&data)
} }
@ -309,7 +296,7 @@ mod test {
let reader = &mut &tape_data[..]; let reader = &mut &tape_data[..];
let reader = EmulateTapeReader::new(reader); let reader = EmulateTapeReader::new(reader);
match BlockedReader::open(reader) { match BlockedReader::open(reader) {
Err(BlockReadError::EndOfFile) => { /* OK */ }, Err(BlockReadError::EndOfFile) => { /* OK */ }
_ => bail!("expected EOF"), _ => bail!("expected EOF"),
} }
@ -320,7 +307,7 @@ mod test {
fn no_end_marker() -> Result<(), Error> { fn no_end_marker() -> Result<(), Error> {
let mut tape_data = Vec::new(); let mut tape_data = Vec::new();
{ {
let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024); let writer = EmulateTapeWriter::new(&mut tape_data, 1024 * 1024);
let mut writer = BlockedWriter::new(writer); let mut writer = BlockedWriter::new(writer);
// write at least one block // write at least one block
let data = proxmox_sys::linux::random_data(PROXMOX_TAPE_BLOCK_SIZE)?; let data = proxmox_sys::linux::random_data(PROXMOX_TAPE_BLOCK_SIZE)?;
@ -343,7 +330,7 @@ mod test {
let mut tape_data = Vec::new(); let mut tape_data = Vec::new();
{ {
let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024); let writer = EmulateTapeWriter::new(&mut tape_data, 1024 * 1024);
let mut writer = BlockedWriter::new(writer); let mut writer = BlockedWriter::new(writer);
writer.write_all(b"ABC")?; writer.write_all(b"ABC")?;

View File

@ -1,11 +1,6 @@
use proxmox_io::vec; use proxmox_io::vec;
use crate::{ use crate::{BlockHeader, BlockHeaderFlags, BlockWrite, TapeWrite};
TapeWrite,
BlockWrite,
BlockHeader,
BlockHeaderFlags,
};
/// Assemble and write blocks of data /// Assemble and write blocks of data
/// ///
@ -22,8 +17,7 @@ pub struct BlockedWriter<W: BlockWrite> {
wrote_eof: bool, wrote_eof: bool,
} }
impl <W: BlockWrite> Drop for BlockedWriter<W> { impl<W: BlockWrite> Drop for BlockedWriter<W> {
// Try to make sure to end the file with a filemark // Try to make sure to end the file with a filemark
fn drop(&mut self) { fn drop(&mut self) {
if !self.wrote_eof { if !self.wrote_eof {
@ -32,8 +26,7 @@ impl <W: BlockWrite> Drop for BlockedWriter<W> {
} }
} }
impl <W: BlockWrite> BlockedWriter<W> { impl<W: BlockWrite> BlockedWriter<W> {
/// Allow access to underlying writer /// Allow access to underlying writer
pub fn writer_ref_mut(&mut self) -> &mut W { pub fn writer_ref_mut(&mut self) -> &mut W {
&mut self.writer &mut self.writer
@ -53,7 +46,6 @@ impl <W: BlockWrite> BlockedWriter<W> {
} }
fn write_block(buffer: &BlockHeader, writer: &mut W) -> Result<bool, std::io::Error> { fn write_block(buffer: &BlockHeader, writer: &mut W) -> Result<bool, std::io::Error> {
let data = unsafe { let data = unsafe {
std::slice::from_raw_parts( std::slice::from_raw_parts(
(buffer as *const BlockHeader) as *const u8, (buffer as *const BlockHeader) as *const u8,
@ -73,12 +65,13 @@ impl <W: BlockWrite> BlockedWriter<W> {
} }
fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> { fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
if data.is_empty() {
if data.is_empty() { return Ok(0); } return Ok(0);
}
let rest = self.buffer.payload.len() - self.buffer_pos; let rest = self.buffer.payload.len() - self.buffer_pos;
let bytes = if data.len() < rest { data.len() } else { rest }; let bytes = if data.len() < rest { data.len() } else { rest };
self.buffer.payload[self.buffer_pos..(self.buffer_pos+bytes)] self.buffer.payload[self.buffer_pos..(self.buffer_pos + bytes)]
.copy_from_slice(&data[..bytes]); .copy_from_slice(&data[..bytes]);
let rest = rest - bytes; let rest = rest - bytes;
@ -89,21 +82,20 @@ impl <W: BlockWrite> BlockedWriter<W> {
self.buffer.set_seq_nr(self.seq_nr); self.buffer.set_seq_nr(self.seq_nr);
self.seq_nr += 1; self.seq_nr += 1;
let leom = Self::write_block(&self.buffer, &mut self.writer)?; let leom = Self::write_block(&self.buffer, &mut self.writer)?;
if leom { self.logical_end_of_media = true; } if leom {
self.logical_end_of_media = true;
}
self.buffer_pos = 0; self.buffer_pos = 0;
self.bytes_written += BlockHeader::SIZE; self.bytes_written += BlockHeader::SIZE;
} else { } else {
self.buffer_pos += bytes; self.buffer_pos += bytes;
} }
Ok(bytes) Ok(bytes)
} }
} }
impl <W: BlockWrite> TapeWrite for BlockedWriter<W> { impl<W: BlockWrite> TapeWrite for BlockedWriter<W> {
fn write_all(&mut self, mut data: &[u8]) -> Result<bool, std::io::Error> { fn write_all(&mut self, mut data: &[u8]) -> Result<bool, std::io::Error> {
while !data.is_empty() { while !data.is_empty() {
match self.write(data) { match self.write(data) {
@ -125,7 +117,9 @@ impl <W: BlockWrite> TapeWrite for BlockedWriter<W> {
fn finish(&mut self, incomplete: bool) -> Result<bool, std::io::Error> { fn finish(&mut self, incomplete: bool) -> Result<bool, std::io::Error> {
vec::clear(&mut self.buffer.payload[self.buffer_pos..]); vec::clear(&mut self.buffer.payload[self.buffer_pos..]);
self.buffer.flags = BlockHeaderFlags::END_OF_STREAM; self.buffer.flags = BlockHeaderFlags::END_OF_STREAM;
if incomplete { self.buffer.flags |= BlockHeaderFlags::INCOMPLETE; } if incomplete {
self.buffer.flags |= BlockHeaderFlags::INCOMPLETE;
}
self.buffer.set_size(self.buffer_pos); self.buffer.set_size(self.buffer_pos);
self.buffer.set_seq_nr(self.seq_nr); self.buffer.set_seq_nr(self.seq_nr);
self.seq_nr += 1; self.seq_nr += 1;
@ -139,5 +133,4 @@ impl <W: BlockWrite> TapeWrite for BlockedWriter<W> {
fn logical_end_of_media(&self) -> bool { fn logical_end_of_media(&self) -> bool {
self.logical_end_of_media self.logical_end_of_media
} }
} }

View File

@ -12,17 +12,21 @@ pub struct EmulateTapeReader<R: Read> {
got_eof: bool, got_eof: bool,
} }
impl <R: Read> EmulateTapeReader<R> { impl<R: Read> EmulateTapeReader<R> {
pub fn new(reader: R) -> Self { pub fn new(reader: R) -> Self {
Self { reader, got_eof: false } Self {
reader,
got_eof: false,
}
} }
} }
impl <R: Read> BlockRead for EmulateTapeReader<R> { impl<R: Read> BlockRead for EmulateTapeReader<R> {
fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> { fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
if self.got_eof { if self.got_eof {
return Err(BlockReadError::Error(proxmox_lang::io_format_err!("detected read after EOF!"))); return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
"detected read after EOF!"
)));
} }
match self.reader.read_exact_or_eof(buffer)? { match self.reader.read_exact_or_eof(buffer)? {
false => { false => {
@ -32,13 +36,11 @@ impl <R: Read> BlockRead for EmulateTapeReader<R> {
true => { true => {
// test buffer len after EOF test (to allow EOF test with small buffers in BufferedReader) // test buffer len after EOF test (to allow EOF test with small buffers in BufferedReader)
if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE { if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE {
return Err(BlockReadError::Error( return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
proxmox_lang::io_format_err!(
"EmulateTapeReader: read_block with wrong block size ({} != {})", "EmulateTapeReader: read_block with wrong block size ({} != {})",
buffer.len(), buffer.len(),
PROXMOX_TAPE_BLOCK_SIZE, PROXMOX_TAPE_BLOCK_SIZE,
) )));
));
} }
Ok(buffer.len()) Ok(buffer.len())
} }

View File

@ -14,12 +14,10 @@ pub struct EmulateTapeWriter<W> {
wrote_eof: bool, wrote_eof: bool,
} }
impl <W: Write> EmulateTapeWriter<W> { impl<W: Write> EmulateTapeWriter<W> {
/// Create a new instance allowing to write about max_size bytes /// Create a new instance allowing to write about max_size bytes
pub fn new(writer: W, max_size: usize) -> Self { pub fn new(writer: W, max_size: usize) -> Self {
let mut max_blocks = max_size / PROXMOX_TAPE_BLOCK_SIZE;
let mut max_blocks = max_size/PROXMOX_TAPE_BLOCK_SIZE;
if max_blocks < 2 { if max_blocks < 2 {
max_blocks = 2; // at least 2 blocks max_blocks = 2; // at least 2 blocks
@ -34,17 +32,20 @@ impl <W: Write> EmulateTapeWriter<W> {
} }
} }
impl <W: Write> BlockWrite for EmulateTapeWriter<W> { impl<W: Write> BlockWrite for EmulateTapeWriter<W> {
fn write_block(&mut self, buffer: &[u8]) -> Result<bool, io::Error> { fn write_block(&mut self, buffer: &[u8]) -> Result<bool, io::Error> {
if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE { if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE {
proxmox_lang::io_bail!("EmulateTapeWriter: got write with wrong block size ({} != {}", proxmox_lang::io_bail!(
buffer.len(), PROXMOX_TAPE_BLOCK_SIZE); "EmulateTapeWriter: got write with wrong block size ({} != {}",
buffer.len(),
PROXMOX_TAPE_BLOCK_SIZE
);
} }
if self.block_nr >= self.max_blocks + 2 { if self.block_nr >= self.max_blocks + 2 {
return Err(io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32)); return Err(io::Error::from_raw_os_error(
nix::errno::Errno::ENOSPC as i32,
));
} }
self.writer.write_all(buffer)?; self.writer.write_all(buffer)?;

View File

@ -3,7 +3,7 @@ use std::collections::HashSet;
use anyhow::{bail, Error}; use anyhow::{bail, Error};
use bitflags::bitflags; use bitflags::bitflags;
use endian_trait::Endian; use endian_trait::Endian;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use proxmox_uuid::Uuid; use proxmox_uuid::Uuid;
@ -37,7 +37,7 @@ pub mod sg_tape;
pub mod sg_pt_changer; pub mod sg_pt_changer;
/// We use 256KB blocksize (always) /// We use 256KB blocksize (always)
pub const PROXMOX_TAPE_BLOCK_SIZE: usize = 256*1024; pub const PROXMOX_TAPE_BLOCK_SIZE: usize = 256 * 1024;
// openssl::sha::sha256(b"Proxmox Tape Block Header v1.0")[0..8] // openssl::sha::sha256(b"Proxmox Tape Block Header v1.0")[0..8]
pub const PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0: [u8; 8] = [220, 189, 175, 202, 235, 160, 165, 40]; pub const PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0: [u8; 8] = [220, 189, 175, 202, 235, 160, 165, 40];
@ -61,7 +61,7 @@ pub const PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0: [u8; 8] = [8, 96, 99, 249, 4
/// header has an additional size field. For streams of blocks, there /// header has an additional size field. For streams of blocks, there
/// is a sequence number (`seq_nr`) which may be use for additional /// is a sequence number (`seq_nr`) which may be use for additional
/// error checking. /// error checking.
#[repr(C,packed)] #[repr(C, packed)]
pub struct BlockHeader { pub struct BlockHeader {
/// fixed value `PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0` /// fixed value `PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0`
pub magic: [u8; 8], pub magic: [u8; 8],
@ -84,7 +84,7 @@ bitflags! {
} }
#[derive(Endian, Copy, Clone, Debug)] #[derive(Endian, Copy, Clone, Debug)]
#[repr(C,packed)] #[repr(C, packed)]
/// Media Content Header /// Media Content Header
/// ///
/// All tape files start with this header. The header may contain some /// All tape files start with this header. The header may contain some
@ -115,11 +115,9 @@ pub struct MediaContentHeader {
} }
impl MediaContentHeader { impl MediaContentHeader {
/// Create a new instance with autogenerated Uuid /// Create a new instance with autogenerated Uuid
pub fn new(content_magic: [u8; 8], size: u32) -> Self { pub fn new(content_magic: [u8; 8], size: u32) -> Self {
let uuid = *Uuid::generate() let uuid = *Uuid::generate().into_inner();
.into_inner();
Self { Self {
magic: PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0, magic: PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
content_magic, content_magic,
@ -153,9 +151,7 @@ impl MediaContentHeader {
} }
} }
impl BlockHeader { impl BlockHeader {
pub const SIZE: usize = PROXMOX_TAPE_BLOCK_SIZE; pub const SIZE: usize = PROXMOX_TAPE_BLOCK_SIZE;
/// Allocates a new instance on the heap /// Allocates a new instance on the heap
@ -166,13 +162,9 @@ impl BlockHeader {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
let mut buffer = unsafe { let mut buffer = unsafe {
let ptr = alloc_zeroed( let ptr = alloc_zeroed(Layout::from_size_align(Self::SIZE, page_size).unwrap());
Layout::from_size_align(Self::SIZE, page_size)
.unwrap(),
);
Box::from_raw( Box::from_raw(
std::slice::from_raw_parts_mut(ptr, Self::SIZE - 16) std::slice::from_raw_parts_mut(ptr, Self::SIZE - 16) as *mut [u8] as *mut Self,
as *mut [u8] as *mut Self
) )
}; };
buffer.magic = PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0; buffer.magic = PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0;
@ -187,7 +179,7 @@ impl BlockHeader {
/// Returns the `size` field /// Returns the `size` field
pub fn size(&self) -> usize { pub fn size(&self) -> usize {
(self.size[0] as usize) + ((self.size[1] as usize)<<8) + ((self.size[2] as usize)<<16) (self.size[0] as usize) + ((self.size[1] as usize) << 8) + ((self.size[2] as usize) << 16)
} }
/// Set the `seq_nr` field /// Set the `seq_nr` field
@ -263,16 +255,19 @@ pub struct MtxStatus {
} }
impl MtxStatus { impl MtxStatus {
pub fn slot_address(&self, slot: u64) -> Result<u16, Error> { pub fn slot_address(&self, slot: u64) -> Result<u16, Error> {
if slot == 0 { if slot == 0 {
bail!("invalid slot number '{}' (slots numbers starts at 1)", slot); bail!("invalid slot number '{}' (slots numbers starts at 1)", slot);
} }
if slot > (self.slots.len() as u64) { if slot > (self.slots.len() as u64) {
bail!("invalid slot number '{}' (max {} slots)", slot, self.slots.len()); bail!(
"invalid slot number '{}' (max {} slots)",
slot,
self.slots.len()
);
} }
Ok(self.slots[(slot -1) as usize].element_address) Ok(self.slots[(slot - 1) as usize].element_address)
} }
pub fn drive_address(&self, drivenum: u64) -> Result<u16, Error> { pub fn drive_address(&self, drivenum: u64) -> Result<u16, Error> {
@ -287,8 +282,7 @@ impl MtxStatus {
// simply use first transport // simply use first transport
// (are there changers exposing more than one?) // (are there changers exposing more than one?)
// defaults to 0 for changer that do not report transports // defaults to 0 for changer that do not report transports
self self.transports
.transports
.get(0) .get(0)
.map(|t| t.element_address) .map(|t| t.element_address)
.unwrap_or(0u16) .unwrap_or(0u16)
@ -301,14 +295,14 @@ impl MtxStatus {
continue; // skip slots of wrong type continue; // skip slots of wrong type
} }
if let ElementStatus::Empty = slot_info.status { if let ElementStatus::Empty = slot_info.status {
free_slot = Some((i+1) as u64); free_slot = Some((i + 1) as u64);
break; break;
} }
} }
free_slot free_slot
} }
pub fn mark_import_export_slots(&mut self, config: &ScsiTapeChanger) -> Result<(), Error>{ pub fn mark_import_export_slots(&mut self, config: &ScsiTapeChanger) -> Result<(), Error> {
let mut export_slots: HashSet<u64> = HashSet::new(); let mut export_slots: HashSet<u64> = HashSet::new();
if let Some(slots) = &config.export_slots { if let Some(slots) = &config.export_slots {

View File

@ -1,8 +1,8 @@
use std::path::{Path, PathBuf};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::{OpenOptions, File}; use std::fs::{File, OpenOptions};
use std::os::unix::fs::OpenOptionsExt; use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use nix::fcntl::{fcntl, FcntlArg, OFlag}; use nix::fcntl::{fcntl, FcntlArg, OFlag};
@ -12,21 +12,20 @@ use proxmox_sys::fs::scan_subdir;
use pbs_api_types::{DeviceKind, OptionalDeviceIdentification, TapeDeviceInfo}; use pbs_api_types::{DeviceKind, OptionalDeviceIdentification, TapeDeviceInfo};
lazy_static::lazy_static!{ lazy_static::lazy_static! {
static ref SCSI_GENERIC_NAME_REGEX: regex::Regex = static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
regex::Regex::new(r"^sg\d+$").unwrap(); regex::Regex::new(r"^sg\d+$").unwrap();
} }
/// List linux tape changer devices /// List linux tape changer devices
pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> { pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
let mut list = Vec::new(); let mut list = Vec::new();
let dir_iter = match scan_subdir( let dir_iter = match scan_subdir(
libc::AT_FDCWD, libc::AT_FDCWD,
"/sys/class/scsi_generic", "/sys/class/scsi_generic",
&SCSI_GENERIC_NAME_REGEX) &SCSI_GENERIC_NAME_REGEX,
{ ) {
Err(_) => return list, Err(_) => return list,
Ok(iter) => iter, Ok(iter) => iter,
}; };
@ -63,7 +62,9 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
continue; continue;
} }
} }
_ => { continue; } _ => {
continue;
}
} }
// let mut test_path = sys_path.clone(); // let mut test_path = sys_path.clone();
@ -75,22 +76,42 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
Some(dev_path) => dev_path, Some(dev_path) => dev_path,
}; };
let serial = match device.property_value("ID_SCSI_SERIAL") let serial = match device
.property_value("ID_SCSI_SERIAL")
.map(std::ffi::OsString::from) .map(std::ffi::OsString::from)
.and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None }) .and_then(|s| {
{ if let Ok(s) = s.into_string() {
Some(s)
} else {
None
}
}) {
None => continue, None => continue,
Some(serial) => serial, Some(serial) => serial,
}; };
let vendor = device.property_value("ID_VENDOR") let vendor = device
.property_value("ID_VENDOR")
.map(std::ffi::OsString::from) .map(std::ffi::OsString::from)
.and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None }) .and_then(|s| {
if let Ok(s) = s.into_string() {
Some(s)
} else {
None
}
})
.unwrap_or_else(|| String::from("unknown")); .unwrap_or_else(|| String::from("unknown"));
let model = device.property_value("ID_MODEL") let model = device
.property_value("ID_MODEL")
.map(std::ffi::OsString::from) .map(std::ffi::OsString::from)
.and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None }) .and_then(|s| {
if let Ok(s) = s.into_string() {
Some(s)
} else {
None
}
})
.unwrap_or_else(|| String::from("unknown")); .unwrap_or_else(|| String::from("unknown"));
let dev_path = format!("/dev/tape/by-id/scsi-{}", serial); let dev_path = format!("/dev/tape/by-id/scsi-{}", serial);
@ -113,14 +134,13 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
/// List LTO drives /// List LTO drives
pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> { pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
let mut list = Vec::new(); let mut list = Vec::new();
let dir_iter = match scan_subdir( let dir_iter = match scan_subdir(
libc::AT_FDCWD, libc::AT_FDCWD,
"/sys/class/scsi_generic", "/sys/class/scsi_generic",
&SCSI_GENERIC_NAME_REGEX) &SCSI_GENERIC_NAME_REGEX,
{ ) {
Err(_) => return list, Err(_) => return list,
Ok(iter) => iter, Ok(iter) => iter,
}; };
@ -157,7 +177,9 @@ pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
continue; continue;
} }
} }
_ => { continue; } _ => {
continue;
}
} }
// let mut test_path = sys_path.clone(); // let mut test_path = sys_path.clone();
@ -169,22 +191,42 @@ pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
Some(dev_path) => dev_path, Some(dev_path) => dev_path,
}; };
let serial = match device.property_value("ID_SCSI_SERIAL") let serial = match device
.property_value("ID_SCSI_SERIAL")
.map(std::ffi::OsString::from) .map(std::ffi::OsString::from)
.and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None }) .and_then(|s| {
{ if let Ok(s) = s.into_string() {
Some(s)
} else {
None
}
}) {
None => continue, None => continue,
Some(serial) => serial, Some(serial) => serial,
}; };
let vendor = device.property_value("ID_VENDOR") let vendor = device
.property_value("ID_VENDOR")
.map(std::ffi::OsString::from) .map(std::ffi::OsString::from)
.and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None }) .and_then(|s| {
if let Ok(s) = s.into_string() {
Some(s)
} else {
None
}
})
.unwrap_or_else(|| String::from("unknown")); .unwrap_or_else(|| String::from("unknown"));
let model = device.property_value("ID_MODEL") let model = device
.property_value("ID_MODEL")
.map(std::ffi::OsString::from) .map(std::ffi::OsString::from)
.and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None }) .and_then(|s| {
if let Ok(s) = s.into_string() {
Some(s)
} else {
None
}
})
.unwrap_or_else(|| String::from("unknown")); .unwrap_or_else(|| String::from("unknown"));
let dev_path = format!("/dev/tape/by-id/scsi-{}-sg", serial); let dev_path = format!("/dev/tape/by-id/scsi-{}-sg", serial);
@ -206,17 +248,14 @@ pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
} }
/// Test if a device exists, and returns associated `TapeDeviceInfo` /// Test if a device exists, and returns associated `TapeDeviceInfo`
pub fn lookup_device<'a>( pub fn lookup_device<'a>(devices: &'a [TapeDeviceInfo], path: &str) -> Option<&'a TapeDeviceInfo> {
devices: &'a[TapeDeviceInfo],
path: &str,
) -> Option<&'a TapeDeviceInfo> {
if let Ok(stat) = nix::sys::stat::stat(path) { if let Ok(stat) = nix::sys::stat::stat(path) {
let major = unsafe { libc::major(stat.st_rdev) }; let major = unsafe { libc::major(stat.st_rdev) };
let minor = unsafe { libc::minor(stat.st_rdev) }; let minor = unsafe { libc::minor(stat.st_rdev) };
devices.iter().find(|d| d.major == major && d.minor == minor) devices
.iter()
.find(|d| d.major == major && d.minor == minor)
} else { } else {
None None
} }
@ -224,10 +263,9 @@ pub fn lookup_device<'a>(
/// Lookup optional drive identification attributes /// Lookup optional drive identification attributes
pub fn lookup_device_identification<'a>( pub fn lookup_device_identification<'a>(
devices: &'a[TapeDeviceInfo], devices: &'a [TapeDeviceInfo],
path: &str, path: &str,
) -> OptionalDeviceIdentification { ) -> OptionalDeviceIdentification {
if let Some(info) = lookup_device(devices, path) { if let Some(info) = lookup_device(devices, path) {
OptionalDeviceIdentification { OptionalDeviceIdentification {
vendor: Some(info.vendor.clone()), vendor: Some(info.vendor.clone()),
@ -244,20 +282,15 @@ pub fn lookup_device_identification<'a>(
} }
/// Make sure path is a lto tape device /// Make sure path is a lto tape device
pub fn check_drive_path( pub fn check_drive_path(drives: &[TapeDeviceInfo], path: &str) -> Result<(), Error> {
drives: &[TapeDeviceInfo],
path: &str,
) -> Result<(), Error> {
if lookup_device(drives, path).is_none() { if lookup_device(drives, path).is_none() {
bail!("path '{}' is not a lto SCSI-generic tape device", path); bail!("path '{}' is not a lto SCSI-generic tape device", path);
} }
Ok(()) Ok(())
} }
/// Check for correct Major/Minor numbers /// Check for correct Major/Minor numbers
pub fn check_tape_is_lto_tape_device(file: &File) -> Result<(), Error> { pub fn check_tape_is_lto_tape_device(file: &File) -> Result<(), Error> {
let stat = nix::sys::stat::fstat(file.as_raw_fd())?; let stat = nix::sys::stat::fstat(file.as_raw_fd())?;
let devnum = stat.st_rdev; let devnum = stat.st_rdev;
@ -281,10 +314,7 @@ pub fn check_tape_is_lto_tape_device(file: &File) -> Result<(), Error> {
/// The open call use O_NONBLOCK, but that flag is cleard after open /// The open call use O_NONBLOCK, but that flag is cleard after open
/// succeeded. This also checks if the device is a non-rewinding tape /// succeeded. This also checks if the device is a non-rewinding tape
/// device. /// device.
pub fn open_lto_tape_device( pub fn open_lto_tape_device(path: &str) -> Result<File, Error> {
path: &str,
) -> Result<File, Error> {
let file = OpenOptions::new() let file = OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
@ -293,14 +323,12 @@ pub fn open_lto_tape_device(
// clear O_NONBLOCK from now on. // clear O_NONBLOCK from now on.
let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL) let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL).into_io_result()?;
.into_io_result()?;
let mut flags = OFlag::from_bits_truncate(flags); let mut flags = OFlag::from_bits_truncate(flags);
flags.remove(OFlag::O_NONBLOCK); flags.remove(OFlag::O_NONBLOCK);
fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)) fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)).into_io_result()?;
.into_io_result()?;
check_tape_is_lto_tape_device(&file) check_tape_is_lto_tape_device(&file)
.map_err(|err| format_err!("device type check {:?} failed - {}", path, err))?; .map_err(|err| format_err!("device type check {:?} failed - {}", path, err))?;
@ -308,15 +336,20 @@ pub fn open_lto_tape_device(
Ok(file) Ok(file)
} }
// shell completion helper // shell completion helper
/// List changer device paths /// List changer device paths
pub fn complete_changer_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { pub fn complete_changer_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
linux_tape_changer_list().iter().map(|v| v.path.clone()).collect() linux_tape_changer_list()
.iter()
.map(|v| v.path.clone())
.collect()
} }
/// List tape device paths /// List tape device paths
pub fn complete_drive_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { pub fn complete_drive_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
lto_tape_device_list().iter().map(|v| v.path.clone()).collect() lto_tape_device_list()
.iter()
.map(|v| v.path.clone())
.collect()
} }

View File

@ -1,9 +1,9 @@
//! SCSI changer implementation using libsgutil2 //! SCSI changer implementation using libsgutil2
use std::os::unix::prelude::AsRawFd;
use std::io::Read;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::Read;
use std::os::unix::prelude::AsRawFd;
use std::path::Path; use std::path::Path;
use std::fs::{OpenOptions, File};
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use endian_trait::Endian; use endian_trait::Endian;
@ -13,31 +13,25 @@ use proxmox_io::ReadExt;
use pbs_api_types::ScsiTapeChanger; use pbs_api_types::ScsiTapeChanger;
use crate::{ use crate::{
ElementStatus,MtxStatus,TransportElementStatus,DriveStatus,StorageElementStatus, sgutils2::{scsi_ascii_to_string, scsi_inquiry, ScsiError, SgRaw, SENSE_KEY_NOT_READY},
sgutils2::{ DriveStatus, ElementStatus, MtxStatus, StorageElementStatus, TransportElementStatus,
SgRaw,
SENSE_KEY_NOT_READY,
ScsiError,
scsi_ascii_to_string,
scsi_inquiry,
},
}; };
const SCSI_CHANGER_DEFAULT_TIMEOUT: usize = 60*5; // 5 minutes const SCSI_CHANGER_DEFAULT_TIMEOUT: usize = 60 * 5; // 5 minutes
const SCSI_VOLUME_TAG_LEN: usize = 36; const SCSI_VOLUME_TAG_LEN: usize = 36;
/// Initialize element status (Inventory) /// Initialize element status (Inventory)
pub fn initialize_element_status<F: AsRawFd>(file: &mut F) -> Result<(), Error> { pub fn initialize_element_status<F: AsRawFd>(file: &mut F) -> Result<(), Error> {
let mut sg_raw = SgRaw::new(file, 64)?; let mut sg_raw = SgRaw::new(file, 64)?;
// like mtx(1), set a very long timeout (30 minutes) // like mtx(1), set a very long timeout (30 minutes)
sg_raw.set_timeout(30*60); sg_raw.set_timeout(30 * 60);
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.extend(&[0x07, 0, 0, 0, 0, 0]); // INITIALIZE ELEMENT STATUS (07h) cmd.extend(&[0x07, 0, 0, 0, 0, 0]); // INITIALIZE ELEMENT STATUS (07h)
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("initializte element status (07h) failed - {}", err))?; .map_err(|err| format_err!("initializte element status (07h) failed - {}", err))?;
Ok(()) Ok(())
@ -78,7 +72,6 @@ fn execute_scsi_command<F: AsRawFd>(
error_prefix: &str, error_prefix: &str,
retry: bool, retry: bool,
) -> Result<Vec<u8>, Error> { ) -> Result<Vec<u8>, Error> {
let start = std::time::SystemTime::now(); let start = std::time::SystemTime::now();
let mut last_msg: Option<String> = None; let mut last_msg: Option<String> = None;
@ -103,9 +96,12 @@ fn execute_scsi_command<F: AsRawFd>(
if let ScsiError::Sense(ref sense) = err { if let ScsiError::Sense(ref sense) = err {
// Not Ready - becoming ready // Not Ready - becoming ready
if sense.sense_key == SENSE_KEY_NOT_READY && sense.asc == 0x04 && sense.ascq == 1 { if sense.sense_key == SENSE_KEY_NOT_READY
&& sense.asc == 0x04
&& sense.ascq == 1
{
// wait up to 5 minutes, long enough to finish inventorize // wait up to 5 minutes, long enough to finish inventorize
timeout = std::time::Duration::new(5*60, 0); timeout = std::time::Duration::new(5 * 60, 0);
} }
} }
@ -120,11 +116,9 @@ fn execute_scsi_command<F: AsRawFd>(
} }
} }
fn read_element_address_assignment<F: AsRawFd>( fn read_element_address_assignment<F: AsRawFd>(
file: &mut F, file: &mut F,
) -> Result<AddressAssignmentPage, Error> { ) -> Result<AddressAssignmentPage, Error> {
let allocation_len: u8 = u8::MAX; let allocation_len: u8 = u8::MAX;
let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
@ -148,7 +142,8 @@ fn read_element_address_assignment<F: AsRawFd>(
} }
Ok(page) Ok(page)
}).map_err(|err: Error| format_err!("decode element address assignment page failed - {}", err)) })
.map_err(|err: Error| format_err!("decode element address assignment page failed - {}", err))
} }
fn scsi_move_medium_cdb( fn scsi_move_medium_cdb(
@ -156,7 +151,6 @@ fn scsi_move_medium_cdb(
source_element_address: u16, source_element_address: u16,
destination_element_address: u16, destination_element_address: u16,
) -> Vec<u8> { ) -> Vec<u8> {
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.push(0xA5); // MOVE MEDIUM (A5h) cmd.push(0xA5); // MOVE MEDIUM (A5h)
cmd.push(0); // reserved cmd.push(0); // reserved
@ -172,11 +166,7 @@ fn scsi_move_medium_cdb(
} }
/// Load media from storage slot into drive /// Load media from storage slot into drive
pub fn load_slot( pub fn load_slot(file: &mut File, from_slot: u64, drivenum: u64) -> Result<(), Error> {
file: &mut File,
from_slot: u64,
drivenum: u64,
) -> Result<(), Error> {
let status = read_element_status(file)?; let status = read_element_status(file)?;
let transport_address = status.transport_address(); let transport_address = status.transport_address();
@ -192,19 +182,15 @@ pub fn load_slot(
let mut sg_raw = SgRaw::new(file, 64)?; let mut sg_raw = SgRaw::new(file, 64)?;
sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("load drive failed - {}", err))?; .map_err(|err| format_err!("load drive failed - {}", err))?;
Ok(()) Ok(())
} }
/// Unload media from drive into a storage slot /// Unload media from drive into a storage slot
pub fn unload( pub fn unload(file: &mut File, to_slot: u64, drivenum: u64) -> Result<(), Error> {
file: &mut File,
to_slot: u64,
drivenum: u64,
) -> Result<(), Error> {
let status = read_element_status(file)?; let status = read_element_status(file)?;
let transport_address = status.transport_address(); let transport_address = status.transport_address();
@ -220,7 +206,8 @@ pub fn unload(
let mut sg_raw = SgRaw::new(file, 64)?; let mut sg_raw = SgRaw::new(file, 64)?;
sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("unload drive failed - {}", err))?; .map_err(|err| format_err!("unload drive failed - {}", err))?;
Ok(()) Ok(())
@ -232,7 +219,6 @@ pub fn transfer_medium<F: AsRawFd>(
from_slot: u64, from_slot: u64,
to_slot: u64, to_slot: u64,
) -> Result<(), Error> { ) -> Result<(), Error> {
let status = read_element_status(file)?; let status = read_element_status(file)?;
let transport_address = status.transport_address(); let transport_address = status.transport_address();
@ -248,10 +234,13 @@ pub fn transfer_medium<F: AsRawFd>(
let mut sg_raw = SgRaw::new(file, 64)?; let mut sg_raw = SgRaw::new(file, 64)?;
sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
sg_raw.do_command(&cmd) sg_raw.do_command(&cmd).map_err(|err| {
.map_err(|err| { format_err!(
format_err!("transfer medium from slot {} to slot {} failed - {}", "transfer medium from slot {} to slot {} failed - {}",
from_slot, to_slot, err) from_slot,
to_slot,
err
)
})?; })?;
Ok(()) Ok(())
@ -293,7 +282,6 @@ fn scsi_read_element_status_cdb(
element_type: ElementType, element_type: ElementType,
allocation_len: u32, allocation_len: u32,
) -> Vec<u8> { ) -> Vec<u8> {
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.push(0xB8); // READ ELEMENT STATUS (B8h) cmd.push(0xB8); // READ ELEMENT STATUS (B8h)
cmd.push(element_type.byte1()); cmd.push(element_type.byte1());
@ -315,7 +303,6 @@ fn get_element<F: AsRawFd>(
allocation_len: u32, allocation_len: u32,
mut retry: bool, mut retry: bool,
) -> Result<DecodedStatusPage, Error> { ) -> Result<DecodedStatusPage, Error> {
let mut start_element_address = 0; let mut start_element_address = 0;
let number_of_elements: u16 = 1000; // some changers limit the query let number_of_elements: u16 = 1000; // some changers limit the query
@ -328,7 +315,12 @@ fn get_element<F: AsRawFd>(
}; };
loop { loop {
let cmd = scsi_read_element_status_cdb(start_element_address, number_of_elements, element_type, allocation_len); let cmd = scsi_read_element_status_cdb(
start_element_address,
number_of_elements,
element_type,
allocation_len,
);
let data = execute_scsi_command(sg_raw, &cmd, "read element status (B8h)", retry)?; let data = execute_scsi_command(sg_raw, &cmd, "read element status (B8h)", retry)?;
@ -364,7 +356,6 @@ fn get_element<F: AsRawFd>(
/// Read element status. /// Read element status.
pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error> { pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error> {
let inquiry = scsi_inquiry(file)?; let inquiry = scsi_inquiry(file)?;
if inquiry.peripheral_type != 8 { if inquiry.peripheral_type != 8 {
@ -387,15 +378,30 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error>
let page = get_element(&mut sg_raw, ElementType::Storage, allocation_len, true)?; let page = get_element(&mut sg_raw, ElementType::Storage, allocation_len, true)?;
storage_slots.extend(page.storage_slots); storage_slots.extend(page.storage_slots);
let page = get_element(&mut sg_raw, ElementType::ImportExport, allocation_len, false)?; let page = get_element(
&mut sg_raw,
ElementType::ImportExport,
allocation_len,
false,
)?;
import_export_slots.extend(page.import_export_slots); import_export_slots.extend(page.import_export_slots);
let page = get_element(&mut sg_raw, ElementType::DataTransfer, allocation_len, false)?; let page = get_element(
&mut sg_raw,
ElementType::DataTransfer,
allocation_len,
false,
)?;
drives.extend(page.drives); drives.extend(page.drives);
// get the serial + vendor + model, // get the serial + vendor + model,
// some changer require this to be an extra scsi command // some changer require this to be an extra scsi command
let page = get_element(&mut sg_raw, ElementType::DataTransferWithDVCID, allocation_len, false)?; let page = get_element(
&mut sg_raw,
ElementType::DataTransferWithDVCID,
allocation_len,
false,
)?;
// should be in same order and same count, but be on the safe side. // should be in same order and same count, but be on the safe side.
// there should not be too many drives normally // there should not be too many drives normally
for drive in drives.iter_mut() { for drive in drives.iter_mut() {
@ -408,7 +414,12 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error>
} }
} }
let page = get_element(&mut sg_raw, ElementType::MediumTransport, allocation_len, false)?; let page = get_element(
&mut sg_raw,
ElementType::MediumTransport,
allocation_len,
false,
)?;
transports.extend(page.transports); transports.extend(page.transports);
let transport_count = setup.transport_element_count as usize; let transport_count = setup.transport_element_count as usize;
@ -451,7 +462,11 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error>
let mut slots = storage_slots; let mut slots = storage_slots;
slots.extend(import_export_slots); slots.extend(import_export_slots);
let mut status = MtxStatus { transports, drives, slots }; let mut status = MtxStatus {
transports,
drives,
slots,
};
// sanity checks // sanity checks
if status.drives.is_empty() { if status.drives.is_empty() {
@ -482,8 +497,7 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error>
pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> { pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
let path = &config.path; let path = &config.path;
let mut file = open(path) let mut file = open(path).map_err(|err| format_err!("error opening '{}': {}", path, err))?;
.map_err(|err| format_err!("error opening '{}': {}", path, err))?;
let mut status = read_element_status(&mut file) let mut status = read_element_status(&mut file)
.map_err(|err| format_err!("error reading element status: {}", err))?; .map_err(|err| format_err!("error reading element status: {}", err))?;
@ -492,14 +506,13 @@ pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
Ok(status) Ok(status)
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Endian)] #[derive(Endian)]
struct ElementStatusHeader { struct ElementStatusHeader {
first_element_address_reported: u16, first_element_address_reported: u16,
number_of_elements_available: u16, number_of_elements_available: u16,
reserved: u8, reserved: u8,
byte_count_of_report_available: [u8;3], byte_count_of_report_available: [u8; 3],
} }
#[repr(C, packed)] #[repr(C, packed)]
@ -509,18 +522,17 @@ struct SubHeader {
flags: u8, flags: u8,
descriptor_length: u16, descriptor_length: u16,
reserved: u8, reserved: u8,
byte_count_of_descriptor_data_available: [u8;3], byte_count_of_descriptor_data_available: [u8; 3],
} }
impl SubHeader { impl SubHeader {
fn parse_optional_volume_tag<R: Read>( fn parse_optional_volume_tag<R: Read>(
&self, &self,
reader: &mut R, reader: &mut R,
full: bool, full: bool,
) -> Result<Option<String>, Error> { ) -> Result<Option<String>, Error> {
if (self.flags & 128) != 0 {
if (self.flags & 128) != 0 { // has PVolTag // has PVolTag
let tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?; let tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?;
if full { if full {
let volume_tag = scsi_ascii_to_string(&tmp); let volume_tag = scsi_ascii_to_string(&tmp);
@ -532,12 +544,9 @@ impl SubHeader {
// AFAIK, tape changer do not use AlternateVolumeTag // AFAIK, tape changer do not use AlternateVolumeTag
// but parse anyways, just to be sure // but parse anyways, just to be sure
fn skip_alternate_volume_tag<R: Read>( fn skip_alternate_volume_tag<R: Read>(&self, reader: &mut R) -> Result<Option<String>, Error> {
&self, if (self.flags & 64) != 0 {
reader: &mut R, // has AVolTag
) -> Result<Option<String>, Error> {
if (self.flags & 64) != 0 { // has AVolTag
let _tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?; let _tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?;
} }
@ -547,13 +556,14 @@ impl SubHeader {
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Endian)] #[derive(Endian)]
struct TransportDescriptor { // Robot/Griper struct TransportDescriptor {
// Robot/Griper
element_address: u16, element_address: u16,
flags1: u8, flags1: u8,
reserved_3: u8, reserved_3: u8,
additional_sense_code: u8, additional_sense_code: u8,
additional_sense_code_qualifier: u8, additional_sense_code_qualifier: u8,
reserved_6: [u8;3], reserved_6: [u8; 3],
flags2: u8, flags2: u8,
source_storage_element_address: u16, source_storage_element_address: u16,
// volume tag and Mixed media descriptor follows (depends on flags) // volume tag and Mixed media descriptor follows (depends on flags)
@ -561,7 +571,8 @@ struct TransportDescriptor { // Robot/Griper
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Endian)] #[derive(Endian)]
struct TransferDescriptor { // Tape drive struct TransferDescriptor {
// Tape drive
element_address: u16, element_address: u16,
flags1: u8, flags1: u8,
reserved_3: u8, reserved_3: u8,
@ -578,7 +589,8 @@ struct TransferDescriptor { // Tape drive
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Endian)] #[derive(Endian)]
struct DvcidHead { // Drive Identifier Header struct DvcidHead {
// Drive Identifier Header
code_set: u8, code_set: u8,
identifier_type: u8, identifier_type: u8,
reserved: u8, reserved: u8,
@ -588,13 +600,14 @@ struct DvcidHead { // Drive Identifier Header
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Endian)] #[derive(Endian)]
struct StorageDescriptor { // Mail Slot struct StorageDescriptor {
// Mail Slot
element_address: u16, element_address: u16,
flags1: u8, flags1: u8,
reserved_3: u8, reserved_3: u8,
additional_sense_code: u8, additional_sense_code: u8,
additional_sense_code_qualifier: u8, additional_sense_code_qualifier: u8,
reserved_6: [u8;3], reserved_6: [u8; 3],
flags2: u8, flags2: u8,
source_storage_element_address: u16, source_storage_element_address: u16,
// volume tag and Mixed media descriptor follows (depends on flags) // volume tag and Mixed media descriptor follows (depends on flags)
@ -630,7 +643,8 @@ fn decode_dvcid_info<R: Read>(reader: &mut R) -> Result<DvcidInfo, Error> {
let dvcid: DvcidHead = unsafe { reader.read_be_value()? }; let dvcid: DvcidHead = unsafe { reader.read_be_value()? };
let (serial, vendor, model) = match (dvcid.code_set, dvcid.identifier_type) { let (serial, vendor, model) = match (dvcid.code_set, dvcid.identifier_type) {
(2, 0) => { // Serial number only (Quantum Superloader3 uses this) (2, 0) => {
// Serial number only (Quantum Superloader3 uses this)
let serial = reader.read_exact_allocated(dvcid.identifier_len as usize)?; let serial = reader.read_exact_allocated(dvcid.identifier_len as usize)?;
let serial = scsi_ascii_to_string(&serial); let serial = scsi_ascii_to_string(&serial);
(Some(serial), None, None) (Some(serial), None, None)
@ -661,9 +675,7 @@ fn decode_element_status_page(
data: &[u8], data: &[u8],
start_element_address: u16, start_element_address: u16,
) -> Result<DecodedStatusPage, Error> { ) -> Result<DecodedStatusPage, Error> {
proxmox_lang::try_block!({ proxmox_lang::try_block!({
let mut result = DecodedStatusPage { let mut result = DecodedStatusPage {
last_element_address: None, last_element_address: None,
transports: Vec::new(), transports: Vec::new(),
@ -690,7 +702,11 @@ fn decode_element_status_page(
if len < reader.len() { if len < reader.len() {
reader = &reader[..len]; reader = &reader[..len];
} else if len > reader.len() { } else if len > reader.len() {
bail!("wrong amount of data: expected {}, got {}", len, reader.len()); bail!(
"wrong amount of data: expected {}, got {}",
len,
reader.len()
);
} }
loop { loop {
@ -763,7 +779,8 @@ fn decode_element_status_page(
4 => { 4 => {
let desc: TransferDescriptor = unsafe { reader.read_be_value()? }; let desc: TransferDescriptor = unsafe { reader.read_be_value()? };
let loaded_slot = if (desc.flags2 & 128) != 0 { // SValid let loaded_slot = if (desc.flags2 & 128) != 0 {
// SValid
Some(desc.source_storage_element_address as u64) Some(desc.source_storage_element_address as u64)
} else { } else {
None None
@ -798,23 +815,21 @@ fn decode_element_status_page(
} }
Ok(result) Ok(result)
}).map_err(|err: Error| format_err!("decode element status failed - {}", err)) })
.map_err(|err: Error| format_err!("decode element status failed - {}", err))
} }
/// Open the device for read/write, returns the file handle /// Open the device for read/write, returns the file handle
pub fn open<P: AsRef<Path>>(path: P) -> Result<File, Error> { pub fn open<P: AsRef<Path>>(path: P) -> Result<File, Error> {
let file = OpenOptions::new() let file = OpenOptions::new().read(true).write(true).open(path)?;
.read(true)
.write(true)
.open(path)?;
Ok(file) Ok(file)
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use anyhow::Error;
use super::*; use super::*;
use anyhow::Error;
struct StorageDesc { struct StorageDesc {
address: u16, address: u16,
@ -826,9 +841,10 @@ mod test {
trailing: &[u8], trailing: &[u8],
element_type: u8, element_type: u8,
) -> Vec<u8> { ) -> Vec<u8> {
let descs: Vec<Vec<u8>> = descriptors.iter().map(|desc| { let descs: Vec<Vec<u8>> = descriptors
build_storage_descriptor(desc, trailing) .iter()
}).collect(); .map(|desc| build_storage_descriptor(desc, trailing))
.collect();
let (desc_len, address) = if let Some(el) = descs.get(0) { let (desc_len, address) = if let Some(el) = descs.get(0) {
(el.len() as u16, descriptors[0].address) (el.len() as u16, descriptors[0].address)
@ -863,10 +879,7 @@ mod test {
res res
} }
fn build_storage_descriptor( fn build_storage_descriptor(desc: &StorageDesc, trailing: &[u8]) -> Vec<u8> {
desc: &StorageDesc,
trailing: &[u8],
) -> Vec<u8> {
let mut res = Vec::new(); let mut res = Vec::new();
res.push(((desc.address >> 8) & 0xFF) as u8); res.push(((desc.address >> 8) & 0xFF) as u8);
res.push((desc.address & 0xFF) as u8); res.push((desc.address & 0xFF) as u8);
@ -876,7 +889,7 @@ mod test {
res.push(0x00); // full res.push(0x00); // full
} }
res.extend_from_slice(&[0,0,0,0,0,0,0x80]); res.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0x80]);
res.push(((desc.address >> 8) & 0xFF) as u8); res.push(((desc.address >> 8) & 0xFF) as u8);
res.push((desc.address & 0xFF) as u8); res.push((desc.address & 0xFF) as u8);
@ -942,7 +955,7 @@ mod test {
pvoltag: Some("1234567890".to_string()), pvoltag: Some("1234567890".to_string()),
}, },
]; ];
let test_data = build_element_status_page(descs, &[0,0,0,0,0], 0x2); let test_data = build_element_status_page(descs, &[0, 0, 0, 0, 0], 0x2);
let page = decode_element_status_page(&test_data, 0)?; let page = decode_element_status_page(&test_data, 0)?;
assert_eq!(page.storage_slots.len(), 2); assert_eq!(page.storage_slots.len(), 2);
Ok(()) Ok(())

View File

@ -1,10 +1,10 @@
use std::time::SystemTime; use std::convert::TryFrom;
use std::convert::TryInto;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::os::unix::fs::OpenOptionsExt; use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::path::Path; use std::path::Path;
use std::convert::TryFrom; use std::time::SystemTime;
use std::convert::TryInto;
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use endian_trait::Endian; use endian_trait::Endian;
@ -25,40 +25,28 @@ pub use mam::*;
mod report_density; mod report_density;
pub use report_density::*; pub use report_density::*;
use proxmox_sys::error::SysResult;
use proxmox_io::{ReadExt, WriteExt}; use proxmox_io::{ReadExt, WriteExt};
use proxmox_sys::error::SysResult;
use pbs_api_types::{MamAttribute, Lp17VolumeStatistics, LtoDriveAndMediaStatus}; use pbs_api_types::{Lp17VolumeStatistics, LtoDriveAndMediaStatus, MamAttribute};
use crate::{ use crate::{
BlockRead,
BlockReadError,
BlockWrite,
BlockedWriter,
BlockedReader,
sgutils2::{ sgutils2::{
SgRaw, alloc_page_aligned_buffer, scsi_inquiry, scsi_mode_sense, scsi_request_sense, InquiryInfo,
SenseInfo, ModeBlockDescriptor, ModeParameterHeader, ScsiError, SenseInfo, SgRaw,
ScsiError,
InquiryInfo,
ModeParameterHeader,
ModeBlockDescriptor,
alloc_page_aligned_buffer,
scsi_inquiry,
scsi_mode_sense,
scsi_request_sense,
}, },
BlockRead, BlockReadError, BlockWrite, BlockedReader, BlockedWriter,
}; };
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Endian, Debug, Copy, Clone)] #[derive(Endian, Debug, Copy, Clone)]
pub struct ReadPositionLongPage { pub struct ReadPositionLongPage {
flags: u8, flags: u8,
reserved: [u8;3], reserved: [u8; 3],
partition_number: u32, partition_number: u32,
pub logical_object_number: u64, pub logical_object_number: u64,
pub logical_file_id: u64, pub logical_file_id: u64,
obsolete: [u8;8], obsolete: [u8; 8],
} }
#[repr(C, packed)] #[repr(C, packed)]
@ -70,11 +58,10 @@ struct DataCompressionModePage {
flags3: u8, flags3: u8,
compression_algorithm: u32, compression_algorithm: u32,
decompression_algorithm: u32, decompression_algorithm: u32,
reserved: [u8;4], reserved: [u8; 4],
} }
impl DataCompressionModePage { impl DataCompressionModePage {
pub fn set_compression(&mut self, enable: bool) { pub fn set_compression(&mut self, enable: bool) {
if enable { if enable {
self.flags2 |= 128; self.flags2 |= 128;
@ -94,15 +81,13 @@ struct MediumConfigurationModePage {
page_code: u8, // 0x1d page_code: u8, // 0x1d
page_length: u8, // 0x1e page_length: u8, // 0x1e
flags2: u8, flags2: u8,
reserved: [u8;29], reserved: [u8; 29],
} }
impl MediumConfigurationModePage { impl MediumConfigurationModePage {
pub fn is_worm(&self) -> bool { pub fn is_worm(&self) -> bool {
(self.flags2 & 1) == 1 (self.flags2 & 1) == 1
} }
} }
#[derive(Debug)] #[derive(Debug)]
@ -122,18 +107,19 @@ pub struct SgTape {
} }
impl SgTape { impl SgTape {
const SCSI_TAPE_DEFAULT_TIMEOUT: usize = 60 * 10; // 10 minutes
const SCSI_TAPE_DEFAULT_TIMEOUT: usize = 60*10; // 10 minutes
/// Create a new instance /// Create a new instance
/// ///
/// Uses scsi_inquiry to check the device type. /// Uses scsi_inquiry to check the device type.
pub fn new(mut file: File) -> Result<Self, Error> { pub fn new(mut file: File) -> Result<Self, Error> {
let info = scsi_inquiry(&mut file)?; let info = scsi_inquiry(&mut file)?;
if info.peripheral_type != 1 { if info.peripheral_type != 1 {
bail!("not a tape device (peripheral_type = {})", info.peripheral_type); bail!(
"not a tape device (peripheral_type = {})",
info.peripheral_type
);
} }
Ok(Self { Ok(Self {
@ -169,14 +155,12 @@ impl SgTape {
.open(path)?; .open(path)?;
// then clear O_NONBLOCK // then clear O_NONBLOCK
let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL) let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL).into_io_result()?;
.into_io_result()?;
let mut flags = OFlag::from_bits_truncate(flags); let mut flags = OFlag::from_bits_truncate(flags);
flags.remove(OFlag::O_NONBLOCK); flags.remove(OFlag::O_NONBLOCK);
fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)) fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)).into_io_result()?;
.into_io_result()?;
Self::new(file) Self::new(file)
} }
@ -203,7 +187,8 @@ impl SgTape {
} }
cmd.extend(&[0, 0, 0, 0]); cmd.extend(&[0, 0, 0, 0]);
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("erase failed - {}", err))?; .map_err(|err| format_err!("erase failed - {}", err))?;
Ok(()) Ok(())
@ -211,7 +196,6 @@ impl SgTape {
/// Format media, single partition /// Format media, single partition
pub fn format_media(&mut self, fast: bool) -> Result<(), Error> { pub fn format_media(&mut self, fast: bool) -> Result<(), Error> {
// try to get info about loaded media first // try to get info about loaded media first
let (has_format, is_worm) = match self.read_medium_configuration_page() { let (has_format, is_worm) = match self.read_medium_configuration_page() {
Ok((_head, block_descriptor, page)) => { Ok((_head, block_descriptor, page)) => {
@ -236,7 +220,6 @@ impl SgTape {
} }
Ok(()) Ok(())
} else { } else {
self.rewind()?; self.rewind()?;
@ -261,7 +244,6 @@ impl SgTape {
/// Lock/Unlock drive door /// Lock/Unlock drive door
pub fn set_medium_removal(&mut self, allow: bool) -> Result<(), ScsiError> { pub fn set_medium_removal(&mut self, allow: bool) -> Result<(), ScsiError> {
let mut sg_raw = SgRaw::new(&mut self.file, 16)?; let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
let mut cmd = Vec::new(); let mut cmd = Vec::new();
@ -279,13 +261,13 @@ impl SgTape {
} }
pub fn rewind(&mut self) -> Result<(), Error> { pub fn rewind(&mut self) -> Result<(), Error> {
let mut sg_raw = SgRaw::new(&mut self.file, 16)?; let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.extend(&[0x01, 0, 0, 0, 0, 0]); // REWIND cmd.extend(&[0x01, 0, 0, 0, 0, 0]); // REWIND
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("rewind failed - {}", err))?; .map_err(|err| format_err!("rewind failed - {}", err))?;
Ok(()) Ok(())
@ -303,7 +285,8 @@ impl SgTape {
self.rewind()?; self.rewind()?;
let mut sg_raw = SgRaw::new(&mut self.file, 16)?; let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
sg_raw.do_command(SPACE_ONE_FILEMARK) sg_raw
.do_command(SPACE_ONE_FILEMARK)
.map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?; .map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
return Ok(()); return Ok(());
} }
@ -334,20 +317,22 @@ impl SgTape {
cmd.extend(&fixed_position.to_be_bytes()); cmd.extend(&fixed_position.to_be_bytes());
cmd.extend(&[0, 0, 0, 0]); cmd.extend(&[0, 0, 0, 0]);
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("locate file {} failed - {}", position, err))?; .map_err(|err| format_err!("locate file {} failed - {}", position, err))?;
// LOCATE always position at the BOT side of the filemark, so // LOCATE always position at the BOT side of the filemark, so
// we need to move to other side of filemark // we need to move to other side of filemark
sg_raw.do_command(SPACE_ONE_FILEMARK) sg_raw
.do_command(SPACE_ONE_FILEMARK)
.map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?; .map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
if self.locate_offset.is_none() { if self.locate_offset.is_none() {
// check if we landed at correct position // check if we landed at correct position
let current_file = self.current_file_number()?; let current_file = self.current_file_number()?;
if current_file != position { if current_file != position {
let offset: i64 = let offset: i64 = i64::try_from((position as i128) - (current_file as i128))
i64::try_from((position as i128) - (current_file as i128)).map_err(|err| { .map_err(|err| {
format_err!( format_err!(
"locate_file: offset between {} and {} invalid: {}", "locate_file: offset between {} and {} invalid: {}",
position, position,
@ -370,7 +355,6 @@ impl SgTape {
} }
pub fn position(&mut self) -> Result<ReadPositionLongPage, Error> { pub fn position(&mut self) -> Result<ReadPositionLongPage, Error> {
let expected_size = std::mem::size_of::<ReadPositionLongPage>(); let expected_size = std::mem::size_of::<ReadPositionLongPage>();
let mut sg_raw = SgRaw::new(&mut self.file, 32)?; let mut sg_raw = SgRaw::new(&mut self.file, 32)?;
@ -381,12 +365,17 @@ impl SgTape {
// reference manual. // reference manual.
cmd.extend(&[0x34, 0x06, 0, 0, 0, 0, 0, 0, 0, 0]); // READ POSITION LONG FORM cmd.extend(&[0x34, 0x06, 0, 0, 0, 0, 0, 0, 0, 0]); // READ POSITION LONG FORM
let data = sg_raw.do_command(&cmd) let data = sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("read position failed - {}", err))?; .map_err(|err| format_err!("read position failed - {}", err))?;
let page = proxmox_lang::try_block!({ let page = proxmox_lang::try_block!({
if data.len() != expected_size { if data.len() != expected_size {
bail!("got unexpected data len ({} != {}", data.len(), expected_size); bail!(
"got unexpected data len ({} != {}",
data.len(),
expected_size
);
} }
let mut reader = data; let mut reader = data;
@ -394,7 +383,8 @@ impl SgTape {
let page: ReadPositionLongPage = unsafe { reader.read_be_value()? }; let page: ReadPositionLongPage = unsafe { reader.read_be_value()? };
Ok(page) Ok(page)
}).map_err(|err: Error| format_err!("decode position page failed - {}", err))?; })
.map_err(|err: Error| format_err!("decode position page failed - {}", err))?;
if page.partition_number != 0 { if page.partition_number != 0 {
bail!("detecthed partitioned tape - not supported"); bail!("detecthed partitioned tape - not supported");
@ -410,7 +400,6 @@ impl SgTape {
/// Check if we are positioned after a filemark (or BOT) /// Check if we are positioned after a filemark (or BOT)
pub fn check_filemark(&mut self) -> Result<bool, Error> { pub fn check_filemark(&mut self) -> Result<bool, Error> {
let pos = self.position()?; let pos = self.position()?;
if pos.logical_object_number == 0 { if pos.logical_object_number == 0 {
// at BOT, Ok (no filemark required) // at BOT, Ok (no filemark required)
@ -421,13 +410,24 @@ impl SgTape {
match self.space(-1, true) { match self.space(-1, true) {
Ok(_) => { Ok(_) => {
self.space(1, true) // move back to end self.space(1, true) // move back to end
.map_err(|err| format_err!("check_filemark failed (space forward) - {}", err))?; .map_err(|err| {
format_err!("check_filemark failed (space forward) - {}", err)
})?;
Ok(false) Ok(false)
} }
Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 })) => { Err(ScsiError::Sense(SenseInfo {
sense_key: 0,
asc: 0,
ascq: 1,
})) => {
// Filemark detected - good // Filemark detected - good
self.space(1, false) // move to EOT side of filemark self.space(1, false) // move to EOT side of filemark
.map_err(|err| format_err!("check_filemark failed (move to EOT side of filemark) - {}", err))?; .map_err(|err| {
format_err!(
"check_filemark failed (move to EOT side of filemark) - {}",
err
)
})?;
Ok(true) Ok(true)
} }
Err(err) => { Err(err) => {
@ -442,7 +442,8 @@ impl SgTape {
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.extend(&[0x11, 0x03, 0, 0, 0, 0]); // SPACE(6) move to EOD cmd.extend(&[0x11, 0x03, 0, 0, 0, 0]); // SPACE(6) move to EOD
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("move to EOD failed - {}", err))?; .map_err(|err| format_err!("move to EOD failed - {}", err))?;
if write_missing_eof && !self.check_filemark()? { if write_missing_eof && !self.check_filemark()? {
@ -503,7 +504,8 @@ impl SgTape {
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.extend(&[0x1B, 0, 0, 0, 0, 0]); // LODA/UNLOAD HOLD=0, LOAD=0 cmd.extend(&[0x1B, 0, 0, 0, 0, 0]); // LODA/UNLOAD HOLD=0, LOAD=0
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("eject failed - {}", err))?; .map_err(|err| format_err!("eject failed - {}", err))?;
Ok(()) Ok(())
@ -515,24 +517,21 @@ impl SgTape {
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.extend(&[0x1B, 0, 0, 0, 0b0000_0001, 0]); // LODA/UNLOAD HOLD=0, LOAD=1 cmd.extend(&[0x1B, 0, 0, 0, 0b0000_0001, 0]); // LODA/UNLOAD HOLD=0, LOAD=1
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("load media failed - {}", err))?; .map_err(|err| format_err!("load media failed - {}", err))?;
Ok(()) Ok(())
} }
pub fn write_filemarks( pub fn write_filemarks(&mut self, count: usize, immediate: bool) -> Result<(), std::io::Error> {
&mut self,
count: usize,
immediate: bool,
) -> Result<(), std::io::Error> {
if count > 255 { if count > 255 {
proxmox_lang::io_bail!("write_filemarks failed: got strange count '{}'", count); proxmox_lang::io_bail!("write_filemarks failed: got strange count '{}'", count);
} }
let mut sg_raw = SgRaw::new(&mut self.file, 16) let mut sg_raw = SgRaw::new(&mut self.file, 16).map_err(|err| {
.map_err(|err| proxmox_lang::io_format_err!("write_filemarks failed (alloc) - {}", err))?; proxmox_lang::io_format_err!("write_filemarks failed (alloc) - {}", err)
})?;
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
let mut cmd = Vec::new(); let mut cmd = Vec::new();
@ -547,9 +546,11 @@ impl SgTape {
match sg_raw.do_command(&cmd) { match sg_raw.do_command(&cmd) {
Ok(_) => { /* OK */ } Ok(_) => { /* OK */ }
Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => { Err(ScsiError::Sense(SenseInfo {
/* LEOM - ignore */ sense_key: 0,
} asc: 0,
ascq: 2,
})) => { /* LEOM - ignore */ }
Err(err) => { Err(err) => {
proxmox_lang::io_bail!("write filemark failed - {}", err); proxmox_lang::io_bail!("write filemark failed - {}", err);
} }
@ -565,7 +566,6 @@ impl SgTape {
} }
pub fn test_unit_ready(&mut self) -> Result<(), Error> { pub fn test_unit_ready(&mut self) -> Result<(), Error> {
let mut sg_raw = SgRaw::new(&mut self.file, 16)?; let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
sg_raw.set_timeout(30); // use short timeout sg_raw.set_timeout(30); // use short timeout
let mut cmd = Vec::new(); let mut cmd = Vec::new();
@ -580,7 +580,6 @@ impl SgTape {
} }
pub fn wait_until_ready(&mut self) -> Result<(), Error> { pub fn wait_until_ready(&mut self) -> Result<(), Error> {
let start = SystemTime::now(); let start = SystemTime::now();
let max_wait = std::time::Duration::new(Self::SCSI_TAPE_DEFAULT_TIMEOUT as u64, 0); let max_wait = std::time::Duration::new(Self::SCSI_TAPE_DEFAULT_TIMEOUT as u64, 0);
@ -612,11 +611,7 @@ impl SgTape {
read_volume_statistics(&mut self.file) read_volume_statistics(&mut self.file)
} }
pub fn set_encryption( pub fn set_encryption(&mut self, key: Option<[u8; 32]>) -> Result<(), Error> {
&mut self,
key: Option<[u8; 32]>,
) -> Result<(), Error> {
self.encryption_key_loaded = key.is_some(); self.encryption_key_loaded = key.is_some();
set_encryption(&mut self.file, key) set_encryption(&mut self.file, key)
@ -626,15 +621,13 @@ impl SgTape {
// //
// Returns true if the drive reached the Logical End Of Media (early warning) // Returns true if the drive reached the Logical End Of Media (early warning)
fn write_block(&mut self, data: &[u8]) -> Result<bool, std::io::Error> { fn write_block(&mut self, data: &[u8]) -> Result<bool, std::io::Error> {
let transfer_len = data.len(); let transfer_len = data.len();
if transfer_len > 0x800000 { if transfer_len > 0x800000 {
proxmox_lang::io_bail!("write failed - data too large"); proxmox_lang::io_bail!("write failed - data too large");
} }
let mut sg_raw = SgRaw::new(&mut self.file, 0) let mut sg_raw = SgRaw::new(&mut self.file, 0).unwrap(); // cannot fail with size 0
.unwrap(); // cannot fail with size 0
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
let mut cmd = Vec::new(); let mut cmd = Vec::new();
@ -649,8 +642,12 @@ impl SgTape {
//println!("WRITE {:?}", data); //println!("WRITE {:?}", data);
match sg_raw.do_out_command(&cmd, data) { match sg_raw.do_out_command(&cmd, data) {
Ok(()) => { Ok(false) } Ok(()) => Ok(false),
Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => { Err(ScsiError::Sense(SenseInfo {
sense_key: 0,
asc: 0,
ascq: 2,
})) => {
Ok(true) // LEOM Ok(true) // LEOM
} }
Err(err) => { Err(err) => {
@ -663,13 +660,12 @@ impl SgTape {
let transfer_len = buffer.len(); let transfer_len = buffer.len();
if transfer_len > 0xFFFFFF { if transfer_len > 0xFFFFFF {
return Err(BlockReadError::Error( return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
proxmox_lang::io_format_err!("read failed - buffer too large") "read failed - buffer too large"
)); )));
} }
let mut sg_raw = SgRaw::new(&mut self.file, 0) let mut sg_raw = SgRaw::new(&mut self.file, 0).unwrap(); // cannot fail with size 0
.unwrap(); // cannot fail with size 0
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
let mut cmd = Vec::new(); let mut cmd = Vec::new();
@ -683,23 +679,34 @@ impl SgTape {
let data = match sg_raw.do_in_command(&cmd, buffer) { let data = match sg_raw.do_in_command(&cmd, buffer) {
Ok(data) => data, Ok(data) => data,
Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 })) => { Err(ScsiError::Sense(SenseInfo {
sense_key: 0,
asc: 0,
ascq: 1,
})) => {
return Err(BlockReadError::EndOfFile); return Err(BlockReadError::EndOfFile);
} }
Err(ScsiError::Sense(SenseInfo { sense_key: 8, asc: 0, ascq: 5 })) => { Err(ScsiError::Sense(SenseInfo {
sense_key: 8,
asc: 0,
ascq: 5,
})) => {
return Err(BlockReadError::EndOfStream); return Err(BlockReadError::EndOfStream);
} }
Err(err) => { Err(err) => {
return Err(BlockReadError::Error( return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
proxmox_lang::io_format_err!("read failed - {}", err) "read failed - {}",
)); err
)));
} }
}; };
if data.len() != transfer_len { if data.len() != transfer_len {
return Err(BlockReadError::Error( return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
proxmox_lang::io_format_err!("read failed - unexpected block len ({} != {})", data.len(), buffer.len()) "read failed - unexpected block len ({} != {})",
)); data.len(),
buffer.len()
)));
} }
Ok(transfer_len) Ok(transfer_len)
@ -717,7 +724,6 @@ impl SgTape {
/// Set all options we need/want /// Set all options we need/want
pub fn set_default_options(&mut self) -> Result<(), Error> { pub fn set_default_options(&mut self) -> Result<(), Error> {
let compression = Some(true); let compression = Some(true);
let block_length = Some(0); // variable length mode let block_length = Some(0); // variable length mode
let buffer_mode = Some(true); // Always use drive buffer let buffer_mode = Some(true); // Always use drive buffer
@ -734,7 +740,6 @@ impl SgTape {
block_length: Option<u32>, block_length: Option<u32>,
buffer_mode: Option<bool>, buffer_mode: Option<bool>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Note: Read/Modify/Write // Note: Read/Modify/Write
let (mut head, mut block_descriptor, mut page) = self.read_compression_page()?; let (mut head, mut block_descriptor, mut page) = self.read_compression_page()?;
@ -766,7 +771,7 @@ impl SgTape {
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.push(0x55); // MODE SELECT(10) cmd.push(0x55); // MODE SELECT(10)
cmd.push(0b0001_0000); // PF=1 cmd.push(0b0001_0000); // PF=1
cmd.extend(&[0,0,0,0,0]); //reserved cmd.extend(&[0, 0, 0, 0, 0]); //reserved
let param_list_len: u16 = data.len() as u16; let param_list_len: u16 = data.len() as u16;
cmd.extend(&param_list_len.to_be_bytes()); cmd.extend(&param_list_len.to_be_bytes());
@ -776,7 +781,8 @@ impl SgTape {
buffer[..data.len()].copy_from_slice(&data[..]); buffer[..data.len()].copy_from_slice(&data[..]);
sg_raw.do_out_command(&cmd, &buffer[..data.len()]) sg_raw
.do_out_command(&cmd, &buffer[..data.len()])
.map_err(|err| format_err!("set drive options failed - {}", err))?; .map_err(|err| format_err!("set drive options failed - {}", err))?;
Ok(()) Ok(())
@ -784,10 +790,16 @@ impl SgTape {
fn read_medium_configuration_page( fn read_medium_configuration_page(
&mut self, &mut self,
) -> Result<(ModeParameterHeader, ModeBlockDescriptor, MediumConfigurationModePage), Error> { ) -> Result<
(
let (head, block_descriptor, page): (_,_, MediumConfigurationModePage) ModeParameterHeader,
= scsi_mode_sense(&mut self.file, false, 0x1d, 0)?; ModeBlockDescriptor,
MediumConfigurationModePage,
),
Error,
> {
let (head, block_descriptor, page): (_, _, MediumConfigurationModePage) =
scsi_mode_sense(&mut self.file, false, 0x1d, 0)?;
proxmox_lang::try_block!({ proxmox_lang::try_block!({
if (page.page_code & 0b0011_1111) != 0x1d { if (page.page_code & 0b0011_1111) != 0x1d {
@ -803,15 +815,22 @@ impl SgTape {
}; };
Ok((head, block_descriptor, page)) Ok((head, block_descriptor, page))
}).map_err(|err| format_err!("read_medium_configuration failed - {}", err)) })
.map_err(|err| format_err!("read_medium_configuration failed - {}", err))
} }
fn read_compression_page( fn read_compression_page(
&mut self, &mut self,
) -> Result<(ModeParameterHeader, ModeBlockDescriptor, DataCompressionModePage), Error> { ) -> Result<
(
let (head, block_descriptor, page): (_,_, DataCompressionModePage) ModeParameterHeader,
= scsi_mode_sense(&mut self.file, false, 0x0f, 0)?; ModeBlockDescriptor,
DataCompressionModePage,
),
Error,
> {
let (head, block_descriptor, page): (_, _, DataCompressionModePage) =
scsi_mode_sense(&mut self.file, false, 0x0f, 0)?;
proxmox_lang::try_block!({ proxmox_lang::try_block!({
if (page.page_code & 0b0011_1111) != 0x0f { if (page.page_code & 0b0011_1111) != 0x0f {
@ -827,7 +846,8 @@ impl SgTape {
}; };
Ok((head, block_descriptor, page)) Ok((head, block_descriptor, page))
}).map_err(|err| format_err!("read_compression_page failed: {}", err)) })
.map_err(|err| format_err!("read_compression_page failed: {}", err))
} }
/// Read drive options/status /// Read drive options/status
@ -835,7 +855,6 @@ impl SgTape {
/// We read the drive compression page, including the /// We read the drive compression page, including the
/// block_descriptor. This is all information we need for now. /// block_descriptor. This is all information we need for now.
pub fn read_drive_status(&mut self) -> Result<LtoTapeStatus, Error> { pub fn read_drive_status(&mut self) -> Result<LtoTapeStatus, Error> {
// We do a Request Sense, but ignore the result. // We do a Request Sense, but ignore the result.
// This clears deferred error or media changed events. // This clears deferred error or media changed events.
let _ = scsi_request_sense(&mut self.file); let _ = scsi_request_sense(&mut self.file);
@ -853,10 +872,10 @@ impl SgTape {
/// Get Tape and Media status /// Get Tape and Media status
pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error> { pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error> {
let drive_status = self.read_drive_status()?; let drive_status = self.read_drive_status()?;
let alert_flags = self.tape_alert_flags() let alert_flags = self
.tape_alert_flags()
.map(|flags| format!("{:?}", flags)) .map(|flags| format!("{:?}", flags))
.ok(); .ok();
@ -881,7 +900,6 @@ impl SgTape {
}; };
if self.test_unit_ready().is_ok() { if self.test_unit_ready().is_ok() {
if drive_status.write_protect { if drive_status.write_protect {
status.write_protect = Some(drive_status.write_protect); status.write_protect = Some(drive_status.write_protect);
} }
@ -892,7 +910,6 @@ impl SgTape {
status.block_number = Some(position.logical_object_number); status.block_number = Some(position.logical_object_number);
if let Ok(mam) = self.cartridge_memory() { if let Ok(mam) = self.cartridge_memory() {
let usage = mam_extract_media_usage(&mam)?; let usage = mam_extract_media_usage(&mam)?;
status.manufactured = Some(usage.manufactured); status.manufactured = Some(usage.manufactured);
@ -900,7 +917,6 @@ impl SgTape {
status.bytes_written = Some(usage.bytes_written); status.bytes_written = Some(usage.bytes_written);
if let Ok(volume_stats) = self.volume_statistics() { if let Ok(volume_stats) = self.volume_statistics() {
let passes = std::cmp::max( let passes = std::cmp::max(
volume_stats.beginning_of_medium_passes, volume_stats.beginning_of_medium_passes,
volume_stats.middle_of_tape_passes, volume_stats.middle_of_tape_passes,
@ -908,7 +924,7 @@ impl SgTape {
// assume max. 16000 medium passes // assume max. 16000 medium passes
// see: https://en.wikipedia.org/wiki/Linear_Tape-Open // see: https://en.wikipedia.org/wiki/Linear_Tape-Open
let wearout: f64 = (passes as f64)/16000.0_f64; let wearout: f64 = (passes as f64) / 16000.0_f64;
status.medium_passes = Some(passes); status.medium_passes = Some(passes);
status.medium_wearout = Some(wearout); status.medium_wearout = Some(wearout);
@ -920,7 +936,6 @@ impl SgTape {
Ok(status) Ok(status)
} }
} }
impl Drop for SgTape { impl Drop for SgTape {
@ -932,31 +947,33 @@ impl Drop for SgTape {
} }
} }
pub struct SgTapeReader<'a> { pub struct SgTapeReader<'a> {
sg_tape: &'a mut SgTape, sg_tape: &'a mut SgTape,
end_of_file: bool, end_of_file: bool,
} }
impl <'a> SgTapeReader<'a> { impl<'a> SgTapeReader<'a> {
pub fn new(sg_tape: &'a mut SgTape) -> Self { pub fn new(sg_tape: &'a mut SgTape) -> Self {
Self { sg_tape, end_of_file: false, } Self {
sg_tape,
end_of_file: false,
}
} }
} }
impl <'a> BlockRead for SgTapeReader<'a> { impl<'a> BlockRead for SgTapeReader<'a> {
fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> { fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
if self.end_of_file { if self.end_of_file {
return Err(BlockReadError::Error(proxmox_lang::io_format_err!("detected read after EOF!"))); return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
"detected read after EOF!"
)));
} }
match self.sg_tape.read_block(buffer) { match self.sg_tape.read_block(buffer) {
Ok(usize) => Ok(usize), Ok(usize) => Ok(usize),
Err(BlockReadError::EndOfFile) => { Err(BlockReadError::EndOfFile) => {
self.end_of_file = true; self.end_of_file = true;
Err(BlockReadError::EndOfFile) Err(BlockReadError::EndOfFile)
}, }
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
@ -967,15 +984,16 @@ pub struct SgTapeWriter<'a> {
_leom_sent: bool, _leom_sent: bool,
} }
impl <'a> SgTapeWriter<'a> { impl<'a> SgTapeWriter<'a> {
pub fn new(sg_tape: &'a mut SgTape) -> Self { pub fn new(sg_tape: &'a mut SgTape) -> Self {
Self { sg_tape, _leom_sent: false } Self {
sg_tape,
_leom_sent: false,
}
} }
} }
impl <'a> BlockWrite for SgTapeWriter<'a> { impl<'a> BlockWrite for SgTapeWriter<'a> {
fn write_block(&mut self, buffer: &[u8]) -> Result<bool, std::io::Error> { fn write_block(&mut self, buffer: &[u8]) -> Result<bool, std::io::Error> {
self.sg_tape.write_block(buffer) self.sg_tape.write_block(buffer)
} }

View File

@ -1,20 +1,17 @@
use std::os::unix::prelude::AsRawFd;
use std::io::Write; use std::io::Write;
use std::os::unix::prelude::AsRawFd;
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use endian_trait::Endian; use endian_trait::Endian;
use proxmox_io::{ReadExt, WriteExt}; use proxmox_io::{ReadExt, WriteExt};
use crate::sgutils2::{SgRaw, alloc_page_aligned_buffer}; use crate::sgutils2::{alloc_page_aligned_buffer, SgRaw};
/// Test if drive supports hardware encryption /// Test if drive supports hardware encryption
/// ///
/// We search for AES_GCM algorithm with 256bits key. /// We search for AES_GCM algorithm with 256bits key.
pub fn has_encryption<F: AsRawFd>( pub fn has_encryption<F: AsRawFd>(file: &mut F) -> bool {
file: &mut F,
) -> bool {
let data = match sg_spin_data_encryption_caps(file) { let data = match sg_spin_data_encryption_caps(file) {
Ok(data) => data, Ok(data) => data,
Err(_) => return false, Err(_) => return false,
@ -25,11 +22,7 @@ pub fn has_encryption<F: AsRawFd>(
/// Set or clear encryption key /// Set or clear encryption key
/// ///
/// We always use mixed mode, /// We always use mixed mode,
pub fn set_encryption<F: AsRawFd>( pub fn set_encryption<F: AsRawFd>(file: &mut F, key: Option<[u8; 32]>) -> Result<(), Error> {
file: &mut F,
key: Option<[u8; 32]>,
) -> Result<(), Error> {
let data = match sg_spin_data_encryption_caps(file) { let data = match sg_spin_data_encryption_caps(file) {
Ok(data) => data, Ok(data) => data,
Err(_) if key.is_none() => { Err(_) if key.is_none() => {
@ -85,7 +78,6 @@ fn sg_spout_set_encryption<F: AsRawFd>(
algorythm_index: u8, algorythm_index: u8,
key: Option<[u8; 32]>, key: Option<[u8; 32]>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut sg_raw = SgRaw::new(file, 0)?; let mut sg_raw = SgRaw::new(file, 0)?;
let mut outbuf_len = std::mem::size_of::<SspSetDataEncryptionPage>(); let mut outbuf_len = std::mem::size_of::<SspSetDataEncryptionPage>();
@ -106,7 +98,11 @@ fn sg_spout_set_encryption<F: AsRawFd>(
algorythm_index, algorythm_index,
key_format: 0, key_format: 0,
reserved: [0u8; 8], reserved: [0u8; 8],
key_len: if let Some(ref key) = key { key.len() as u16 } else { 0 }, key_len: if let Some(ref key) = key {
key.len() as u16
} else {
0
},
}; };
let mut writer = &mut outbuf[..]; let mut writer = &mut outbuf[..];
@ -119,58 +115,72 @@ fn sg_spout_set_encryption<F: AsRawFd>(
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT) cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT)
cmd.push(0x20); // Tape Data Encryption Page cmd.push(0x20); // Tape Data Encryption Page
cmd.push(0);cmd.push(0x10); // Set Data Encryption page cmd.push(0);
cmd.push(0x10); // Set Data Encryption page
cmd.push(0); cmd.push(0);
cmd.push(0); cmd.push(0);
cmd.extend(&(outbuf_len as u32).to_be_bytes()); // data out len cmd.extend(&(outbuf_len as u32).to_be_bytes()); // data out len
cmd.push(0); cmd.push(0);
cmd.push(0); cmd.push(0);
sg_raw.do_out_command(&cmd, &outbuf) sg_raw
.do_out_command(&cmd, &outbuf)
.map_err(|err| format_err!("set data encryption SPOUT(20h[0010h]) failed - {}", err)) .map_err(|err| format_err!("set data encryption SPOUT(20h[0010h]) failed - {}", err))
} }
// Warning: this blocks and fails if there is no media loaded // Warning: this blocks and fails if there is no media loaded
fn sg_spin_data_encryption_status<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> { fn sg_spin_data_encryption_status<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
let allocation_len: u32 = 8192 + 4;
let allocation_len: u32 = 8192+4;
let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN) cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
cmd.push(0x20); // Tape Data Encryption Page cmd.push(0x20); // Tape Data Encryption Page
cmd.push(0);cmd.push(0x20); // Data Encryption Status page cmd.push(0);
cmd.push(0x20); // Data Encryption Status page
cmd.push(0); cmd.push(0);
cmd.push(0); cmd.push(0);
cmd.extend(&allocation_len.to_be_bytes()); cmd.extend(&allocation_len.to_be_bytes());
cmd.push(0); cmd.push(0);
cmd.push(0); cmd.push(0);
sg_raw.do_command(&cmd) sg_raw
.map_err(|err| format_err!("read data encryption status SPIN(20h[0020h]) failed - {}", err)) .do_command(&cmd)
.map_err(|err| {
format_err!(
"read data encryption status SPIN(20h[0020h]) failed - {}",
err
)
})
.map(|v| v.to_vec()) .map(|v| v.to_vec())
} }
// Warning: this blocks and fails if there is no media loaded // Warning: this blocks and fails if there is no media loaded
fn sg_spin_data_encryption_caps<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> { fn sg_spin_data_encryption_caps<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
let allocation_len: u32 = 8192 + 4;
let allocation_len: u32 = 8192+4;
let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN) cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
cmd.push(0x20); // Tape Data Encryption Page cmd.push(0x20); // Tape Data Encryption Page
cmd.push(0);cmd.push(0x10); // Data Encryption Capabilities page cmd.push(0);
cmd.push(0x10); // Data Encryption Capabilities page
cmd.push(0); cmd.push(0);
cmd.push(0); cmd.push(0);
cmd.extend(&allocation_len.to_be_bytes()); cmd.extend(&allocation_len.to_be_bytes());
cmd.push(0); cmd.push(0);
cmd.push(0); cmd.push(0);
sg_raw.do_command(&cmd) sg_raw
.map_err(|err| format_err!("read data encryption caps SPIN(20h[0010h]) failed - {}", err)) .do_command(&cmd)
.map_err(|err| {
format_err!(
"read data encryption caps SPIN(20h[0010h]) failed - {}",
err
)
})
.map(|v| v.to_vec()) .map(|v| v.to_vec())
} }
@ -215,7 +225,6 @@ struct SspDataEncryptionAlgorithmDescriptor {
// Returns the algorythm_index for AES-GCM // Returns the algorythm_index for AES-GCM
fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> { fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
proxmox_lang::try_block!({ proxmox_lang::try_block!({
let mut reader = data; let mut reader = data;
let _page: SspDataEncryptionCapabilityPage = unsafe { reader.read_be_value()? }; let _page: SspDataEncryptionCapabilityPage = unsafe { reader.read_be_value()? };
@ -223,9 +232,10 @@ fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
let mut aes_gcm_index = None; let mut aes_gcm_index = None;
loop { loop {
if reader.is_empty() { break; }; if reader.is_empty() {
let desc: SspDataEncryptionAlgorithmDescriptor = break;
unsafe { reader.read_be_value()? }; };
let desc: SspDataEncryptionAlgorithmDescriptor = unsafe { reader.read_be_value()? };
if desc.descriptor_len != 0x14 { if desc.descriptor_len != 0x14 {
bail!("got wrong key descriptor len"); bail!("got wrong key descriptor len");
} }
@ -245,8 +255,8 @@ fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
Some(index) => Ok(index), Some(index) => Ok(index),
None => bail!("drive does not support AES-GCM encryption"), None => bail!("drive does not support AES-GCM encryption"),
} }
}).map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err)) })
.map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err))
} }
#[derive(Endian)] #[derive(Endian)]
@ -266,7 +276,6 @@ struct SspDataEncryptionStatusPage {
} }
fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatus, Error> { fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatus, Error> {
proxmox_lang::try_block!({ proxmox_lang::try_block!({
let mut reader = data; let mut reader = data;
let page: SspDataEncryptionStatusPage = unsafe { reader.read_be_value()? }; let page: SspDataEncryptionStatusPage = unsafe { reader.read_be_value()? };
@ -283,11 +292,9 @@ fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatu
_ => bail!("unknown encryption mode"), _ => bail!("unknown encryption mode"),
}; };
let status = DataEncryptionStatus { let status = DataEncryptionStatus { mode };
mode,
};
Ok(status) Ok(status)
})
}).map_err(|err| format_err!("decode data encryption status page failed - {}", err)) .map_err(|err| format_err!("decode data encryption status page failed - {}", err))
} }

View File

@ -17,7 +17,7 @@ use super::TapeAlertFlags;
// see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1 // see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1
#[derive(Endian)] #[derive(Endian)]
#[repr(C,packed)] #[repr(C, packed)]
struct MamAttributeHeader { struct MamAttributeHeader {
id: u16, id: u16,
flags: u8, flags: u8,
@ -30,8 +30,13 @@ enum MamFormat {
DEC, DEC,
} }
static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[ static MAM_ATTRIBUTES: &[(u16, u16, MamFormat, &str)] = &[
(0x00_00, 8, MamFormat::DEC, "Remaining Capacity In Partition"), (
0x00_00,
8,
MamFormat::DEC,
"Remaining Capacity In Partition",
),
(0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"), (0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"),
(0x00_02, 8, MamFormat::DEC, "Tapealert Flags"), (0x00_02, 8, MamFormat::DEC, "Tapealert Flags"),
(0x00_03, 8, MamFormat::DEC, "Load Count"), (0x00_03, 8, MamFormat::DEC, "Load Count"),
@ -40,19 +45,66 @@ static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
(0x00_06, 1, MamFormat::BINARY, "Formatted Density Code"), (0x00_06, 1, MamFormat::BINARY, "Formatted Density Code"),
(0x00_07, 2, MamFormat::DEC, "Initialization Count"), (0x00_07, 2, MamFormat::DEC, "Initialization Count"),
(0x00_09, 4, MamFormat::BINARY, "Volume Change Reference"), (0x00_09, 4, MamFormat::BINARY, "Volume Change Reference"),
(
(0x02_0A, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Last Load"), 0x02_0A,
(0x02_0B, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-1"), 40,
(0x02_0C, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-2"), MamFormat::ASCII,
(0x02_0D, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-3"), "Device Vendor/Serial Number at Last Load",
),
(0x02_20, 8, MamFormat::DEC, "Total MBytes Written in Medium Life"), (
(0x02_21, 8, MamFormat::DEC, "Total MBytes Read In Medium Life"), 0x02_0B,
(0x02_22, 8, MamFormat::DEC, "Total MBytes Written in Current Load"), 40,
(0x02_23, 8, MamFormat::DEC, "Total MBytes Read in Current/Last Load"), MamFormat::ASCII,
(0x02_24, 8, MamFormat::BINARY, "Logical Position of First Encrypted Block"), "Device Vendor/Serial Number at Load-1",
(0x02_25, 8, MamFormat::BINARY, "Logical Position of First Unencrypted Block After the First Encrypted Block"), ),
(
0x02_0C,
40,
MamFormat::ASCII,
"Device Vendor/Serial Number at Load-2",
),
(
0x02_0D,
40,
MamFormat::ASCII,
"Device Vendor/Serial Number at Load-3",
),
(
0x02_20,
8,
MamFormat::DEC,
"Total MBytes Written in Medium Life",
),
(
0x02_21,
8,
MamFormat::DEC,
"Total MBytes Read In Medium Life",
),
(
0x02_22,
8,
MamFormat::DEC,
"Total MBytes Written in Current Load",
),
(
0x02_23,
8,
MamFormat::DEC,
"Total MBytes Read in Current/Last Load",
),
(
0x02_24,
8,
MamFormat::BINARY,
"Logical Position of First Encrypted Block",
),
(
0x02_25,
8,
MamFormat::BINARY,
"Logical Position of First Unencrypted Block After the First Encrypted Block",
),
(0x04_00, 8, MamFormat::ASCII, "Medium Manufacturer"), (0x04_00, 8, MamFormat::ASCII, "Medium Manufacturer"),
(0x04_01, 32, MamFormat::ASCII, "Medium Serial Number"), (0x04_01, 32, MamFormat::ASCII, "Medium Serial Number"),
(0x04_02, 4, MamFormat::DEC, "Medium Length"), (0x04_02, 4, MamFormat::DEC, "Medium Length"),
@ -64,27 +116,54 @@ static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
(0x04_08, 1, MamFormat::BINARY, "Medium Type"), (0x04_08, 1, MamFormat::BINARY, "Medium Type"),
(0x04_09, 2, MamFormat::BINARY, "Medium Type Information"), (0x04_09, 2, MamFormat::BINARY, "Medium Type Information"),
(0x04_0B, 10, MamFormat::BINARY, "Supported Density Codes"), (0x04_0B, 10, MamFormat::BINARY, "Supported Density Codes"),
(0x08_00, 8, MamFormat::ASCII, "Application Vendor"), (0x08_00, 8, MamFormat::ASCII, "Application Vendor"),
(0x08_01, 32, MamFormat::ASCII, "Application Name"), (0x08_01, 32, MamFormat::ASCII, "Application Name"),
(0x08_02, 8, MamFormat::ASCII, "Application Version"), (0x08_02, 8, MamFormat::ASCII, "Application Version"),
(0x08_03, 160, MamFormat::ASCII, "User Medium Text Label"), (0x08_03, 160, MamFormat::ASCII, "User Medium Text Label"),
(0x08_04, 12, MamFormat::ASCII, "Date And Time Last Written"), (0x08_04, 12, MamFormat::ASCII, "Date And Time Last Written"),
(0x08_05, 1, MamFormat::BINARY, "Text Localization Identifier"), (
0x08_05,
1,
MamFormat::BINARY,
"Text Localization Identifier",
),
(0x08_06, 32, MamFormat::ASCII, "Barcode"), (0x08_06, 32, MamFormat::ASCII, "Barcode"),
(0x08_07, 80, MamFormat::ASCII, "Owning Host Textual Name"), (0x08_07, 80, MamFormat::ASCII, "Owning Host Textual Name"),
(0x08_08, 160, MamFormat::ASCII, "Media Pool"), (0x08_08, 160, MamFormat::ASCII, "Media Pool"),
(0x08_0B, 16, MamFormat::ASCII, "Application Format Version"), (0x08_0B, 16, MamFormat::ASCII, "Application Format Version"),
(0x08_0C, 50, MamFormat::ASCII, "Volume Coherency Information"), (
(0x08_20, 36, MamFormat::ASCII, "Medium Globally Unique Identifier"), 0x08_0C,
(0x08_21, 36, MamFormat::ASCII, "Media Pool Globally Unique Identifier"), 50,
MamFormat::ASCII,
(0x10_00, 28, MamFormat::BINARY, "Unique Cartridge Identify (UCI)"), "Volume Coherency Information",
(0x10_01, 24, MamFormat::BINARY, "Alternate Unique Cartridge Identify (Alt-UCI)"), ),
(
0x08_20,
36,
MamFormat::ASCII,
"Medium Globally Unique Identifier",
),
(
0x08_21,
36,
MamFormat::ASCII,
"Media Pool Globally Unique Identifier",
),
(
0x10_00,
28,
MamFormat::BINARY,
"Unique Cartridge Identify (UCI)",
),
(
0x10_01,
24,
MamFormat::BINARY,
"Alternate Unique Cartridge Identify (Alt-UCI)",
),
]; ];
lazy_static::lazy_static!{ lazy_static::lazy_static! {
static ref MAM_ATTRIBUTE_NAMES: HashMap<u16, &'static (u16, u16, MamFormat, &'static str)> = { static ref MAM_ATTRIBUTE_NAMES: HashMap<u16, &'static (u16, u16, MamFormat, &'static str)> = {
let mut map = HashMap::new(); let mut map = HashMap::new();
@ -98,8 +177,7 @@ lazy_static::lazy_static!{
} }
fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> { fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
let alloc_len: u32 = 32 * 1024;
let alloc_len: u32 = 32*1024;
let mut sg_raw = SgRaw::new(file, alloc_len as usize)?; let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
let mut cmd = Vec::new(); let mut cmd = Vec::new();
@ -108,33 +186,35 @@ fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
cmd.extend(&alloc_len.to_be_bytes()); // alloc len cmd.extend(&alloc_len.to_be_bytes()); // alloc len
cmd.extend(&[0u8, 0u8]); cmd.extend(&[0u8, 0u8]);
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("read cartidge memory failed - {}", err)) .map_err(|err| format_err!("read cartidge memory failed - {}", err))
.map(|v| v.to_vec()) .map(|v| v.to_vec())
} }
/// Read Medium auxiliary memory attributes (cartridge memory) using raw SCSI command. /// Read Medium auxiliary memory attributes (cartridge memory) using raw SCSI command.
pub fn read_mam_attributes<F: AsRawFd>(file: &mut F) -> Result<Vec<MamAttribute>, Error> { pub fn read_mam_attributes<F: AsRawFd>(file: &mut F) -> Result<Vec<MamAttribute>, Error> {
let data = read_tape_mam(file)?; let data = read_tape_mam(file)?;
decode_mam_attributes(&data) decode_mam_attributes(&data)
} }
fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> { fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
let mut reader = data; let mut reader = data;
let data_len: u32 = unsafe { reader.read_be_value()? }; let data_len: u32 = unsafe { reader.read_be_value()? };
let expected_len = data_len as usize; let expected_len = data_len as usize;
if reader.len() < expected_len { if reader.len() < expected_len {
bail!("read_mam_attributes: got unexpected data len ({} != {})", reader.len(), expected_len); bail!(
"read_mam_attributes: got unexpected data len ({} != {})",
reader.len(),
expected_len
);
} else if reader.len() > expected_len { } else if reader.len() > expected_len {
// Note: Quantum hh7 returns the allocation_length instead of real data_len // Note: Quantum hh7 returns the allocation_length instead of real data_len
reader = &data[4..expected_len+4]; reader = &data[4..expected_len + 4];
} }
let mut list = Vec::new(); let mut list = Vec::new();
@ -164,7 +244,8 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
} else if info.1 == 4 { } else if info.1 == 4 {
format!("{}", u32::from_be_bytes(data[0..4].try_into()?)) format!("{}", u32::from_be_bytes(data[0..4].try_into()?))
} else if info.1 == 8 { } else if info.1 == 8 {
if head_id == 2 { // Tape Alert Flags if head_id == 2 {
// Tape Alert Flags
let value = u64::from_be_bytes(data[0..8].try_into()?); let value = u64::from_be_bytes(data[0..8].try_into()?);
let flags = TapeAlertFlags::from_bits_truncate(value); let flags = TapeAlertFlags::from_bits_truncate(value);
format!("{:?}", flags) format!("{:?}", flags)
@ -174,7 +255,7 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
} else { } else {
unreachable!(); unreachable!();
} }
}, }
MamFormat::BINARY => hex::encode(&data), MamFormat::BINARY => hex::encode(&data),
}; };
list.push(MamAttribute { list.push(MamAttribute {
@ -183,7 +264,10 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
value, value,
}); });
} else { } else {
eprintln!("read_mam_attributes: got starnge data len for id {:04X}", head_id); eprintln!(
"read_mam_attributes: got starnge data len for id {:04X}",
head_id
);
} }
} else { } else {
// skip unknown IDs // skip unknown IDs
@ -201,8 +285,11 @@ pub struct MediaUsageInfo {
/// Extract Media Usage Information from Cartridge Memory /// Extract Media Usage Information from Cartridge Memory
pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, Error> { pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, Error> {
let manufactured: i64 = match mam
let manufactured: i64 = match mam.iter().find(|v| v.id == 0x04_06).map(|v| v.value.clone()) { .iter()
.find(|v| v.id == 0x04_06)
.map(|v| v.value.clone())
{
Some(date_str) => { Some(date_str) => {
if date_str.len() != 8 { if date_str.len() != 8 {
bail!("unable to parse 'Medium Manufacture Date' - wrong length"); bail!("unable to parse 'Medium Manufacture Date' - wrong length");
@ -222,15 +309,27 @@ pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, E
None => bail!("unable to read MAM 'Medium Manufacture Date'"), None => bail!("unable to read MAM 'Medium Manufacture Date'"),
}; };
let bytes_written: u64 = match mam.iter().find(|v| v.id == 0x02_20).map(|v| v.value.clone()) { let bytes_written: u64 = match mam
Some(read_str) => read_str.parse::<u64>()? * 1024*1024, .iter()
.find(|v| v.id == 0x02_20)
.map(|v| v.value.clone())
{
Some(read_str) => read_str.parse::<u64>()? * 1024 * 1024,
None => bail!("unable to read MAM 'Total MBytes Written In Medium Life'"), None => bail!("unable to read MAM 'Total MBytes Written In Medium Life'"),
}; };
let bytes_read: u64 = match mam.iter().find(|v| v.id == 0x02_21).map(|v| v.value.clone()) { let bytes_read: u64 = match mam
Some(read_str) => read_str.parse::<u64>()? * 1024*1024, .iter()
.find(|v| v.id == 0x02_21)
.map(|v| v.value.clone())
{
Some(read_str) => read_str.parse::<u64>()? * 1024 * 1024,
None => bail!("unable to read MAM 'Total MBytes Read In Medium Life'"), None => bail!("unable to read MAM 'Total MBytes Read In Medium Life'"),
}; };
Ok(MediaUsageInfo { manufactured, bytes_written, bytes_read }) Ok(MediaUsageInfo {
manufactured,
bytes_written,
bytes_read,
})
} }

View File

@ -1,6 +1,6 @@
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use std::io::Read;
use endian_trait::Endian; use endian_trait::Endian;
use std::io::Read;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use proxmox_io::ReadExt; use proxmox_io::ReadExt;
@ -33,7 +33,8 @@ pub fn report_density<F: AsRawFd>(file: &mut F) -> Result<u8, Error> {
cmd.extend(&alloc_len.to_be_bytes()); // alloc len cmd.extend(&alloc_len.to_be_bytes()); // alloc len
cmd.push(0u8); // control byte cmd.push(0u8); // control byte
let data = sg_raw.do_command(&cmd) let data = sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("report density failed - {}", err))?; .map_err(|err| format_err!("report density failed - {}", err))?;
let mut max_density = 0u8; let mut max_density = 0u8;
@ -48,13 +49,15 @@ pub fn report_density<F: AsRawFd>(file: &mut F) -> Result<u8, Error> {
bail!("invalid page length {} {}", page_len + 2, data.len()); bail!("invalid page length {} {}", page_len + 2, data.len());
} else { } else {
// Note: Quantum hh7 returns the allocation_length instead of real data_len // Note: Quantum hh7 returns the allocation_length instead of real data_len
reader = &data[2..page_len+2]; reader = &data[2..page_len + 2];
} }
let mut reserved = [0u8; 2]; let mut reserved = [0u8; 2];
reader.read_exact(&mut reserved)?; reader.read_exact(&mut reserved)?;
loop { loop {
if reader.is_empty() { break; } if reader.is_empty() {
break;
}
let block: DesnityDescriptorBlock = unsafe { reader.read_be_value()? }; let block: DesnityDescriptorBlock = unsafe { reader.read_be_value()? };
if block.primary_density_code > max_density { if block.primary_density_code > max_density {
max_density = block.primary_density_code; max_density = block.primary_density_code;
@ -62,8 +65,8 @@ pub fn report_density<F: AsRawFd>(file: &mut F) -> Result<u8, Error> {
} }
Ok(()) Ok(())
})
}).map_err(|err| format_err!("decode report density failed - {}", err))?; .map_err(|err| format_err!("decode report density failed - {}", err))?;
Ok(max_density) Ok(max_density)
} }

View File

@ -7,7 +7,7 @@ use proxmox_io::ReadExt;
use crate::sgutils2::SgRaw; use crate::sgutils2::SgRaw;
bitflags::bitflags!{ bitflags::bitflags! {
/// Tape Alert Flags /// Tape Alert Flags
/// ///
@ -74,15 +74,12 @@ bitflags::bitflags!{
/// Read Tape Alert Flags using raw SCSI command. /// Read Tape Alert Flags using raw SCSI command.
pub fn read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<TapeAlertFlags, Error> { pub fn read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<TapeAlertFlags, Error> {
let data = sg_read_tape_alert_flags(file)?; let data = sg_read_tape_alert_flags(file)?;
decode_tape_alert_flags(&data) decode_tape_alert_flags(&data)
} }
fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> { fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
let mut sg_raw = SgRaw::new(file, 512)?; let mut sg_raw = SgRaw::new(file, 512)?;
// Note: We cannjot use LP 2Eh TapeAlerts, because that clears flags on read. // Note: We cannjot use LP 2Eh TapeAlerts, because that clears flags on read.
@ -91,7 +88,7 @@ fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error>
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.push(0x4D); // LOG SENSE cmd.push(0x4D); // LOG SENSE
cmd.push(0); cmd.push(0);
cmd.push((1<<6) | 0x12); // Tape Alert Response log page cmd.push((1 << 6) | 0x12); // Tape Alert Response log page
cmd.push(0); cmd.push(0);
cmd.push(0); cmd.push(0);
cmd.push(0); cmd.push(0);
@ -99,13 +96,13 @@ fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error>
cmd.extend(&[2u8, 0u8]); // alloc len cmd.extend(&[2u8, 0u8]); // alloc len
cmd.push(0u8); // control byte cmd.push(0u8); // control byte
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("read tape alert flags failed - {}", err)) .map_err(|err| format_err!("read tape alert flags failed - {}", err))
.map(|v| v.to_vec()) .map(|v| v.to_vec())
} }
fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> { fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> {
proxmox_lang::try_block!({ proxmox_lang::try_block!({
if !((data[0] & 0x7f) == 0x12 && data[1] == 0) { if !((data[0] & 0x7f) == 0x12 && data[1] == 0) {
bail!("invalid response"); bail!("invalid response");
@ -136,30 +133,30 @@ fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> {
value = value.reverse_bits(); value = value.reverse_bits();
Ok(TapeAlertFlags::from_bits_truncate(value)) Ok(TapeAlertFlags::from_bits_truncate(value))
}).map_err(|err| format_err!("decode tape alert flags failed - {}", err)) })
.map_err(|err| format_err!("decode tape alert flags failed - {}", err))
} }
const CRITICAL_FLAG_MASK: u64 = const CRITICAL_FLAG_MASK: u64 = TapeAlertFlags::MEDIA.bits()
TapeAlertFlags::MEDIA.bits() | | TapeAlertFlags::WRITE_FAILURE.bits()
TapeAlertFlags::WRITE_FAILURE.bits() | | TapeAlertFlags::READ_FAILURE.bits()
TapeAlertFlags::READ_FAILURE.bits() | | TapeAlertFlags::WRITE_PROTECT.bits()
TapeAlertFlags::WRITE_PROTECT.bits() | | TapeAlertFlags::UNRECOVERABLE_SNAPPED_TAPE.bits()
TapeAlertFlags::UNRECOVERABLE_SNAPPED_TAPE.bits() | | TapeAlertFlags::FORCED_EJECT.bits()
TapeAlertFlags::FORCED_EJECT.bits() | | TapeAlertFlags::EXPIRED_CLEANING_MEDIA.bits()
TapeAlertFlags::EXPIRED_CLEANING_MEDIA.bits() | | TapeAlertFlags::INVALID_CLEANING_TAPE.bits()
TapeAlertFlags::INVALID_CLEANING_TAPE.bits() | | TapeAlertFlags::HARDWARE_A.bits()
TapeAlertFlags::HARDWARE_A.bits() | | TapeAlertFlags::HARDWARE_B.bits()
TapeAlertFlags::HARDWARE_B.bits() | | TapeAlertFlags::EJECT_MEDIA.bits()
TapeAlertFlags::EJECT_MEDIA.bits() | | TapeAlertFlags::PREDICTIVE_FAILURE.bits()
TapeAlertFlags::PREDICTIVE_FAILURE.bits() | | TapeAlertFlags::LOADER_STRAY_TAPE.bits()
TapeAlertFlags::LOADER_STRAY_TAPE.bits() | | TapeAlertFlags::LOADER_MAGAZINE.bits()
TapeAlertFlags::LOADER_MAGAZINE.bits() | | TapeAlertFlags::TAPE_SYSTEM_AREA_WRITE_FAILURE.bits()
TapeAlertFlags::TAPE_SYSTEM_AREA_WRITE_FAILURE.bits() | | TapeAlertFlags::TAPE_SYSTEM_AREA_READ_FAILURE.bits()
TapeAlertFlags::TAPE_SYSTEM_AREA_READ_FAILURE.bits() | | TapeAlertFlags::NO_START_OF_DATA.bits()
TapeAlertFlags::NO_START_OF_DATA.bits() | | TapeAlertFlags::LOADING_FAILURE.bits()
TapeAlertFlags::LOADING_FAILURE.bits() | | TapeAlertFlags::UNRECOVERABLE_UNLOAD_FAILURE.bits()
TapeAlertFlags::UNRECOVERABLE_UNLOAD_FAILURE.bits() | | TapeAlertFlags::AUTOMATION_INTERFACE_FAILURE.bits();
TapeAlertFlags::AUTOMATION_INTERFACE_FAILURE.bits();
/// Check if tape-alert-flags contains critial errors. /// Check if tape-alert-flags contains critial errors.
pub fn tape_alert_flags_critical(flags: TapeAlertFlags) -> bool { pub fn tape_alert_flags_critical(flags: TapeAlertFlags) -> bool {
@ -167,8 +164,7 @@ pub fn tape_alert_flags_critical(flags: TapeAlertFlags) -> bool {
} }
const MEDIA_LIFE_MASK: u64 = const MEDIA_LIFE_MASK: u64 =
TapeAlertFlags::MEDIA_LIFE.bits() | TapeAlertFlags::MEDIA_LIFE.bits() | TapeAlertFlags::NEARING_MEDIA_LIFE.bits();
TapeAlertFlags::NEARING_MEDIA_LIFE.bits();
/// Check if tape-alert-flags indicates media-life end /// Check if tape-alert-flags indicates media-life end
pub fn tape_alert_flags_media_life(flags: TapeAlertFlags) -> bool { pub fn tape_alert_flags_media_life(flags: TapeAlertFlags) -> bool {
@ -176,8 +172,7 @@ pub fn tape_alert_flags_media_life(flags: TapeAlertFlags) -> bool {
} }
const MEDIA_CLEAN_MASK: u64 = const MEDIA_CLEAN_MASK: u64 =
TapeAlertFlags::CLEAN_NOW.bits() | TapeAlertFlags::CLEAN_NOW.bits() | TapeAlertFlags::CLEAN_PERIODIC.bits();
TapeAlertFlags::CLEAN_PERIODIC.bits();
/// Check if tape-alert-flags indicates media cleaning request /// Check if tape-alert-flags indicates media cleaning request
pub fn tape_alert_flags_cleaning_request(flags: TapeAlertFlags) -> bool { pub fn tape_alert_flags_cleaning_request(flags: TapeAlertFlags) -> bool {

View File

@ -17,21 +17,19 @@ use crate::sgutils2::SgRaw;
/// The Volume Statistics log page is included in Ultrium 5 and later /// The Volume Statistics log page is included in Ultrium 5 and later
/// drives. /// drives.
pub fn read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Lp17VolumeStatistics, Error> { pub fn read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Lp17VolumeStatistics, Error> {
let data = sg_read_volume_statistics(file)?; let data = sg_read_volume_statistics(file)?;
decode_volume_statistics(&data) decode_volume_statistics(&data)
} }
fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> { fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
let alloc_len: u16 = 8192; let alloc_len: u16 = 8192;
let mut sg_raw = SgRaw::new(file, alloc_len as usize)?; let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.push(0x4D); // LOG SENSE cmd.push(0x4D); // LOG SENSE
cmd.push(0); cmd.push(0);
cmd.push((1<<6) | 0x17); // Volume Statistics log page cmd.push((1 << 6) | 0x17); // Volume Statistics log page
cmd.push(0); // Subpage 0 cmd.push(0); // Subpage 0
cmd.push(0); cmd.push(0);
cmd.push(0); cmd.push(0);
@ -39,7 +37,8 @@ fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error>
cmd.extend(&alloc_len.to_be_bytes()); // alloc len cmd.extend(&alloc_len.to_be_bytes()); // alloc len
cmd.push(0u8); // control byte cmd.push(0u8); // control byte
sg_raw.do_command(&cmd) sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("read tape volume statistics failed - {}", err)) .map_err(|err| format_err!("read tape volume statistics failed - {}", err))
.map(|v| v.to_vec()) .map(|v| v.to_vec())
} }
@ -53,8 +52,6 @@ struct LpParameterHeader {
} }
fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> { fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> {
let read_be_counter = |reader: &mut &[u8], len: u8| { let read_be_counter = |reader: &mut &[u8], len: u8| {
let len = len as usize; let len = len as usize;
if len == 0 || len > 8 { if len == 0 || len > 8 {
@ -86,7 +83,7 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
bail!("invalid page length"); bail!("invalid page length");
} else { } else {
// Note: Quantum hh7 returns the allocation_length instead of real data_len // Note: Quantum hh7 returns the allocation_length instead of real data_len
reader = &data[4..page_len+4]; reader = &data[4..page_len + 4];
} }
let mut stat = Lp17VolumeStatistics::default(); let mut stat = Lp17VolumeStatistics::default();
@ -107,8 +104,7 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
page_valid = true; page_valid = true;
} }
0x0001 => { 0x0001 => {
stat.volume_mounts = stat.volume_mounts = read_be_counter(&mut reader, head.parameter_len)?;
read_be_counter(&mut reader, head.parameter_len)?;
} }
0x0002 => { 0x0002 => {
stat.volume_datasets_written = stat.volume_datasets_written =
@ -131,8 +127,7 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
read_be_counter(&mut reader, head.parameter_len)?; read_be_counter(&mut reader, head.parameter_len)?;
} }
0x0007 => { 0x0007 => {
stat.volume_datasets_read = stat.volume_datasets_read = read_be_counter(&mut reader, head.parameter_len)?;
read_be_counter(&mut reader, head.parameter_len)?;
} }
0x0008 => { 0x0008 => {
stat.volume_recovered_read_errors = stat.volume_recovered_read_errors =
@ -175,12 +170,10 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
read_be_counter(&mut reader, head.parameter_len)?; read_be_counter(&mut reader, head.parameter_len)?;
} }
0x0014 => { 0x0014 => {
stat.medium_mount_time = stat.medium_mount_time = read_be_counter(&mut reader, head.parameter_len)?;
read_be_counter(&mut reader, head.parameter_len)?;
} }
0x0015 => { 0x0015 => {
stat.medium_ready_time = stat.medium_ready_time = read_be_counter(&mut reader, head.parameter_len)?;
read_be_counter(&mut reader, head.parameter_len)?;
} }
0x0016 => { 0x0016 => {
stat.total_native_capacity = stat.total_native_capacity =
@ -211,8 +204,7 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
read_be_counter(&mut reader, head.parameter_len)?; read_be_counter(&mut reader, head.parameter_len)?;
} }
0x0102 => { 0x0102 => {
stat.middle_of_tape_passes = stat.middle_of_tape_passes = read_be_counter(&mut reader, head.parameter_len)?;
read_be_counter(&mut reader, head.parameter_len)?;
} }
_ => { _ => {
reader.read_exact_allocated(head.parameter_len as usize)?; reader.read_exact_allocated(head.parameter_len as usize)?;
@ -225,6 +217,6 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
} }
Ok(stat) Ok(stat)
})
}).map_err(|err| format_err!("decode volume statistics failed - {}", err)) .map_err(|err| format_err!("decode volume statistics failed - {}", err))
} }

View File

@ -25,9 +25,7 @@ pub struct SenseInfo {
} }
impl std::fmt::Display for SenseInfo { impl std::fmt::Display for SenseInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let sense_text = SENSE_KEY_DESCRIPTIONS let sense_text = SENSE_KEY_DESCRIPTIONS
.get(self.sense_key as usize) .get(self.sense_key as usize)
.map(|s| String::from(*s)) .map(|s| String::from(*s))
@ -58,7 +56,9 @@ impl From<std::io::Error> for ScsiError {
// Opaque wrapper for sg_pt_base // Opaque wrapper for sg_pt_base
#[repr(C)] #[repr(C)]
struct SgPtBase { _private: [u8; 0] } struct SgPtBase {
_private: [u8; 0],
}
#[repr(transparent)] #[repr(transparent)]
struct SgPt { struct SgPt {
@ -184,9 +184,9 @@ pub struct RequestSenseFixed {
pub response_code: u8, pub response_code: u8,
obsolete: u8, obsolete: u8,
pub flags2: u8, pub flags2: u8,
pub information: [u8;4], pub information: [u8; 4],
pub additional_sense_len: u8, pub additional_sense_len: u8,
pub command_specific_information: [u8;4], pub command_specific_information: [u8; 4],
pub additional_sense_code: u8, pub additional_sense_code: u8,
pub additional_sense_code_qualifier: u8, pub additional_sense_code_qualifier: u8,
pub field_replacable_unit_code: u8, pub field_replacable_unit_code: u8,
@ -195,12 +195,12 @@ pub struct RequestSenseFixed {
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Endian, Debug)] #[derive(Endian, Debug)]
struct RequestSenseDescriptor{ struct RequestSenseDescriptor {
response_code: u8, response_code: u8,
sense_key: u8, sense_key: u8,
additional_sense_code: u8, additional_sense_code: u8,
additional_sense_code_qualifier: u8, additional_sense_code_qualifier: u8,
reserved: [u8;4], reserved: [u8; 4],
additional_sense_len: u8, additional_sense_len: u8,
} }
@ -228,12 +228,11 @@ pub struct ModeParameterHeader {
// not compatible with IBM. // not compatible with IBM.
pub medium_type: u8, pub medium_type: u8,
pub flags3: u8, pub flags3: u8,
reserved4: [u8;2], reserved4: [u8; 2],
pub block_descriptior_len: u16, pub block_descriptior_len: u16,
} }
impl ModeParameterHeader { impl ModeParameterHeader {
pub fn buffer_mode(&self) -> u8 { pub fn buffer_mode(&self) -> u8 {
(self.flags3 & 0b0111_0000) >> 4 (self.flags3 & 0b0111_0000) >> 4
} }
@ -256,18 +255,16 @@ impl ModeParameterHeader {
/// SCSI ModeBlockDescriptor for Tape devices /// SCSI ModeBlockDescriptor for Tape devices
pub struct ModeBlockDescriptor { pub struct ModeBlockDescriptor {
pub density_code: u8, pub density_code: u8,
pub number_of_blocks: [u8;3], pub number_of_blocks: [u8; 3],
reserverd: u8, reserverd: u8,
pub block_length: [u8; 3], pub block_length: [u8; 3],
} }
impl ModeBlockDescriptor { impl ModeBlockDescriptor {
pub fn block_length(&self) -> u32 { pub fn block_length(&self) -> u32 {
((self.block_length[0] as u32) << 16) + ((self.block_length[0] as u32) << 16)
((self.block_length[1] as u32) << 8) + + ((self.block_length[1] as u32) << 8)
(self.block_length[2] as u32) + (self.block_length[2] as u32)
} }
pub fn set_block_length(&mut self, length: u32) -> Result<(), Error> { pub fn set_block_length(&mut self, length: u32) -> Result<(), Error> {
@ -281,64 +278,36 @@ impl ModeBlockDescriptor {
} }
} }
pub const SCSI_PT_DO_START_OK:c_int = 0; pub const SCSI_PT_DO_START_OK: c_int = 0;
pub const SCSI_PT_DO_BAD_PARAMS:c_int = 1; pub const SCSI_PT_DO_BAD_PARAMS: c_int = 1;
pub const SCSI_PT_DO_TIMEOUT:c_int = 2; pub const SCSI_PT_DO_TIMEOUT: c_int = 2;
pub const SCSI_PT_RESULT_GOOD:c_int = 0; pub const SCSI_PT_RESULT_GOOD: c_int = 0;
pub const SCSI_PT_RESULT_STATUS:c_int = 1; pub const SCSI_PT_RESULT_STATUS: c_int = 1;
pub const SCSI_PT_RESULT_SENSE:c_int = 2; pub const SCSI_PT_RESULT_SENSE: c_int = 2;
pub const SCSI_PT_RESULT_TRANSPORT_ERR:c_int = 3; pub const SCSI_PT_RESULT_TRANSPORT_ERR: c_int = 3;
pub const SCSI_PT_RESULT_OS_ERR:c_int = 4; pub const SCSI_PT_RESULT_OS_ERR: c_int = 4;
#[link(name = "sgutils2")] #[link(name = "sgutils2")]
extern "C" { extern "C" {
#[allow(dead_code)] #[allow(dead_code)]
fn scsi_pt_open_device( fn scsi_pt_open_device(device_name: *const c_char, read_only: bool, verbose: c_int) -> c_int;
device_name: * const c_char,
read_only: bool,
verbose: c_int,
) -> c_int;
fn sg_is_scsi_cdb( fn sg_is_scsi_cdb(cdbp: *const u8, clen: c_int) -> bool;
cdbp: *const u8,
clen: c_int,
) -> bool;
fn construct_scsi_pt_obj() -> *mut SgPtBase; fn construct_scsi_pt_obj() -> *mut SgPtBase;
fn destruct_scsi_pt_obj(objp: *mut SgPtBase); fn destruct_scsi_pt_obj(objp: *mut SgPtBase);
fn set_scsi_pt_data_in( fn set_scsi_pt_data_in(objp: *mut SgPtBase, dxferp: *mut u8, dxfer_ilen: c_int);
objp: *mut SgPtBase,
dxferp: *mut u8,
dxfer_ilen: c_int,
);
fn set_scsi_pt_data_out( fn set_scsi_pt_data_out(objp: *mut SgPtBase, dxferp: *const u8, dxfer_olen: c_int);
objp: *mut SgPtBase,
dxferp: *const u8,
dxfer_olen: c_int,
);
fn set_scsi_pt_cdb( fn set_scsi_pt_cdb(objp: *mut SgPtBase, cdb: *const u8, cdb_len: c_int);
objp: *mut SgPtBase,
cdb: *const u8,
cdb_len: c_int,
);
fn set_scsi_pt_sense( fn set_scsi_pt_sense(objp: *mut SgPtBase, sense: *mut u8, max_sense_len: c_int);
objp: *mut SgPtBase,
sense: *mut u8,
max_sense_len: c_int,
);
fn do_scsi_pt( fn do_scsi_pt(objp: *mut SgPtBase, fd: c_int, timeout_secs: c_int, verbose: c_int) -> c_int;
objp: *mut SgPtBase,
fd: c_int,
timeout_secs: c_int,
verbose: c_int,
) -> c_int;
fn get_scsi_pt_resid(objp: *const SgPtBase) -> c_int; fn get_scsi_pt_resid(objp: *const SgPtBase) -> c_int;
@ -352,10 +321,10 @@ extern "C" {
fn sg_get_asc_ascq_str( fn sg_get_asc_ascq_str(
asc: c_int, asc: c_int,
ascq:c_int, ascq: c_int,
buff_len: c_int, buff_len: c_int,
buffer: *mut c_char, buffer: *mut c_char,
) -> * const c_char; ) -> *const c_char;
} }
/// Safe interface to run RAW SCSI commands /// Safe interface to run RAW SCSI commands
@ -368,31 +337,30 @@ pub struct SgRaw<'a, F> {
/// Get the string associated with ASC/ASCQ values /// Get the string associated with ASC/ASCQ values
pub fn get_asc_ascq_string(asc: u8, ascq: u8) -> String { pub fn get_asc_ascq_string(asc: u8, ascq: u8) -> String {
let mut buffer = [0u8; 1024]; let mut buffer = [0u8; 1024];
let res = unsafe { let res = unsafe {
sg_get_asc_ascq_str( sg_get_asc_ascq_str(
asc as c_int, asc as c_int,
ascq as c_int, ascq as c_int,
buffer.len() as c_int, buffer.len() as c_int,
buffer.as_mut_ptr() as * mut c_char, buffer.as_mut_ptr() as *mut c_char,
) )
}; };
proxmox_lang::try_block!({ proxmox_lang::try_block!({
if res.is_null() { // just to be safe if res.is_null() {
// just to be safe
bail!("unexpected NULL ptr"); bail!("unexpected NULL ptr");
} }
Ok(unsafe { CStr::from_ptr(res) }.to_str()?.to_owned()) Ok(unsafe { CStr::from_ptr(res) }.to_str()?.to_owned())
}).unwrap_or_else(|_err: Error| {
format!("ASC={:02x}x, ASCQ={:02x}x", asc, ascq)
}) })
.unwrap_or_else(|_err: Error| format!("ASC={:02x}x, ASCQ={:02x}x", asc, ascq))
} }
/// Allocate a page aligned buffer /// Allocate a page aligned buffer
/// ///
/// SG RAWIO commands needs page aligned transfer buffers. /// SG RAWIO commands needs page aligned transfer buffers.
pub fn alloc_page_aligned_buffer(buffer_size: usize) -> Result<Box<[u8]> , Error> { pub fn alloc_page_aligned_buffer(buffer_size: usize) -> Result<Box<[u8]>, Error> {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
let layout = std::alloc::Layout::from_size_align(buffer_size, page_size)?; let layout = std::alloc::Layout::from_size_align(buffer_size, page_size)?;
let dinp = unsafe { std::alloc::alloc_zeroed(layout) }; let dinp = unsafe { std::alloc::alloc_zeroed(layout) };
@ -400,17 +368,15 @@ pub fn alloc_page_aligned_buffer(buffer_size: usize) -> Result<Box<[u8]> , Error
bail!("alloc SCSI output buffer failed"); bail!("alloc SCSI output buffer failed");
} }
let buffer_slice = unsafe { std::slice::from_raw_parts_mut(dinp, buffer_size)}; let buffer_slice = unsafe { std::slice::from_raw_parts_mut(dinp, buffer_size) };
Ok(unsafe { Box::from_raw(buffer_slice) }) Ok(unsafe { Box::from_raw(buffer_slice) })
} }
impl <'a, F: AsRawFd> SgRaw<'a, F> { impl<'a, F: AsRawFd> SgRaw<'a, F> {
/// Create a new instance to run commands /// Create a new instance to run commands
/// ///
/// The file must be a handle to a SCSI device. /// The file must be a handle to a SCSI device.
pub fn new(file: &'a mut F, buffer_size: usize) -> Result<Self, Error> { pub fn new(file: &'a mut F, buffer_size: usize) -> Result<Self, Error> {
let buffer; let buffer;
if buffer_size > 0 { if buffer_size > 0 {
@ -421,7 +387,12 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
let sense_buffer = [0u8; 32]; let sense_buffer = [0u8; 32];
Ok(Self { file, buffer, sense_buffer, timeout: 0 }) Ok(Self {
file,
buffer,
sense_buffer,
timeout: 0,
})
} }
/// Set the command timeout in seconds (0 means default (60 seconds)) /// Set the command timeout in seconds (0 means default (60 seconds))
@ -435,7 +406,6 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
// create new object with initialized data_in and sense buffer // create new object with initialized data_in and sense buffer
fn create_scsi_pt_obj(&mut self) -> Result<SgPt, Error> { fn create_scsi_pt_obj(&mut self) -> Result<SgPt, Error> {
let mut ptvp = SgPt::new()?; let mut ptvp = SgPt::new()?;
if !self.buffer.is_empty() { if !self.buffer.is_empty() {
@ -460,18 +430,21 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
} }
fn do_scsi_pt_checked(&mut self, ptvp: &mut SgPt) -> Result<(), ScsiError> { fn do_scsi_pt_checked(&mut self, ptvp: &mut SgPt) -> Result<(), ScsiError> {
let res = unsafe { do_scsi_pt(ptvp.as_mut_ptr(), self.file.as_raw_fd(), self.timeout, 0) }; let res = unsafe { do_scsi_pt(ptvp.as_mut_ptr(), self.file.as_raw_fd(), self.timeout, 0) };
match res { match res {
SCSI_PT_DO_START_OK => { /* Ok */ }, SCSI_PT_DO_START_OK => { /* Ok */ }
SCSI_PT_DO_BAD_PARAMS => return Err(format_err!("do_scsi_pt failed - bad pass through setup").into()), SCSI_PT_DO_BAD_PARAMS => {
return Err(format_err!("do_scsi_pt failed - bad pass through setup").into())
}
SCSI_PT_DO_TIMEOUT => return Err(format_err!("do_scsi_pt failed - timeout").into()), SCSI_PT_DO_TIMEOUT => return Err(format_err!("do_scsi_pt failed - timeout").into()),
code if code < 0 => { code if code < 0 => {
let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) }; let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) };
let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno)); let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno));
return Err(format_err!("do_scsi_pt failed with err {}", err).into()); return Err(format_err!("do_scsi_pt failed with err {}", err).into());
} }
unknown => return Err(format_err!("do_scsi_pt failed: unknown error {}", unknown).into()), unknown => {
return Err(format_err!("do_scsi_pt failed: unknown error {}", unknown).into())
}
} }
if res < 0 { if res < 0 {
@ -490,7 +463,9 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
SCSI_PT_RESULT_STATUS => { SCSI_PT_RESULT_STATUS => {
let status = unsafe { get_scsi_pt_status_response(ptvp.as_ptr()) }; let status = unsafe { get_scsi_pt_status_response(ptvp.as_ptr()) };
if status != 0 { if status != 0 {
return Err(format_err!("unknown scsi error - status response {}", status).into()); return Err(
format_err!("unknown scsi error - status response {}", status).into(),
);
} }
Ok(()) Ok(())
} }
@ -521,28 +496,39 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
} }
} }
0x71 | 0x73 => { 0x71 | 0x73 => {
return Err(format_err!("scsi command failed: received deferred Sense").into()); return Err(
format_err!("scsi command failed: received deferred Sense").into()
);
} }
unknown => { unknown => {
return Err(format_err!("scsi command failed: invalid Sense response code {:x}", unknown).into()); return Err(format_err!(
"scsi command failed: invalid Sense response code {:x}",
unknown
)
.into());
} }
}; };
Err(ScsiError::Sense(sense)) Err(ScsiError::Sense(sense))
} }
SCSI_PT_RESULT_TRANSPORT_ERR => return Err(format_err!("scsi command failed: transport error").into()), SCSI_PT_RESULT_TRANSPORT_ERR => {
return Err(format_err!("scsi command failed: transport error").into())
}
SCSI_PT_RESULT_OS_ERR => { SCSI_PT_RESULT_OS_ERR => {
let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) }; let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) };
let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno)); let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno));
return Err(format_err!("scsi command failed with err {}", err).into()); return Err(format_err!("scsi command failed with err {}", err).into());
} }
unknown => return Err(format_err!("scsi command failed: unknown result category {}", unknown).into()), unknown => {
return Err(
format_err!("scsi command failed: unknown result category {}", unknown).into(),
)
}
} }
} }
/// Run the specified RAW SCSI command /// Run the specified RAW SCSI command
pub fn do_command(&mut self, cmd: &[u8]) -> Result<&[u8], ScsiError> { pub fn do_command(&mut self, cmd: &[u8]) -> Result<&[u8], ScsiError> {
if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } { if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
return Err(format_err!("no valid SCSI command").into()); return Err(format_err!("no valid SCSI command").into());
} }
@ -553,19 +539,15 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
let mut ptvp = self.create_scsi_pt_obj()?; let mut ptvp = self.create_scsi_pt_obj()?;
unsafe { unsafe { set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int) };
set_scsi_pt_cdb(
ptvp.as_mut_ptr(),
cmd.as_ptr(),
cmd.len() as c_int,
)
};
self.do_scsi_pt_checked(&mut ptvp)?; self.do_scsi_pt_checked(&mut ptvp)?;
let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize; let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize;
if resid > self.buffer.len() { if resid > self.buffer.len() {
return Err(format_err!("do_scsi_pt failed - got strange resid (value too big)").into()); return Err(
format_err!("do_scsi_pt failed - got strange resid (value too big)").into(),
);
} }
let data_len = self.buffer.len() - resid; let data_len = self.buffer.len() - resid;
@ -573,8 +555,11 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
} }
/// Run the specified RAW SCSI command, use data as input buffer /// Run the specified RAW SCSI command, use data as input buffer
pub fn do_in_command<'b>(&mut self, cmd: &[u8], data: &'b mut [u8]) -> Result<&'b [u8], ScsiError> { pub fn do_in_command<'b>(
&mut self,
cmd: &[u8],
data: &'b mut [u8],
) -> Result<&'b [u8], ScsiError> {
if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } { if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
return Err(format_err!("no valid SCSI command").into()); return Err(format_err!("no valid SCSI command").into());
} }
@ -586,17 +571,9 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
let mut ptvp = self.create_scsi_pt_obj()?; let mut ptvp = self.create_scsi_pt_obj()?;
unsafe { unsafe {
set_scsi_pt_data_in( set_scsi_pt_data_in(ptvp.as_mut_ptr(), data.as_mut_ptr(), data.len() as c_int);
ptvp.as_mut_ptr(),
data.as_mut_ptr(),
data.len() as c_int,
);
set_scsi_pt_cdb( set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int);
ptvp.as_mut_ptr(),
cmd.as_ptr(),
cmd.len() as c_int,
);
}; };
self.do_scsi_pt_checked(&mut ptvp)?; self.do_scsi_pt_checked(&mut ptvp)?;
@ -604,7 +581,9 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize; let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize;
if resid > data.len() { if resid > data.len() {
return Err(format_err!("do_scsi_pt failed - got strange resid (value too big)").into()); return Err(
format_err!("do_scsi_pt failed - got strange resid (value too big)").into(),
);
} }
let data_len = data.len() - resid; let data_len = data.len() - resid;
@ -615,30 +594,21 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
/// ///
/// Note: use alloc_page_aligned_buffer to alloc data transfer buffer /// Note: use alloc_page_aligned_buffer to alloc data transfer buffer
pub fn do_out_command(&mut self, cmd: &[u8], data: &[u8]) -> Result<(), ScsiError> { pub fn do_out_command(&mut self, cmd: &[u8], data: &[u8]) -> Result<(), ScsiError> {
if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } { if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
return Err(format_err!("no valid SCSI command").into()); return Err(format_err!("no valid SCSI command").into());
} }
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
if ((data.as_ptr() as usize) & (page_size -1)) != 0 { if ((data.as_ptr() as usize) & (page_size - 1)) != 0 {
return Err(format_err!("wrong transfer buffer alignment").into()); return Err(format_err!("wrong transfer buffer alignment").into());
} }
let mut ptvp = self.create_scsi_pt_obj()?; let mut ptvp = self.create_scsi_pt_obj()?;
unsafe { unsafe {
set_scsi_pt_data_out( set_scsi_pt_data_out(ptvp.as_mut_ptr(), data.as_ptr(), data.len() as c_int);
ptvp.as_mut_ptr(),
data.as_ptr(),
data.len() as c_int,
);
set_scsi_pt_cdb( set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int);
ptvp.as_mut_ptr(),
cmd.as_ptr(),
cmd.len() as c_int,
);
}; };
self.do_scsi_pt_checked(&mut ptvp)?; self.do_scsi_pt_checked(&mut ptvp)?;
@ -660,10 +630,7 @@ pub fn scsi_ascii_to_string(data: &[u8]) -> String {
/// Read SCSI Inquiry page /// Read SCSI Inquiry page
/// ///
/// Returns Product/Vendor/Revision and device type. /// Returns Product/Vendor/Revision and device type.
pub fn scsi_inquiry<F: AsRawFd>( pub fn scsi_inquiry<F: AsRawFd>(file: &mut F) -> Result<InquiryInfo, Error> {
file: &mut F,
) -> Result<InquiryInfo, Error> {
let allocation_len: u8 = std::mem::size_of::<InquiryPage>() as u8; let allocation_len: u8 = std::mem::size_of::<InquiryPage>() as u8;
let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
@ -672,7 +639,8 @@ pub fn scsi_inquiry<F: AsRawFd>(
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.extend(&[0x12, 0, 0, 0, allocation_len, 0]); // INQUIRY cmd.extend(&[0x12, 0, 0, 0, allocation_len, 0]); // INQUIRY
let data = sg_raw.do_command(&cmd) let data = sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("SCSI inquiry failed - {}", err))?; .map_err(|err| format_err!("SCSI inquiry failed - {}", err))?;
proxmox_lang::try_block!({ proxmox_lang::try_block!({
@ -691,7 +659,8 @@ pub fn scsi_inquiry<F: AsRawFd>(
}; };
Ok(info) Ok(info)
}).map_err(|err: Error| format_err!("decode inquiry page failed - {}", err)) })
.map_err(|err: Error| format_err!("decode inquiry page failed - {}", err))
} }
/// Run SCSI Mode Sense /// Run SCSI Mode Sense
@ -703,7 +672,6 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
page_code: u8, page_code: u8,
sub_page_code: u8, sub_page_code: u8,
) -> Result<(ModeParameterHeader, Option<ModeBlockDescriptor>, P), Error> { ) -> Result<(ModeParameterHeader, Option<ModeBlockDescriptor>, P), Error> {
let allocation_len: u16 = 4096; let allocation_len: u16 = 4096;
let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
@ -721,7 +689,8 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
cmd.extend(&allocation_len.to_be_bytes()); // allocation len cmd.extend(&allocation_len.to_be_bytes()); // allocation len
cmd.push(0); //control cmd.push(0); //control
let data = sg_raw.do_command(&cmd) let data = sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("mode sense failed - {}", err))?; .map_err(|err| format_err!("mode sense failed - {}", err))?;
proxmox_lang::try_block!({ proxmox_lang::try_block!({
@ -731,7 +700,11 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
let expected_len = head.mode_data_len as usize + 2; let expected_len = head.mode_data_len as usize + 2;
if data.len() < expected_len { if data.len() < expected_len {
bail!("wrong mode_data_len: got {}, expected {}", data.len(), expected_len); bail!(
"wrong mode_data_len: got {}, expected {}",
data.len(),
expected_len
);
} else if data.len() > expected_len { } else if data.len() > expected_len {
// Note: Some hh7 drives returns the allocation_length // Note: Some hh7 drives returns the allocation_length
// instead of real data_len // instead of real data_len
@ -758,14 +731,12 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
let page: P = unsafe { reader.read_be_value()? }; let page: P = unsafe { reader.read_be_value()? };
Ok((head, block_descriptor, page)) Ok((head, block_descriptor, page))
}).map_err(|err: Error| format_err!("decode mode sense failed - {}", err)) })
.map_err(|err: Error| format_err!("decode mode sense failed - {}", err))
} }
/// Resuqest Sense /// Resuqest Sense
pub fn scsi_request_sense<F: AsRawFd>( pub fn scsi_request_sense<F: AsRawFd>(file: &mut F) -> Result<RequestSenseFixed, ScsiError> {
file: &mut F,
) -> Result<RequestSenseFixed, ScsiError> {
// request 252 bytes, as mentioned in the Seagate SCSI reference // request 252 bytes, as mentioned in the Seagate SCSI reference
let allocation_len: u8 = 252; let allocation_len: u8 = 252;
@ -774,7 +745,8 @@ pub fn scsi_request_sense<F: AsRawFd>(
let mut cmd = Vec::new(); let mut cmd = Vec::new();
cmd.extend(&[0x03, 0, 0, 0, allocation_len, 0]); // REQUEST SENSE FIXED FORMAT cmd.extend(&[0x03, 0, 0, 0, allocation_len, 0]); // REQUEST SENSE FIXED FORMAT
let data = sg_raw.do_command(&cmd) let data = sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("request sense failed - {}", err))?; .map_err(|err| format_err!("request sense failed - {}", err))?;
let sense = proxmox_lang::try_block!({ let sense = proxmox_lang::try_block!({
@ -793,7 +765,8 @@ pub fn scsi_request_sense<F: AsRawFd>(
let sense: RequestSenseFixed = unsafe { reader.read_be_value()? }; let sense: RequestSenseFixed = unsafe { reader.read_be_value()? };
Ok(sense) Ok(sense)
}).map_err(|err: Error| format_err!("decode request sense failed - {}", err))?; })
.map_err(|err: Error| format_err!("decode request sense failed - {}", err))?;
Ok(sense) Ok(sense)
} }

View File

@ -38,12 +38,16 @@ pub trait TapeWrite {
} }
let header = header.to_le(); let header = header.to_le();
let res = self.write_all(unsafe { std::slice::from_raw_parts( let res = self.write_all(unsafe {
std::slice::from_raw_parts(
&header as *const MediaContentHeader as *const u8, &header as *const MediaContentHeader as *const u8,
std::mem::size_of::<MediaContentHeader>(), std::mem::size_of::<MediaContentHeader>(),
)})?; )
})?;
if data.is_empty() { return Ok(res); } if data.is_empty() {
return Ok(res);
}
self.write_all(data) self.write_all(data)
} }