pbs tape: rust fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
		| @ -12,54 +12,46 @@ | ||||
| /// - support tape alert flags | ||||
| /// - support volume statistics | ||||
| /// - read cartridge memory | ||||
|  | ||||
| use std::convert::TryInto; | ||||
|  | ||||
| use anyhow::{bail, Error}; | ||||
| use serde_json::Value; | ||||
|  | ||||
| use proxmox_schema::{api, ArraySchema, IntegerSchema, Schema, StringSchema}; | ||||
| use proxmox_router::cli::*; | ||||
| use proxmox_router::RpcEnvironment; | ||||
| use proxmox_schema::{api, ArraySchema, IntegerSchema, Schema, StringSchema}; | ||||
|  | ||||
| use pbs_api_types::{ | ||||
|     LTO_DRIVE_PATH_SCHEMA, DRIVE_NAME_SCHEMA, LtoTapeDrive, | ||||
| }; | ||||
| use pbs_api_types::{LtoTapeDrive, DRIVE_NAME_SCHEMA, LTO_DRIVE_PATH_SCHEMA}; | ||||
| use pbs_config::drive::complete_drive_name; | ||||
| use pbs_tape::{ | ||||
|     sg_tape::SgTape, | ||||
|     linux_list_drives::{complete_drive_path, lto_tape_device_list, open_lto_tape_device}, | ||||
|     sg_tape::SgTape, | ||||
| }; | ||||
|  | ||||
| pub const FILE_MARK_COUNT_SCHEMA: Schema = | ||||
|     IntegerSchema::new("File mark count.") | ||||
| pub const FILE_MARK_COUNT_SCHEMA: Schema = IntegerSchema::new("File mark count.") | ||||
|     .minimum(1) | ||||
|     .maximum(i32::MAX as isize) | ||||
|     .schema(); | ||||
|  | ||||
| pub const FILE_MARK_POSITION_SCHEMA: Schema = | ||||
|     IntegerSchema::new("File mark position (0 is BOT).") | ||||
| pub const FILE_MARK_POSITION_SCHEMA: Schema = IntegerSchema::new("File mark position (0 is BOT).") | ||||
|     .minimum(0) | ||||
|     .maximum(i32::MAX as isize) | ||||
|     .schema(); | ||||
|  | ||||
| pub const RECORD_COUNT_SCHEMA: Schema = | ||||
|     IntegerSchema::new("Record count.") | ||||
| pub const RECORD_COUNT_SCHEMA: Schema = IntegerSchema::new("Record count.") | ||||
|     .minimum(1) | ||||
|     .maximum(i32::MAX as isize) | ||||
|     .schema(); | ||||
|  | ||||
| pub const DRIVE_OPTION_SCHEMA: Schema = StringSchema::new( | ||||
|     "Lto Tape Driver Option, either numeric value or option name.") | ||||
|     .schema(); | ||||
| pub const DRIVE_OPTION_SCHEMA: Schema = | ||||
|     StringSchema::new("Lto Tape Driver Option, either numeric value or option name.").schema(); | ||||
|  | ||||
| pub const DRIVE_OPTION_LIST_SCHEMA: Schema = | ||||
|     ArraySchema::new("Drive Option List.", &DRIVE_OPTION_SCHEMA) | ||||
|     .min_length(1) | ||||
|     .schema(); | ||||
|         .min_length(1) | ||||
|         .schema(); | ||||
|  | ||||
| fn get_tape_handle(param: &Value) -> Result<SgTape, Error> { | ||||
|  | ||||
|     if let Some(name) = param["drive"].as_str() { | ||||
|         let (config, _digest) = pbs_config::drive::config()?; | ||||
|         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(); | ||||
|     for (name, (section_type, _)) in config.sections.iter() { | ||||
|         if section_type != "lto" { continue; } | ||||
|         if section_type != "lto" { | ||||
|             continue; | ||||
|         } | ||||
|         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 | ||||
| /// filemark count) | ||||
| fn asf(count: u64, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     handle.locate_file(count)?; | ||||
| @ -130,7 +123,6 @@ fn asf(count: u64, param: Value) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         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. | ||||
| fn bsf(count: usize, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     handle.space_filemarks(-count.try_into()?)?; | ||||
| @ -160,7 +151,6 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         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 | ||||
| /// that is count - 1 files before the current file. | ||||
| fn bsfm(count: usize, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     handle.space_filemarks(-count.try_into()?)?; | ||||
| @ -192,7 +181,6 @@ fn bsfm(count: usize, param: Value) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -212,7 +200,6 @@ fn bsfm(count: usize, param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Backward space records. | ||||
| fn bsr(count: usize, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     handle.space_blocks(-count.try_into()?)?; | ||||
| @ -220,7 +207,6 @@ fn bsr(count: usize, param: Value) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -241,7 +227,6 @@ fn bsr(count: usize, param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Read Cartridge Memory | ||||
| fn cartridge_memory(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let output_format = get_output_format(¶m); | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
| @ -292,11 +277,11 @@ fn cartridge_memory(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Read Tape Alert Flags | ||||
| fn tape_alert_flags(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let output_format = get_output_format(¶m); | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|     let result = handle.tape_alert_flags() | ||||
|     let result = handle | ||||
|         .tape_alert_flags() | ||||
|         .map(|flags| format!("{:?}", flags)); | ||||
|  | ||||
|     if output_format == "json-pretty" { | ||||
| @ -337,14 +322,12 @@ fn tape_alert_flags(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Eject drive media | ||||
| fn eject(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|     handle.eject()?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -361,14 +344,12 @@ fn eject(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Move to end of media | ||||
| fn eod(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|     handle.move_to_eom(false)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -391,7 +372,6 @@ fn eod(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Erase media (from current position) | ||||
| fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|     handle.erase_media(fast.unwrap_or(true))?; | ||||
|  | ||||
| @ -420,7 +400,6 @@ fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Format media,  single partition | ||||
| fn format(fast: Option<bool>, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|     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. | ||||
| fn fsf(count: usize, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     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 | ||||
| /// is count - 1 files past the current file. | ||||
| fn fsfm(count: usize, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     handle.space_filemarks(count.try_into()?)?; | ||||
| @ -487,7 +464,6 @@ fn fsfm(count: usize, param: Value) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -507,7 +483,6 @@ fn fsfm(count: usize, param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Forward space records. | ||||
| fn fsr(count: usize, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     handle.space_blocks(count.try_into()?)?; | ||||
| @ -515,7 +490,6 @@ fn fsr(count: usize, param: Value) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -532,14 +506,12 @@ fn fsr(count: usize, param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Load media | ||||
| fn load(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|     handle.load()?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -556,7 +528,6 @@ fn load(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Lock the tape drive door | ||||
| fn lock(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     handle.set_medium_removal(false)?; | ||||
| @ -564,7 +535,6 @@ fn lock(param: Value) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -581,14 +551,12 @@ fn lock(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Rewind the tape | ||||
| fn rewind(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|     handle.rewind()?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -601,7 +569,6 @@ fn rewind(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Scan for existing tape changer devices | ||||
| fn scan(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let output_format = get_output_format(¶m); | ||||
|  | ||||
|     let list = lto_tape_device_list(); | ||||
| @ -621,7 +588,10 @@ fn scan(param: Value) -> Result<(), Error> { | ||||
|     } | ||||
|  | ||||
|     for item in list.iter() { | ||||
|         println!("{} ({}/{}/{})", item.path, item.vendor, item.model, item.serial); | ||||
|         println!( | ||||
|             "{} ({}/{}/{})", | ||||
|             item.path, item.vendor, item.model, item.serial | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| @ -647,7 +617,6 @@ fn scan(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Drive Status | ||||
| fn status(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let output_format = get_output_format(¶m); | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
| @ -677,7 +646,6 @@ fn status(param: Value) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|    input: { | ||||
|         properties: { | ||||
| @ -694,7 +662,6 @@ fn status(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Unlock the tape drive door | ||||
| fn unlock(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     handle.set_medium_removal(true)?; | ||||
| @ -702,7 +669,6 @@ fn unlock(param: Value) -> Result<(), Error> { | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|     input: { | ||||
|         properties: { | ||||
| @ -723,7 +689,6 @@ fn unlock(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Volume Statistics | ||||
| fn volume_statistics(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let output_format = get_output_format(¶m); | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
| @ -772,7 +737,6 @@ fn volume_statistics(param: Value) -> Result<(), Error> { | ||||
| )] | ||||
| /// Write count (default 1) EOF marks at current position. | ||||
| fn weof(count: Option<usize>, param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let count = count.unwrap_or(1); | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
| @ -825,7 +789,6 @@ fn options( | ||||
|     defaults: Option<bool>, | ||||
|     param: Value, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     let mut handle = get_tape_handle(¶m)?; | ||||
|  | ||||
|     if let Some(true) = defaults { | ||||
| @ -838,7 +801,6 @@ fn options( | ||||
| } | ||||
|  | ||||
| fn main() -> Result<(), Error> { | ||||
|  | ||||
|     let uid = nix::unistd::Uid::current(); | ||||
|  | ||||
|     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("unlock", std_cmd(&API_METHOD_UNLOCK)) | ||||
|         .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(); | ||||
|     rpcenv.set_auth_id(Some(format!("{}@pam", username))); | ||||
|  | ||||
| @ -11,29 +11,25 @@ | ||||
| /// | ||||
| /// - list serial number for attached drives, so that it is possible | ||||
| ///   to associate drive numbers with drives. | ||||
|  | ||||
| use std::fs::File; | ||||
|  | ||||
| use anyhow::{bail, Error}; | ||||
| use serde_json::Value; | ||||
|  | ||||
| use proxmox_schema::api; | ||||
| use proxmox_router::cli::*; | ||||
| 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_api_types::{ | ||||
|     SCSI_CHANGER_PATH_SCHEMA, CHANGER_NAME_SCHEMA, ScsiTapeChanger, LtoTapeDrive, | ||||
| }; | ||||
| use pbs_tape::{ | ||||
|     linux_list_drives::{complete_changer_path, linux_tape_changer_list}, | ||||
|     sg_pt_changer, | ||||
|     sgutils2::scsi_inquiry, | ||||
|     ElementStatus, | ||||
|     sg_pt_changer, | ||||
|     linux_list_drives::{complete_changer_path, linux_tape_changer_list}, | ||||
| }; | ||||
|  | ||||
| fn get_changer_handle(param: &Value) -> Result<File, Error> { | ||||
|  | ||||
|     if let Some(name) = param["changer"].as_str() { | ||||
|         let (config, _digest) = pbs_config::drive::config()?; | ||||
|         let changer_config: ScsiTapeChanger = config.lookup("changer", name)?; | ||||
| @ -83,10 +79,7 @@ fn get_changer_handle(param: &Value) -> Result<File, Error> { | ||||
|     }, | ||||
| )] | ||||
| /// Inquiry | ||||
| fn inquiry( | ||||
|     param: Value, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
| fn inquiry(param: Value) -> Result<(), Error> { | ||||
|     let output_format = get_output_format(¶m); | ||||
|  | ||||
|     let result: Result<_, Error> = proxmox_lang::try_block!({ | ||||
| @ -113,7 +106,10 @@ fn inquiry( | ||||
|  | ||||
|     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!("Product:  {}", info.product); | ||||
|     println!("Revision: {}", info.revision); | ||||
| @ -136,10 +132,7 @@ fn inquiry( | ||||
|     }, | ||||
| )] | ||||
| /// Inventory | ||||
| fn inventory( | ||||
|     param: Value, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
| fn inventory(param: Value) -> Result<(), Error> { | ||||
|     let mut file = get_changer_handle(¶m)?; | ||||
|     sg_pt_changer::initialize_element_status(&mut file)?; | ||||
|  | ||||
| @ -170,12 +163,7 @@ fn inventory( | ||||
|     }, | ||||
| )] | ||||
| /// Load | ||||
| fn load( | ||||
|     param: Value, | ||||
|     slot: u64, | ||||
|     drivenum: Option<u64>, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
| fn load(param: Value, slot: u64, drivenum: Option<u64>) -> Result<(), Error> { | ||||
|     let mut file = get_changer_handle(¶m)?; | ||||
|  | ||||
|     let drivenum = drivenum.unwrap_or(0); | ||||
| @ -210,12 +198,7 @@ fn load( | ||||
|     }, | ||||
| )] | ||||
| /// Unload | ||||
| fn unload( | ||||
|     param: Value, | ||||
|     slot: Option<u64>, | ||||
|     drivenum: Option<u64>, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
| fn unload(param: Value, slot: Option<u64>, drivenum: Option<u64>) -> Result<(), Error> { | ||||
|     let mut file = get_changer_handle(¶m)?; | ||||
|  | ||||
|     let drivenum = drivenum.unwrap_or(0); | ||||
| @ -271,10 +254,7 @@ fn unload( | ||||
|     }, | ||||
| )] | ||||
| /// Changer Status | ||||
| fn status( | ||||
|     param: Value, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
| fn status(param: Value) -> Result<(), Error> { | ||||
|     let output_format = get_output_format(¶m); | ||||
|  | ||||
|     let result: Result<_, Error> = proxmox_lang::try_block!({ | ||||
| @ -302,7 +282,10 @@ fn status( | ||||
|     let status = result?; | ||||
|  | ||||
|     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() { | ||||
| @ -312,7 +295,7 @@ fn status( | ||||
|         }; | ||||
|         let serial_txt = match drive.drive_serial_number { | ||||
|             Some(ref serial) => format!(", Serial: {}", serial), | ||||
|              None => String::new(), | ||||
|             None => String::new(), | ||||
|         }; | ||||
|  | ||||
|         println!( | ||||
| @ -323,9 +306,9 @@ fn status( | ||||
|  | ||||
|     for (i, slot) in status.slots.iter().enumerate() { | ||||
|         if slot.import_export { | ||||
|             println!("  Import/Export   {:>3}: {:?}", i+1, slot.status); | ||||
|             println!("  Import/Export   {:>3}: {:?}", i + 1, slot.status); | ||||
|         } else { | ||||
|             println!("  Storage Element {:>3}: {:?}", i+1, slot.status); | ||||
|             println!("  Storage Element {:>3}: {:?}", i + 1, slot.status); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -355,12 +338,7 @@ fn status( | ||||
|     }, | ||||
| )] | ||||
| /// Transfer | ||||
| fn transfer( | ||||
|     param: Value, | ||||
|     from: u64, | ||||
|     to: u64, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
| fn transfer(param: Value, from: u64, to: u64) -> Result<(), Error> { | ||||
|     let mut file = get_changer_handle(¶m)?; | ||||
|  | ||||
|     sg_pt_changer::transfer_medium(&mut file, from, to)?; | ||||
| @ -380,7 +358,6 @@ fn transfer( | ||||
| )] | ||||
| /// Scan for existing tape changer devices | ||||
| fn scan(param: Value) -> Result<(), Error> { | ||||
|  | ||||
|     let output_format = get_output_format(¶m); | ||||
|  | ||||
|     let list = linux_tape_changer_list(); | ||||
| @ -400,14 +377,16 @@ fn scan(param: Value) -> Result<(), Error> { | ||||
|     } | ||||
|  | ||||
|     for item in list.iter() { | ||||
|         println!("{} ({}/{}/{})", item.path, item.vendor, item.model, item.serial); | ||||
|         println!( | ||||
|             "{} ({}/{}/{})", | ||||
|             item.path, item.vendor, item.model, item.serial | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn main() -> Result<(), Error> { | ||||
|  | ||||
|     let uid = nix::unistd::Uid::current(); | ||||
|  | ||||
|     let username = match nix::unistd::User::from_uid(uid)? { | ||||
| @ -415,49 +394,47 @@ fn main() -> Result<(), Error> { | ||||
|         None => bail!("unable to get user name"), | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     let cmd_def = CliCommandMap::new() | ||||
|         .usage_skip_options(&["device", "changer", "output-format"]) | ||||
|         .insert( | ||||
|             "inquiry", | ||||
|             CliCommand::new(&API_METHOD_INQUIRY) | ||||
|                 .completion_cb("changer", complete_changer_name) | ||||
|                 .completion_cb("device", complete_changer_path) | ||||
|                 .completion_cb("device", complete_changer_path), | ||||
|         ) | ||||
|         .insert( | ||||
|             "inventory", | ||||
|             CliCommand::new(&API_METHOD_INVENTORY) | ||||
|                 .completion_cb("changer", complete_changer_name) | ||||
|                 .completion_cb("device", complete_changer_path) | ||||
|                 .completion_cb("device", complete_changer_path), | ||||
|         ) | ||||
|         .insert( | ||||
|             "load", | ||||
|             CliCommand::new(&API_METHOD_LOAD) | ||||
|                 .arg_param(&["slot"]) | ||||
|                 .completion_cb("changer", complete_changer_name) | ||||
|                 .completion_cb("device", complete_changer_path) | ||||
|                 .completion_cb("device", complete_changer_path), | ||||
|         ) | ||||
|         .insert( | ||||
|             "unload", | ||||
|             CliCommand::new(&API_METHOD_UNLOAD) | ||||
|                 .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( | ||||
|             "status", | ||||
|             CliCommand::new(&API_METHOD_STATUS) | ||||
|                 .completion_cb("changer", complete_changer_name) | ||||
|                 .completion_cb("device", complete_changer_path) | ||||
|                 .completion_cb("device", complete_changer_path), | ||||
|         ) | ||||
|         .insert( | ||||
|             "transfer", | ||||
|             CliCommand::new(&API_METHOD_TRANSFER) | ||||
|                 .arg_param(&["from", "to"]) | ||||
|                 .completion_cb("changer", complete_changer_name) | ||||
|                 .completion_cb("device", complete_changer_path) | ||||
|         ) | ||||
|          ; | ||||
|                 .completion_cb("device", complete_changer_path), | ||||
|         ); | ||||
|  | ||||
|     let mut rpcenv = CliEnvironment::new(); | ||||
|     rpcenv.set_auth_id(Some(format!("{}@pam", username))); | ||||
|  | ||||
| @ -1,12 +1,8 @@ | ||||
| use std::io::Read; | ||||
|  | ||||
| use crate::{ | ||||
|     TapeRead, | ||||
|     BlockRead, | ||||
|     BlockReadError, | ||||
|     BlockHeader, BlockHeaderFlags, BlockRead, BlockReadError, TapeRead, | ||||
|     PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0, | ||||
|     BlockHeader, | ||||
|     BlockHeaderFlags, | ||||
| }; | ||||
|  | ||||
| /// Read a block stream generated by 'BlockWriter'. | ||||
| @ -31,14 +27,12 @@ pub struct BlockedReader<R> { | ||||
|     read_pos: usize, | ||||
| } | ||||
|  | ||||
| impl <R: BlockRead> BlockedReader<R> { | ||||
|  | ||||
| impl<R: BlockRead> BlockedReader<R> { | ||||
|     /// Create a new BlockedReader instance. | ||||
|     /// | ||||
|     /// This tries to read the first block. Please inspect the error | ||||
|     /// to detect EOF and EOT. | ||||
|     pub fn open(mut reader: R) -> Result<Self, BlockReadError> { | ||||
|  | ||||
|         let mut buffer = BlockHeader::new(); | ||||
|  | ||||
|         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> { | ||||
|  | ||||
|         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() { | ||||
|             proxmox_lang::io_bail!( | ||||
|                 "detected tape block with wrong sequence number ({} != {})", | ||||
|                 seq_nr, buffer.seq_nr()) | ||||
|                 seq_nr, | ||||
|                 buffer.seq_nr() | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         let size = buffer.size(); | ||||
|         let found_end_marker = buffer.flags.contains(BlockHeaderFlags::END_OF_STREAM); | ||||
|  | ||||
|         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 { | ||||
|             proxmox_lang::io_bail!("detected tape block with zero payload size"); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         Ok((size, found_end_marker)) | ||||
|     } | ||||
|  | ||||
|     fn read_block_frame(buffer: &mut BlockHeader, reader: &mut R) -> Result<(), BlockReadError> { | ||||
|  | ||||
|         let data = unsafe { | ||||
|             std::slice::from_raw_parts_mut( | ||||
|                 (buffer as *mut BlockHeader) as *mut u8, | ||||
| @ -115,20 +114,15 @@ impl <R: BlockRead> BlockedReader<R> { | ||||
|             Ok(_) => { | ||||
|                 proxmox_lang::io_bail!("detected tape block after block-stream end marker"); | ||||
|             } | ||||
|             Err(BlockReadError::EndOfFile) => { | ||||
|                 Ok(()) | ||||
|             } | ||||
|             Err(BlockReadError::EndOfFile) => Ok(()), | ||||
|             Err(BlockReadError::EndOfStream) => { | ||||
|                 proxmox_lang::io_bail!("got unexpected end of tape"); | ||||
|             } | ||||
|             Err(BlockReadError::Error(err)) => { | ||||
|                 Err(err) | ||||
|             } | ||||
|             Err(BlockReadError::Error(err)) => Err(err), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|             Ok(()) => { /* ok */ } | ||||
|             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)?; | ||||
|         self.seq_nr += 1; | ||||
|  | ||||
|         if found_end_marker { // consume EOF mark | ||||
|         if found_end_marker { | ||||
|             // consume EOF mark | ||||
|             self.found_end_marker = true; | ||||
|             self.incomplete = self.buffer.flags.contains(BlockHeaderFlags::INCOMPLETE); | ||||
|             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> { | ||||
|         if !self.got_eod { | ||||
|             proxmox_lang::io_bail!("is_incomplete failed: EOD not reached"); | ||||
| @ -202,18 +196,17 @@ 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> { | ||||
|  | ||||
|          if self.read_error { | ||||
|         if self.read_error { | ||||
|             proxmox_lang::io_bail!("detected read after error - internal error"); | ||||
|         } | ||||
|  | ||||
|         let mut buffer_size = self.buffer.size(); | ||||
|         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) { | ||||
|                 Ok(len) => len, | ||||
|                 err => { | ||||
| @ -232,8 +225,8 @@ impl <R: BlockRead> Read for BlockedReader<R> { | ||||
|             } else { | ||||
|                 rest as usize | ||||
|             }; | ||||
|             buffer[..copy_len].copy_from_slice( | ||||
|                 &self.buffer.payload[self.read_pos..(self.read_pos + copy_len)]); | ||||
|             buffer[..copy_len] | ||||
|                 .copy_from_slice(&self.buffer.payload[self.read_pos..(self.read_pos + copy_len)]); | ||||
|             self.read_pos += copy_len; | ||||
|             Ok(copy_len) | ||||
|         } | ||||
| @ -242,24 +235,18 @@ impl <R: BlockRead> Read for BlockedReader<R> { | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use std::io::Read; | ||||
|     use anyhow::{bail, Error}; | ||||
|     use crate::{ | ||||
|         TapeWrite, | ||||
|         BlockReadError, | ||||
|         EmulateTapeReader, | ||||
|         EmulateTapeWriter, | ||||
|         PROXMOX_TAPE_BLOCK_SIZE, | ||||
|         BlockedReader, | ||||
|         BlockedWriter, | ||||
|         BlockReadError, BlockedReader, BlockedWriter, EmulateTapeReader, EmulateTapeWriter, | ||||
|         TapeWrite, PROXMOX_TAPE_BLOCK_SIZE, | ||||
|     }; | ||||
|     use anyhow::{bail, Error}; | ||||
|     use std::io::Read; | ||||
|  | ||||
|     fn write_and_verify(data: &[u8]) -> Result<(), Error> { | ||||
|  | ||||
|         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); | ||||
|  | ||||
|             writer.write_all(data)?; | ||||
| @ -269,8 +256,8 @@ mod test { | ||||
|  | ||||
|         assert_eq!( | ||||
|             tape_data.len(), | ||||
|             ((data.len() + PROXMOX_TAPE_BLOCK_SIZE)/PROXMOX_TAPE_BLOCK_SIZE) | ||||
|                 *PROXMOX_TAPE_BLOCK_SIZE | ||||
|             ((data.len() + PROXMOX_TAPE_BLOCK_SIZE) / PROXMOX_TAPE_BLOCK_SIZE) | ||||
|                 * PROXMOX_TAPE_BLOCK_SIZE | ||||
|         ); | ||||
|  | ||||
|         let reader = &mut &tape_data[..]; | ||||
| @ -299,7 +286,7 @@ mod test { | ||||
|  | ||||
|     #[test] | ||||
|     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) | ||||
|     } | ||||
|  | ||||
| @ -309,7 +296,7 @@ mod test { | ||||
|         let reader = &mut &tape_data[..]; | ||||
|         let reader = EmulateTapeReader::new(reader); | ||||
|         match BlockedReader::open(reader) { | ||||
|             Err(BlockReadError::EndOfFile) => { /* OK */ }, | ||||
|             Err(BlockReadError::EndOfFile) => { /* OK */ } | ||||
|             _ => bail!("expected EOF"), | ||||
|         } | ||||
|  | ||||
| @ -320,7 +307,7 @@ mod test { | ||||
|     fn no_end_marker() -> Result<(), Error> { | ||||
|         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); | ||||
|             // write at least one block | ||||
|             let data = proxmox_sys::linux::random_data(PROXMOX_TAPE_BLOCK_SIZE)?; | ||||
| @ -343,7 +330,7 @@ mod test { | ||||
|         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); | ||||
|  | ||||
|             writer.write_all(b"ABC")?; | ||||
|  | ||||
| @ -1,11 +1,6 @@ | ||||
| use proxmox_io::vec; | ||||
|  | ||||
| use crate::{ | ||||
|     TapeWrite, | ||||
|     BlockWrite, | ||||
|     BlockHeader, | ||||
|     BlockHeaderFlags, | ||||
| }; | ||||
| use crate::{BlockHeader, BlockHeaderFlags, BlockWrite, TapeWrite}; | ||||
|  | ||||
| /// Assemble and write blocks of data | ||||
| /// | ||||
| @ -22,8 +17,7 @@ pub struct BlockedWriter<W: BlockWrite> { | ||||
|     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 | ||||
|     fn drop(&mut self) { | ||||
|         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 | ||||
|     pub fn writer_ref_mut(&mut self) -> &mut W { | ||||
|         &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> { | ||||
|  | ||||
|         let data = unsafe { | ||||
|             std::slice::from_raw_parts( | ||||
|                 (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> { | ||||
|  | ||||
|         if data.is_empty() { return Ok(0); } | ||||
|         if data.is_empty() { | ||||
|             return Ok(0); | ||||
|         } | ||||
|  | ||||
|         let rest = self.buffer.payload.len() - self.buffer_pos; | ||||
|         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]); | ||||
|  | ||||
|         let rest = rest - bytes; | ||||
| @ -89,21 +82,20 @@ impl <W: BlockWrite> BlockedWriter<W> { | ||||
|             self.buffer.set_seq_nr(self.seq_nr); | ||||
|             self.seq_nr += 1; | ||||
|             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.bytes_written += BlockHeader::SIZE; | ||||
|  | ||||
|         } else { | ||||
|             self.buffer_pos += 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> { | ||||
|         while !data.is_empty() { | ||||
|             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> { | ||||
|         vec::clear(&mut self.buffer.payload[self.buffer_pos..]); | ||||
|         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_seq_nr(self.seq_nr); | ||||
|         self.seq_nr += 1; | ||||
| @ -139,5 +133,4 @@ impl <W: BlockWrite> TapeWrite for BlockedWriter<W> { | ||||
|     fn logical_end_of_media(&self) -> bool { | ||||
|         self.logical_end_of_media | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -12,17 +12,21 @@ pub struct EmulateTapeReader<R: Read> { | ||||
|     got_eof: bool, | ||||
| } | ||||
|  | ||||
| impl <R: Read> EmulateTapeReader<R> { | ||||
|  | ||||
| impl<R: Read> EmulateTapeReader<R> { | ||||
|     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> { | ||||
|         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)? { | ||||
|             false => { | ||||
| @ -32,13 +36,11 @@ impl <R: Read> BlockRead for EmulateTapeReader<R> { | ||||
|             true => { | ||||
|                 // test buffer len after EOF test (to allow EOF test with small buffers in BufferedReader) | ||||
|                 if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE { | ||||
|                     return Err(BlockReadError::Error( | ||||
|                         proxmox_lang::io_format_err!( | ||||
|                             "EmulateTapeReader: read_block with wrong block size ({} != {})", | ||||
|                             buffer.len(), | ||||
|                             PROXMOX_TAPE_BLOCK_SIZE, | ||||
|                         ) | ||||
|                     )); | ||||
|                     return Err(BlockReadError::Error(proxmox_lang::io_format_err!( | ||||
|                         "EmulateTapeReader: read_block with wrong block size ({} != {})", | ||||
|                         buffer.len(), | ||||
|                         PROXMOX_TAPE_BLOCK_SIZE, | ||||
|                     ))); | ||||
|                 } | ||||
|                 Ok(buffer.len()) | ||||
|             } | ||||
|  | ||||
| @ -14,12 +14,10 @@ pub struct EmulateTapeWriter<W> { | ||||
|     wrote_eof: bool, | ||||
| } | ||||
|  | ||||
| impl <W: Write> EmulateTapeWriter<W> { | ||||
|  | ||||
| impl<W: Write> EmulateTapeWriter<W> { | ||||
|     /// Create a new instance allowing to write about max_size bytes | ||||
|     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 { | ||||
|             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> { | ||||
|  | ||||
|         if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE { | ||||
|             proxmox_lang::io_bail!("EmulateTapeWriter: got write with wrong block size ({} != {}", | ||||
|                               buffer.len(), PROXMOX_TAPE_BLOCK_SIZE); | ||||
|             proxmox_lang::io_bail!( | ||||
|                 "EmulateTapeWriter: got write with wrong block size ({} != {}", | ||||
|                 buffer.len(), | ||||
|                 PROXMOX_TAPE_BLOCK_SIZE | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         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)?; | ||||
|  | ||||
| @ -3,7 +3,7 @@ use std::collections::HashSet; | ||||
| use anyhow::{bail, Error}; | ||||
| use bitflags::bitflags; | ||||
| use endian_trait::Endian; | ||||
| use serde::{Serialize, Deserialize}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use serde_json::Value; | ||||
|  | ||||
| use proxmox_uuid::Uuid; | ||||
| @ -30,14 +30,14 @@ mod emulate_tape_reader; | ||||
| pub use emulate_tape_reader::EmulateTapeReader; | ||||
|  | ||||
| mod emulate_tape_writer; | ||||
| pub use  emulate_tape_writer::EmulateTapeWriter; | ||||
| pub use emulate_tape_writer::EmulateTapeWriter; | ||||
|  | ||||
| pub mod sg_tape; | ||||
|  | ||||
| pub mod sg_pt_changer; | ||||
|  | ||||
| /// 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] | ||||
| 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 | ||||
| /// is a sequence number (`seq_nr`) which may be use for additional | ||||
| /// error checking. | ||||
| #[repr(C,packed)] | ||||
| #[repr(C, packed)] | ||||
| pub struct BlockHeader { | ||||
|     /// fixed value `PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0` | ||||
|     pub magic: [u8; 8], | ||||
| @ -84,7 +84,7 @@ bitflags! { | ||||
| } | ||||
|  | ||||
| #[derive(Endian, Copy, Clone, Debug)] | ||||
| #[repr(C,packed)] | ||||
| #[repr(C, packed)] | ||||
| /// Media Content Header | ||||
| /// | ||||
| /// All tape files start with this header. The header may contain some | ||||
| @ -115,11 +115,9 @@ pub struct MediaContentHeader { | ||||
| } | ||||
|  | ||||
| impl MediaContentHeader { | ||||
|  | ||||
|     /// Create a new instance with autogenerated Uuid | ||||
|     pub fn new(content_magic: [u8; 8], size: u32) -> Self { | ||||
|         let uuid = *Uuid::generate() | ||||
|             .into_inner(); | ||||
|         let uuid = *Uuid::generate().into_inner(); | ||||
|         Self { | ||||
|             magic: PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0, | ||||
|             content_magic, | ||||
| @ -153,9 +151,7 @@ impl MediaContentHeader { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| impl BlockHeader { | ||||
|  | ||||
|     pub const SIZE: usize = PROXMOX_TAPE_BLOCK_SIZE; | ||||
|  | ||||
|     /// 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 mut buffer = unsafe { | ||||
|             let ptr = alloc_zeroed( | ||||
|                  Layout::from_size_align(Self::SIZE, page_size) | ||||
|                     .unwrap(), | ||||
|             ); | ||||
|             let ptr = alloc_zeroed(Layout::from_size_align(Self::SIZE, page_size).unwrap()); | ||||
|             Box::from_raw( | ||||
|                 std::slice::from_raw_parts_mut(ptr, Self::SIZE - 16) | ||||
|                     as *mut [u8] as *mut Self | ||||
|                 std::slice::from_raw_parts_mut(ptr, Self::SIZE - 16) as *mut [u8] as *mut Self, | ||||
|             ) | ||||
|         }; | ||||
|         buffer.magic = PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0; | ||||
| @ -187,7 +179,7 @@ impl BlockHeader { | ||||
|  | ||||
|     /// Returns the `size` field | ||||
|     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 | ||||
| @ -263,16 +255,19 @@ pub struct MtxStatus { | ||||
| } | ||||
|  | ||||
| impl MtxStatus { | ||||
|  | ||||
|     pub fn slot_address(&self, slot: u64) -> Result<u16, Error> { | ||||
|         if slot == 0 { | ||||
|             bail!("invalid slot number '{}' (slots numbers starts at 1)", slot); | ||||
|         } | ||||
|         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> { | ||||
| @ -287,11 +282,10 @@ impl MtxStatus { | ||||
|         // simply use first transport | ||||
|         // (are there changers exposing more than one?) | ||||
|         // defaults to 0 for changer that do not report transports | ||||
|         self | ||||
|             .transports | ||||
|         self.transports | ||||
|             .get(0) | ||||
|             .map(|t| t.element_address) | ||||
|         .unwrap_or(0u16) | ||||
|             .unwrap_or(0u16) | ||||
|     } | ||||
|  | ||||
|     pub fn find_free_slot(&self, import_export: bool) -> Option<u64> { | ||||
| @ -301,14 +295,14 @@ impl MtxStatus { | ||||
|                 continue; // skip slots of wrong type | ||||
|             } | ||||
|             if let ElementStatus::Empty = slot_info.status { | ||||
|                 free_slot = Some((i+1) as u64); | ||||
|                 free_slot = Some((i + 1) as u64); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         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(); | ||||
|  | ||||
|         if let Some(slots) = &config.export_slots { | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| use std::path::{Path, PathBuf}; | ||||
| use std::collections::HashMap; | ||||
| use std::fs::{OpenOptions, File}; | ||||
| use std::fs::{File, OpenOptions}; | ||||
| use std::os::unix::fs::OpenOptionsExt; | ||||
| use std::os::unix::io::AsRawFd; | ||||
| use std::path::{Path, PathBuf}; | ||||
|  | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use nix::fcntl::{fcntl, FcntlArg, OFlag}; | ||||
| @ -12,21 +12,20 @@ use proxmox_sys::fs::scan_subdir; | ||||
|  | ||||
| use pbs_api_types::{DeviceKind, OptionalDeviceIdentification, TapeDeviceInfo}; | ||||
|  | ||||
| lazy_static::lazy_static!{ | ||||
| lazy_static::lazy_static! { | ||||
|     static ref SCSI_GENERIC_NAME_REGEX: regex::Regex = | ||||
|         regex::Regex::new(r"^sg\d+$").unwrap(); | ||||
| } | ||||
|  | ||||
| /// List linux tape changer devices | ||||
| pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> { | ||||
|  | ||||
|     let mut list = Vec::new(); | ||||
|  | ||||
|     let dir_iter = match scan_subdir( | ||||
|         libc::AT_FDCWD, | ||||
|         "/sys/class/scsi_generic", | ||||
|         &SCSI_GENERIC_NAME_REGEX) | ||||
|     { | ||||
|         &SCSI_GENERIC_NAME_REGEX, | ||||
|     ) { | ||||
|         Err(_) => return list, | ||||
|         Ok(iter) => iter, | ||||
|     }; | ||||
| @ -63,7 +62,9 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> { | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
|             _ => { continue; } | ||||
|             _ => { | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // let mut test_path = sys_path.clone(); | ||||
| @ -75,22 +76,42 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> { | ||||
|             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) | ||||
|             .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, | ||||
|             Some(serial) => serial, | ||||
|         }; | ||||
|  | ||||
|         let vendor = device.property_value("ID_VENDOR") | ||||
|         let vendor = device | ||||
|             .property_value("ID_VENDOR") | ||||
|             .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")); | ||||
|  | ||||
|         let model = device.property_value("ID_MODEL") | ||||
|         let model = device | ||||
|             .property_value("ID_MODEL") | ||||
|             .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")); | ||||
|  | ||||
|         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 | ||||
| pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> { | ||||
|  | ||||
|     let mut list = Vec::new(); | ||||
|  | ||||
|     let dir_iter = match scan_subdir( | ||||
|         libc::AT_FDCWD, | ||||
|         "/sys/class/scsi_generic", | ||||
|         &SCSI_GENERIC_NAME_REGEX) | ||||
|     { | ||||
|         &SCSI_GENERIC_NAME_REGEX, | ||||
|     ) { | ||||
|         Err(_) => return list, | ||||
|         Ok(iter) => iter, | ||||
|     }; | ||||
| @ -157,7 +177,9 @@ pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> { | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
|             _ => { continue; } | ||||
|             _ => { | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // let mut test_path = sys_path.clone(); | ||||
| @ -169,22 +191,42 @@ pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> { | ||||
|             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) | ||||
|             .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, | ||||
|             Some(serial) => serial, | ||||
|         }; | ||||
|  | ||||
|         let vendor = device.property_value("ID_VENDOR") | ||||
|         let vendor = device | ||||
|             .property_value("ID_VENDOR") | ||||
|             .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")); | ||||
|  | ||||
|         let model = device.property_value("ID_MODEL") | ||||
|         let model = device | ||||
|             .property_value("ID_MODEL") | ||||
|             .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")); | ||||
|  | ||||
|         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` | ||||
| pub fn lookup_device<'a>( | ||||
|     devices: &'a[TapeDeviceInfo], | ||||
|     path: &str, | ||||
| ) -> Option<&'a TapeDeviceInfo> { | ||||
|  | ||||
| pub fn lookup_device<'a>(devices: &'a [TapeDeviceInfo], path: &str) -> Option<&'a TapeDeviceInfo> { | ||||
|     if let Ok(stat) = nix::sys::stat::stat(path) { | ||||
|  | ||||
|         let major = unsafe { libc::major(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 { | ||||
|         None | ||||
|     } | ||||
| @ -224,10 +263,9 @@ pub fn lookup_device<'a>( | ||||
|  | ||||
| /// Lookup optional drive identification attributes | ||||
| pub fn lookup_device_identification<'a>( | ||||
|     devices: &'a[TapeDeviceInfo], | ||||
|     devices: &'a [TapeDeviceInfo], | ||||
|     path: &str, | ||||
| ) -> OptionalDeviceIdentification { | ||||
|  | ||||
|     if let Some(info) = lookup_device(devices, path) { | ||||
|         OptionalDeviceIdentification { | ||||
|             vendor: Some(info.vendor.clone()), | ||||
| @ -244,20 +282,15 @@ pub fn lookup_device_identification<'a>( | ||||
| } | ||||
|  | ||||
| /// Make sure path is a lto tape device | ||||
| pub fn check_drive_path( | ||||
|     drives: &[TapeDeviceInfo], | ||||
|     path: &str, | ||||
| ) -> Result<(), Error> { | ||||
| pub fn check_drive_path(drives: &[TapeDeviceInfo], path: &str) -> Result<(), Error> { | ||||
|     if lookup_device(drives, path).is_none() { | ||||
|         bail!("path '{}' is not a lto SCSI-generic tape device", path); | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Check for correct Major/Minor numbers | ||||
| pub fn check_tape_is_lto_tape_device(file: &File) -> Result<(), Error> { | ||||
|  | ||||
|     let stat = nix::sys::stat::fstat(file.as_raw_fd())?; | ||||
|  | ||||
|     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 | ||||
| /// succeeded. This also checks if the device is a non-rewinding tape | ||||
| /// device. | ||||
| pub fn open_lto_tape_device( | ||||
|     path: &str, | ||||
| ) -> Result<File, Error> { | ||||
|  | ||||
| pub fn open_lto_tape_device(path: &str) -> Result<File, Error> { | ||||
|     let file = OpenOptions::new() | ||||
|         .read(true) | ||||
|         .write(true) | ||||
| @ -293,14 +323,12 @@ pub fn open_lto_tape_device( | ||||
|  | ||||
|     // clear O_NONBLOCK from now on. | ||||
|  | ||||
|     let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL) | ||||
|         .into_io_result()?; | ||||
|     let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL).into_io_result()?; | ||||
|  | ||||
|     let mut flags = OFlag::from_bits_truncate(flags); | ||||
|     flags.remove(OFlag::O_NONBLOCK); | ||||
|  | ||||
|     fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)) | ||||
|         .into_io_result()?; | ||||
|     fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)).into_io_result()?; | ||||
|  | ||||
|     check_tape_is_lto_tape_device(&file) | ||||
|         .map_err(|err| format_err!("device type check {:?} failed - {}", path, err))?; | ||||
| @ -308,15 +336,20 @@ pub fn open_lto_tape_device( | ||||
|     Ok(file) | ||||
| } | ||||
|  | ||||
|  | ||||
| // shell completion helper | ||||
|  | ||||
| /// List changer device paths | ||||
| 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 | ||||
| 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() | ||||
| } | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| //! SCSI changer implementation using libsgutil2 | ||||
| use std::os::unix::prelude::AsRawFd; | ||||
| use std::io::Read; | ||||
| 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::fs::{OpenOptions, File}; | ||||
|  | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use endian_trait::Endian; | ||||
| @ -13,31 +13,25 @@ use proxmox_io::ReadExt; | ||||
| use pbs_api_types::ScsiTapeChanger; | ||||
|  | ||||
| use crate::{ | ||||
|     ElementStatus,MtxStatus,TransportElementStatus,DriveStatus,StorageElementStatus, | ||||
|     sgutils2::{ | ||||
|         SgRaw, | ||||
|         SENSE_KEY_NOT_READY, | ||||
|         ScsiError, | ||||
|         scsi_ascii_to_string, | ||||
|         scsi_inquiry, | ||||
|     }, | ||||
|     sgutils2::{scsi_ascii_to_string, scsi_inquiry, ScsiError, SgRaw, SENSE_KEY_NOT_READY}, | ||||
|     DriveStatus, ElementStatus, MtxStatus, StorageElementStatus, TransportElementStatus, | ||||
| }; | ||||
|  | ||||
| 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; | ||||
|  | ||||
| /// Initialize element status (Inventory) | ||||
| pub fn initialize_element_status<F: AsRawFd>(file: &mut F) -> Result<(), Error> { | ||||
|  | ||||
|     let mut sg_raw = SgRaw::new(file, 64)?; | ||||
|  | ||||
|     // 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(); | ||||
|     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))?; | ||||
|  | ||||
|     Ok(()) | ||||
| @ -78,7 +72,6 @@ fn execute_scsi_command<F: AsRawFd>( | ||||
|     error_prefix: &str, | ||||
|     retry: bool, | ||||
| ) -> Result<Vec<u8>, Error> { | ||||
|  | ||||
|     let start = std::time::SystemTime::now(); | ||||
|  | ||||
|     let mut last_msg: Option<String> = None; | ||||
| @ -103,9 +96,12 @@ fn execute_scsi_command<F: AsRawFd>( | ||||
|  | ||||
|                 if let ScsiError::Sense(ref sense) = err { | ||||
|                     // 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 | ||||
|                         timeout = std::time::Duration::new(5*60, 0); | ||||
|                         timeout = std::time::Duration::new(5 * 60, 0); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
| @ -117,14 +113,12 @@ fn execute_scsi_command<F: AsRawFd>( | ||||
|                 continue; // try again | ||||
|             } | ||||
|         } | ||||
|    } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| fn read_element_address_assignment<F: AsRawFd>( | ||||
|     file: &mut F, | ||||
| ) -> Result<AddressAssignmentPage, Error> { | ||||
|  | ||||
|     let allocation_len: u8 = u8::MAX; | ||||
|     let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; | ||||
|     sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); | ||||
| @ -148,7 +142,8 @@ fn read_element_address_assignment<F: AsRawFd>( | ||||
|         } | ||||
|  | ||||
|         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( | ||||
| @ -156,7 +151,6 @@ fn scsi_move_medium_cdb( | ||||
|     source_element_address: u16, | ||||
|     destination_element_address: u16, | ||||
| ) -> Vec<u8> { | ||||
|  | ||||
|     let mut cmd = Vec::new(); | ||||
|     cmd.push(0xA5); // MOVE MEDIUM (A5h) | ||||
|     cmd.push(0); // reserved | ||||
| @ -172,11 +166,7 @@ fn scsi_move_medium_cdb( | ||||
| } | ||||
|  | ||||
| /// Load media from storage slot into drive | ||||
| pub fn load_slot( | ||||
|     file: &mut File, | ||||
|     from_slot: u64, | ||||
|     drivenum: u64, | ||||
| ) -> Result<(), Error> { | ||||
| pub fn load_slot(file: &mut File, from_slot: u64, drivenum: u64) -> Result<(), Error> { | ||||
|     let status = read_element_status(file)?; | ||||
|  | ||||
|     let transport_address = status.transport_address(); | ||||
| @ -192,19 +182,15 @@ pub fn load_slot( | ||||
|     let mut sg_raw = SgRaw::new(file, 64)?; | ||||
|     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))?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Unload media from drive into a storage slot | ||||
| pub fn unload( | ||||
|     file: &mut File, | ||||
|     to_slot: u64, | ||||
|     drivenum: u64, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
| pub fn unload(file: &mut File, to_slot: u64, drivenum: u64) -> Result<(), Error> { | ||||
|     let status = read_element_status(file)?; | ||||
|  | ||||
|     let transport_address = status.transport_address(); | ||||
| @ -220,7 +206,8 @@ pub fn unload( | ||||
|     let mut sg_raw = SgRaw::new(file, 64)?; | ||||
|     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))?; | ||||
|  | ||||
|     Ok(()) | ||||
| @ -232,7 +219,6 @@ pub fn transfer_medium<F: AsRawFd>( | ||||
|     from_slot: u64, | ||||
|     to_slot: u64, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     let status = read_element_status(file)?; | ||||
|  | ||||
|     let transport_address = status.transport_address(); | ||||
| @ -248,11 +234,14 @@ pub fn transfer_medium<F: AsRawFd>( | ||||
|     let mut sg_raw = SgRaw::new(file, 64)?; | ||||
|     sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); | ||||
|  | ||||
|     sg_raw.do_command(&cmd) | ||||
|         .map_err(|err| { | ||||
|             format_err!("transfer medium from slot {} to slot {} failed - {}", | ||||
|                         from_slot, to_slot, err) | ||||
|         })?; | ||||
|     sg_raw.do_command(&cmd).map_err(|err| { | ||||
|         format_err!( | ||||
|             "transfer medium from slot {} to slot {} failed - {}", | ||||
|             from_slot, | ||||
|             to_slot, | ||||
|             err | ||||
|         ) | ||||
|     })?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
| @ -282,7 +271,7 @@ impl ElementType { | ||||
|     fn byte6(&self) -> u8 { | ||||
|         match *self { | ||||
|             ElementType::DataTransferWithDVCID => 0b001, //  Mixed=0,CurData=0,DVCID=1 | ||||
|             _ => 0b000, // Mixed=0,CurData=0,DVCID=0 | ||||
|             _ => 0b000,                                  // Mixed=0,CurData=0,DVCID=0 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -293,7 +282,6 @@ fn scsi_read_element_status_cdb( | ||||
|     element_type: ElementType, | ||||
|     allocation_len: u32, | ||||
| ) -> Vec<u8> { | ||||
|  | ||||
|     let mut cmd = Vec::new(); | ||||
|     cmd.push(0xB8); // READ ELEMENT STATUS (B8h) | ||||
|     cmd.push(element_type.byte1()); | ||||
| @ -315,7 +303,6 @@ fn get_element<F: AsRawFd>( | ||||
|     allocation_len: u32, | ||||
|     mut retry: bool, | ||||
| ) -> Result<DecodedStatusPage, Error> { | ||||
|  | ||||
|     let mut start_element_address = 0; | ||||
|     let number_of_elements: u16 = 1000; // some changers limit the query | ||||
|  | ||||
| @ -328,7 +315,12 @@ fn get_element<F: AsRawFd>( | ||||
|     }; | ||||
|  | ||||
|     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)?; | ||||
|  | ||||
| @ -364,7 +356,6 @@ fn get_element<F: AsRawFd>( | ||||
|  | ||||
| /// Read element status. | ||||
| pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error> { | ||||
|  | ||||
|     let inquiry = scsi_inquiry(file)?; | ||||
|  | ||||
|     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)?; | ||||
|     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); | ||||
|  | ||||
|     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); | ||||
|  | ||||
|     // get the serial + vendor + model, | ||||
|     // 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. | ||||
|     // there should not be too many drives normally | ||||
|     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); | ||||
|  | ||||
|     let transport_count = setup.transport_element_count as usize; | ||||
| @ -451,14 +462,18 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error> | ||||
|     let mut slots = storage_slots; | ||||
|     slots.extend(import_export_slots); | ||||
|  | ||||
|     let mut status = MtxStatus { transports, drives, slots }; | ||||
|     let mut status = MtxStatus { | ||||
|         transports, | ||||
|         drives, | ||||
|         slots, | ||||
|     }; | ||||
|  | ||||
|     // sanity checks | ||||
|     if status.drives.is_empty() { | ||||
|         bail!("no data transfer elements reported"); | ||||
|     } | ||||
|     if status.slots.is_empty() { | ||||
|          bail!("no storage elements reported"); | ||||
|         bail!("no storage elements reported"); | ||||
|     } | ||||
|  | ||||
|     // compute virtual storage slot to element_address map | ||||
| @ -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> { | ||||
|     let path = &config.path; | ||||
|  | ||||
|     let mut file = open(path) | ||||
|         .map_err(|err| format_err!("error opening '{}': {}", path, err))?; | ||||
|     let mut file = open(path).map_err(|err| format_err!("error opening '{}': {}", path, err))?; | ||||
|     let mut status = read_element_status(&mut file) | ||||
|         .map_err(|err| format_err!("error reading element status: {}", err))?; | ||||
|  | ||||
| @ -492,14 +506,13 @@ pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> { | ||||
|     Ok(status) | ||||
| } | ||||
|  | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| #[derive(Endian)] | ||||
| struct ElementStatusHeader { | ||||
|     first_element_address_reported: u16, | ||||
|     number_of_elements_available: u16, | ||||
|     reserved: u8, | ||||
|     byte_count_of_report_available: [u8;3], | ||||
|     byte_count_of_report_available: [u8; 3], | ||||
| } | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| @ -509,18 +522,17 @@ struct SubHeader { | ||||
|     flags: u8, | ||||
|     descriptor_length: u16, | ||||
|     reserved: u8, | ||||
|     byte_count_of_descriptor_data_available: [u8;3], | ||||
|     byte_count_of_descriptor_data_available: [u8; 3], | ||||
| } | ||||
|  | ||||
| impl SubHeader { | ||||
|  | ||||
|     fn parse_optional_volume_tag<R: Read>( | ||||
|         &self, | ||||
|         reader: &mut R, | ||||
|         full: bool, | ||||
|     ) -> Result<Option<String>, Error> { | ||||
|  | ||||
|         if (self.flags & 128) != 0 { // has PVolTag | ||||
|         if (self.flags & 128) != 0 { | ||||
|             // has PVolTag | ||||
|             let tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?; | ||||
|             if full { | ||||
|                 let volume_tag = scsi_ascii_to_string(&tmp); | ||||
| @ -532,12 +544,9 @@ impl SubHeader { | ||||
|  | ||||
|     // AFAIK, tape changer do not use AlternateVolumeTag | ||||
|     // but parse anyways, just to be sure | ||||
|     fn skip_alternate_volume_tag<R: Read>( | ||||
|         &self, | ||||
|         reader: &mut R, | ||||
|     ) -> Result<Option<String>, Error> { | ||||
|  | ||||
|         if (self.flags & 64) != 0 { // has AVolTag | ||||
|     fn skip_alternate_volume_tag<R: Read>(&self, reader: &mut R) -> Result<Option<String>, Error> { | ||||
|         if (self.flags & 64) != 0 { | ||||
|             // has AVolTag | ||||
|             let _tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?; | ||||
|         } | ||||
|  | ||||
| @ -547,13 +556,14 @@ impl SubHeader { | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| #[derive(Endian)] | ||||
| struct TransportDescriptor { // Robot/Griper | ||||
| struct TransportDescriptor { | ||||
|     // Robot/Griper | ||||
|     element_address: u16, | ||||
|     flags1: u8, | ||||
|     reserved_3: u8, | ||||
|     additional_sense_code: u8, | ||||
|     additional_sense_code_qualifier: u8, | ||||
|     reserved_6: [u8;3], | ||||
|     reserved_6: [u8; 3], | ||||
|     flags2: u8, | ||||
|     source_storage_element_address: u16, | ||||
|     // volume tag and Mixed media descriptor follows (depends on flags) | ||||
| @ -561,7 +571,8 @@ struct TransportDescriptor { // Robot/Griper | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| #[derive(Endian)] | ||||
| struct TransferDescriptor { // Tape drive | ||||
| struct TransferDescriptor { | ||||
|     // Tape drive | ||||
|     element_address: u16, | ||||
|     flags1: u8, | ||||
|     reserved_3: u8, | ||||
| @ -578,7 +589,8 @@ struct TransferDescriptor { // Tape drive | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| #[derive(Endian)] | ||||
| struct DvcidHead { // Drive Identifier Header | ||||
| struct DvcidHead { | ||||
|     // Drive Identifier Header | ||||
|     code_set: u8, | ||||
|     identifier_type: u8, | ||||
|     reserved: u8, | ||||
| @ -588,13 +600,14 @@ struct DvcidHead { // Drive Identifier Header | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| #[derive(Endian)] | ||||
| struct StorageDescriptor { // Mail Slot | ||||
| struct StorageDescriptor { | ||||
|     // Mail Slot | ||||
|     element_address: u16, | ||||
|     flags1: u8, | ||||
|     reserved_3: u8, | ||||
|     additional_sense_code: u8, | ||||
|     additional_sense_code_qualifier: u8, | ||||
|     reserved_6: [u8;3], | ||||
|     reserved_6: [u8; 3], | ||||
|     flags2: u8, | ||||
|     source_storage_element_address: u16, | ||||
|     // 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 (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 = scsi_ascii_to_string(&serial); | ||||
|             (Some(serial), None, None) | ||||
| @ -661,9 +675,7 @@ fn decode_element_status_page( | ||||
|     data: &[u8], | ||||
|     start_element_address: u16, | ||||
| ) -> Result<DecodedStatusPage, Error> { | ||||
|  | ||||
|     proxmox_lang::try_block!({ | ||||
|  | ||||
|         let mut result = DecodedStatusPage { | ||||
|             last_element_address: None, | ||||
|             transports: Vec::new(), | ||||
| @ -690,7 +702,11 @@ fn decode_element_status_page( | ||||
|         if len < reader.len() { | ||||
|             reader = &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 { | ||||
| @ -763,7 +779,8 @@ fn decode_element_status_page( | ||||
|                     4 => { | ||||
|                         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) | ||||
|                         } else { | ||||
|                             None | ||||
| @ -798,23 +815,21 @@ fn decode_element_status_page( | ||||
|         } | ||||
|  | ||||
|         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 | ||||
| pub fn open<P: AsRef<Path>>(path: P) -> Result<File, Error> { | ||||
|     let file = OpenOptions::new() | ||||
|         .read(true) | ||||
|         .write(true) | ||||
|         .open(path)?; | ||||
|     let file = OpenOptions::new().read(true).write(true).open(path)?; | ||||
|  | ||||
|     Ok(file) | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use anyhow::Error; | ||||
|     use super::*; | ||||
|     use anyhow::Error; | ||||
|  | ||||
|     struct StorageDesc { | ||||
|         address: u16, | ||||
| @ -826,9 +841,10 @@ mod test { | ||||
|         trailing: &[u8], | ||||
|         element_type: u8, | ||||
|     ) -> Vec<u8> { | ||||
|         let descs: Vec<Vec<u8>> = descriptors.iter().map(|desc| { | ||||
|             build_storage_descriptor(desc, trailing) | ||||
|         }).collect(); | ||||
|         let descs: Vec<Vec<u8>> = descriptors | ||||
|             .iter() | ||||
|             .map(|desc| build_storage_descriptor(desc, trailing)) | ||||
|             .collect(); | ||||
|  | ||||
|         let (desc_len, address) = if let Some(el) = descs.get(0) { | ||||
|             (el.len() as u16, descriptors[0].address) | ||||
| @ -863,10 +879,7 @@ mod test { | ||||
|         res | ||||
|     } | ||||
|  | ||||
|     fn build_storage_descriptor( | ||||
|         desc: &StorageDesc, | ||||
|         trailing: &[u8], | ||||
|     ) -> Vec<u8> { | ||||
|     fn build_storage_descriptor(desc: &StorageDesc, trailing: &[u8]) -> Vec<u8> { | ||||
|         let mut res = Vec::new(); | ||||
|         res.push(((desc.address >> 8) & 0xFF) as u8); | ||||
|         res.push((desc.address & 0xFF) as u8); | ||||
| @ -876,7 +889,7 @@ mod test { | ||||
|             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 & 0xFF) as u8); | ||||
|  | ||||
| @ -942,7 +955,7 @@ mod test { | ||||
|                 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)?; | ||||
|         assert_eq!(page.storage_slots.len(), 2); | ||||
|         Ok(()) | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| use std::time::SystemTime; | ||||
| use std::convert::TryFrom; | ||||
| use std::convert::TryInto; | ||||
| use std::fs::{File, OpenOptions}; | ||||
| use std::os::unix::fs::OpenOptionsExt; | ||||
| use std::os::unix::io::AsRawFd; | ||||
| use std::path::Path; | ||||
| use std::convert::TryFrom; | ||||
| use std::convert::TryInto; | ||||
| use std::time::SystemTime; | ||||
|  | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use endian_trait::Endian; | ||||
| @ -25,56 +25,43 @@ pub use mam::*; | ||||
| mod report_density; | ||||
| pub use report_density::*; | ||||
|  | ||||
| use proxmox_sys::error::SysResult; | ||||
| 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::{ | ||||
|     BlockRead, | ||||
|     BlockReadError, | ||||
|     BlockWrite, | ||||
|     BlockedWriter, | ||||
|     BlockedReader, | ||||
|     sgutils2::{ | ||||
|         SgRaw, | ||||
|         SenseInfo, | ||||
|         ScsiError, | ||||
|         InquiryInfo, | ||||
|         ModeParameterHeader, | ||||
|         ModeBlockDescriptor, | ||||
|         alloc_page_aligned_buffer, | ||||
|         scsi_inquiry, | ||||
|         scsi_mode_sense, | ||||
|         scsi_request_sense, | ||||
|         alloc_page_aligned_buffer, scsi_inquiry, scsi_mode_sense, scsi_request_sense, InquiryInfo, | ||||
|         ModeBlockDescriptor, ModeParameterHeader, ScsiError, SenseInfo, SgRaw, | ||||
|     }, | ||||
|     BlockRead, BlockReadError, BlockWrite, BlockedReader, BlockedWriter, | ||||
| }; | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| #[derive(Endian, Debug, Copy, Clone)] | ||||
| pub struct ReadPositionLongPage { | ||||
|     flags: u8, | ||||
|     reserved: [u8;3], | ||||
|     reserved: [u8; 3], | ||||
|     partition_number: u32, | ||||
|     pub logical_object_number: u64, | ||||
|     pub logical_file_id: u64, | ||||
|     obsolete: [u8;8], | ||||
|     obsolete: [u8; 8], | ||||
| } | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| #[derive(Endian, Debug, Copy, Clone)] | ||||
| struct DataCompressionModePage { | ||||
|     page_code: u8,   // 0x0f | ||||
|     page_length: u8,  // 0x0e | ||||
|     page_length: u8, // 0x0e | ||||
|     flags2: u8, | ||||
|     flags3: u8, | ||||
|     compression_algorithm: u32, | ||||
|     decompression_algorithm: u32, | ||||
|     reserved: [u8;4], | ||||
|     reserved: [u8; 4], | ||||
| } | ||||
|  | ||||
| impl DataCompressionModePage { | ||||
|  | ||||
|     pub fn set_compression(&mut self, enable: bool) { | ||||
|         if enable { | ||||
|             self.flags2 |= 128; | ||||
| @ -92,17 +79,15 @@ impl DataCompressionModePage { | ||||
| #[derive(Endian)] | ||||
| struct MediumConfigurationModePage { | ||||
|     page_code: u8,   // 0x1d | ||||
|     page_length: u8,  // 0x1e | ||||
|     page_length: u8, // 0x1e | ||||
|     flags2: u8, | ||||
|     reserved: [u8;29], | ||||
|     reserved: [u8; 29], | ||||
| } | ||||
|  | ||||
| impl MediumConfigurationModePage { | ||||
|  | ||||
|     pub fn is_worm(&self) -> bool { | ||||
|         (self.flags2 & 1) == 1 | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| @ -122,18 +107,19 @@ pub struct 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 | ||||
|     /// | ||||
|     /// Uses scsi_inquiry to check the device type. | ||||
|     pub fn new(mut file: File) -> Result<Self, Error> { | ||||
|  | ||||
|         let info = scsi_inquiry(&mut file)?; | ||||
|  | ||||
|         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 { | ||||
| @ -169,14 +155,12 @@ impl SgTape { | ||||
|             .open(path)?; | ||||
|  | ||||
|         // then clear O_NONBLOCK | ||||
|         let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL) | ||||
|             .into_io_result()?; | ||||
|         let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL).into_io_result()?; | ||||
|  | ||||
|         let mut flags = OFlag::from_bits_truncate(flags); | ||||
|         flags.remove(OFlag::O_NONBLOCK); | ||||
|  | ||||
|         fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)) | ||||
|             .into_io_result()?; | ||||
|         fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)).into_io_result()?; | ||||
|  | ||||
|         Self::new(file) | ||||
|     } | ||||
| @ -203,7 +187,8 @@ impl SgTape { | ||||
|         } | ||||
|         cmd.extend(&[0, 0, 0, 0]); | ||||
|  | ||||
|         sg_raw.do_command(&cmd) | ||||
|         sg_raw | ||||
|             .do_command(&cmd) | ||||
|             .map_err(|err| format_err!("erase failed - {}", err))?; | ||||
|  | ||||
|         Ok(()) | ||||
| @ -211,7 +196,6 @@ impl SgTape { | ||||
|  | ||||
|     /// Format media, single partition | ||||
|     pub fn format_media(&mut self, fast: bool) -> Result<(), Error> { | ||||
|  | ||||
|         // try to get info about loaded media first | ||||
|         let (has_format, is_worm) = match self.read_medium_configuration_page() { | ||||
|             Ok((_head, block_descriptor, page)) => { | ||||
| @ -236,7 +220,6 @@ impl SgTape { | ||||
|             } | ||||
|  | ||||
|             Ok(()) | ||||
|  | ||||
|         } else { | ||||
|             self.rewind()?; | ||||
|  | ||||
| @ -261,7 +244,6 @@ impl SgTape { | ||||
|  | ||||
|     /// Lock/Unlock drive door | ||||
|     pub fn set_medium_removal(&mut self, allow: bool) -> Result<(), ScsiError> { | ||||
|  | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 16)?; | ||||
|         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); | ||||
|         let mut cmd = Vec::new(); | ||||
| @ -279,19 +261,19 @@ impl SgTape { | ||||
|     } | ||||
|  | ||||
|     pub fn rewind(&mut self) -> Result<(), Error> { | ||||
|  | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 16)?; | ||||
|         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); | ||||
|         let mut cmd = Vec::new(); | ||||
|         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))?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn locate_file(&mut self, position: u64) ->  Result<(), Error> { | ||||
|     pub fn locate_file(&mut self, position: u64) -> Result<(), Error> { | ||||
|         if position == 0 { | ||||
|             return self.rewind(); | ||||
|         } | ||||
| @ -303,7 +285,8 @@ impl SgTape { | ||||
|             self.rewind()?; | ||||
|             let mut sg_raw = SgRaw::new(&mut self.file, 16)?; | ||||
|             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))?; | ||||
|             return Ok(()); | ||||
|         } | ||||
| @ -334,20 +317,22 @@ impl SgTape { | ||||
|         cmd.extend(&fixed_position.to_be_bytes()); | ||||
|         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))?; | ||||
|  | ||||
|         // LOCATE always position at the BOT side of the filemark, so | ||||
|         // 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))?; | ||||
|  | ||||
|         if self.locate_offset.is_none() { | ||||
|             // check if we landed at correct position | ||||
|             let current_file = self.current_file_number()?; | ||||
|             if current_file != position { | ||||
|                 let offset: i64 = | ||||
|                     i64::try_from((position as i128) - (current_file as i128)).map_err(|err| { | ||||
|                 let offset: i64 = i64::try_from((position as i128) - (current_file as i128)) | ||||
|                     .map_err(|err| { | ||||
|                         format_err!( | ||||
|                             "locate_file: offset between {} and {} invalid: {}", | ||||
|                             position, | ||||
| @ -370,7 +355,6 @@ impl SgTape { | ||||
|     } | ||||
|  | ||||
|     pub fn position(&mut self) -> Result<ReadPositionLongPage, Error> { | ||||
|  | ||||
|         let expected_size = std::mem::size_of::<ReadPositionLongPage>(); | ||||
|  | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 32)?; | ||||
| @ -381,12 +365,17 @@ impl SgTape { | ||||
|         // reference manual. | ||||
|         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))?; | ||||
|  | ||||
|         let page = proxmox_lang::try_block!({ | ||||
|             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; | ||||
| @ -394,7 +383,8 @@ impl SgTape { | ||||
|             let page: ReadPositionLongPage = unsafe { reader.read_be_value()? }; | ||||
|  | ||||
|             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 { | ||||
|             bail!("detecthed partitioned tape - not supported"); | ||||
| @ -410,7 +400,6 @@ impl SgTape { | ||||
|  | ||||
|     /// Check if we are positioned after a filemark (or BOT) | ||||
|     pub fn check_filemark(&mut self) -> Result<bool, Error> { | ||||
|  | ||||
|         let pos = self.position()?; | ||||
|         if pos.logical_object_number == 0 { | ||||
|             // at BOT, Ok (no filemark required) | ||||
| @ -421,13 +410,24 @@ impl SgTape { | ||||
|         match self.space(-1, true) { | ||||
|             Ok(_) => { | ||||
|                 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) | ||||
|             } | ||||
|             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 | ||||
|                 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) | ||||
|             } | ||||
|             Err(err) => { | ||||
| @ -436,13 +436,14 @@ impl SgTape { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn move_to_eom(&mut self, write_missing_eof: bool) ->  Result<(), Error> { | ||||
|     pub fn move_to_eom(&mut self, write_missing_eof: bool) -> Result<(), Error> { | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 16)?; | ||||
|         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); | ||||
|         let mut cmd = Vec::new(); | ||||
|         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))?; | ||||
|  | ||||
|         if write_missing_eof && !self.check_filemark()? { | ||||
| @ -452,7 +453,7 @@ impl SgTape { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn space(&mut self, count: isize, blocks: bool) ->  Result<(), ScsiError> { | ||||
|     fn space(&mut self, count: isize, blocks: bool) -> Result<(), ScsiError> { | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 16)?; | ||||
|         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); | ||||
|         let mut cmd = Vec::new(); | ||||
| @ -487,52 +488,50 @@ impl SgTape { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn space_filemarks(&mut self, count: isize) ->  Result<(), Error> { | ||||
|     pub fn space_filemarks(&mut self, count: isize) -> Result<(), Error> { | ||||
|         self.space(count, false) | ||||
|             .map_err(|err| format_err!("space filemarks failed - {}", err)) | ||||
|     } | ||||
|  | ||||
|     pub fn space_blocks(&mut self, count: isize) ->  Result<(), Error> { | ||||
|     pub fn space_blocks(&mut self, count: isize) -> Result<(), Error> { | ||||
|         self.space(count, true) | ||||
|             .map_err(|err| format_err!("space blocks failed - {}", err)) | ||||
|     } | ||||
|  | ||||
|     pub fn eject(&mut self) ->  Result<(), Error> { | ||||
|     pub fn eject(&mut self) -> Result<(), Error> { | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 16)?; | ||||
|         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); | ||||
|         let mut cmd = Vec::new(); | ||||
|         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))?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn load(&mut self) ->  Result<(), Error> { | ||||
|     pub fn load(&mut self) -> Result<(), Error> { | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 16)?; | ||||
|         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); | ||||
|         let mut cmd = Vec::new(); | ||||
|         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))?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn write_filemarks( | ||||
|         &mut self, | ||||
|         count: usize, | ||||
|         immediate: bool, | ||||
|     ) ->  Result<(), std::io::Error> { | ||||
|  | ||||
|     pub fn write_filemarks(&mut self, count: usize, immediate: bool) -> Result<(), std::io::Error> { | ||||
|         if count > 255 { | ||||
|             proxmox_lang::io_bail!("write_filemarks failed: got strange count '{}'", count); | ||||
|         } | ||||
|  | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 16) | ||||
|             .map_err(|err| proxmox_lang::io_format_err!("write_filemarks failed (alloc) - {}", err))?; | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 16).map_err(|err| { | ||||
|             proxmox_lang::io_format_err!("write_filemarks failed (alloc) - {}", err) | ||||
|         })?; | ||||
|  | ||||
|         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); | ||||
|         let mut cmd = Vec::new(); | ||||
| @ -547,9 +546,11 @@ impl SgTape { | ||||
|  | ||||
|         match sg_raw.do_command(&cmd) { | ||||
|             Ok(_) => { /* OK */ } | ||||
|             Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => { | ||||
|                 /* LEOM - ignore */ | ||||
|             } | ||||
|             Err(ScsiError::Sense(SenseInfo { | ||||
|                 sense_key: 0, | ||||
|                 asc: 0, | ||||
|                 ascq: 2, | ||||
|             })) => { /* LEOM - ignore */ } | ||||
|             Err(err) => { | ||||
|                 proxmox_lang::io_bail!("write filemark  failed - {}", err); | ||||
|             } | ||||
| @ -565,7 +566,6 @@ impl SgTape { | ||||
|     } | ||||
|  | ||||
|     pub fn test_unit_ready(&mut self) -> Result<(), Error> { | ||||
|  | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 16)?; | ||||
|         sg_raw.set_timeout(30); // use short timeout | ||||
|         let mut cmd = Vec::new(); | ||||
| @ -580,7 +580,6 @@ impl SgTape { | ||||
|     } | ||||
|  | ||||
|     pub fn wait_until_ready(&mut self) -> Result<(), Error> { | ||||
|  | ||||
|         let start = SystemTime::now(); | ||||
|         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) | ||||
|     } | ||||
|  | ||||
|     pub fn set_encryption( | ||||
|         &mut self, | ||||
|         key: Option<[u8; 32]>, | ||||
|     ) -> Result<(), Error> { | ||||
|  | ||||
|     pub fn set_encryption(&mut self, key: Option<[u8; 32]>) -> Result<(), Error> { | ||||
|         self.encryption_key_loaded = key.is_some(); | ||||
|  | ||||
|         set_encryption(&mut self.file, key) | ||||
| @ -626,19 +621,17 @@ impl SgTape { | ||||
|     // | ||||
|     // 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> { | ||||
|  | ||||
|         let transfer_len = data.len(); | ||||
|  | ||||
|         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) | ||||
|             .unwrap(); // cannot fail with size 0 | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 0).unwrap(); // cannot fail with size 0 | ||||
|  | ||||
|         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); | ||||
|         let mut cmd = Vec::new(); | ||||
|         cmd.push(0x0A);  // WRITE | ||||
|         cmd.push(0x0A); // WRITE | ||||
|         cmd.push(0x00); // VARIABLE SIZED BLOCKS | ||||
|         cmd.push(((transfer_len >> 16) & 0xff) as u8); | ||||
|         cmd.push(((transfer_len >> 8) & 0xff) as u8); | ||||
| @ -649,8 +642,12 @@ impl SgTape { | ||||
|         //println!("WRITE {:?}", data); | ||||
|  | ||||
|         match sg_raw.do_out_command(&cmd, data) { | ||||
|             Ok(()) => { Ok(false) } | ||||
|             Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => { | ||||
|             Ok(()) => Ok(false), | ||||
|             Err(ScsiError::Sense(SenseInfo { | ||||
|                 sense_key: 0, | ||||
|                 asc: 0, | ||||
|                 ascq: 2, | ||||
|             })) => { | ||||
|                 Ok(true) // LEOM | ||||
|             } | ||||
|             Err(err) => { | ||||
| @ -663,19 +660,18 @@ impl SgTape { | ||||
|         let transfer_len = buffer.len(); | ||||
|  | ||||
|         if transfer_len > 0xFFFFFF { | ||||
|             return Err(BlockReadError::Error( | ||||
|                 proxmox_lang::io_format_err!("read failed - buffer too large") | ||||
|             )); | ||||
|             return Err(BlockReadError::Error(proxmox_lang::io_format_err!( | ||||
|                 "read failed - buffer too large" | ||||
|             ))); | ||||
|         } | ||||
|  | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 0) | ||||
|             .unwrap(); // cannot fail with size 0 | ||||
|         let mut sg_raw = SgRaw::new(&mut self.file, 0).unwrap(); // cannot fail with size 0 | ||||
|  | ||||
|         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT); | ||||
|         let mut cmd = Vec::new(); | ||||
|         cmd.push(0x08); // READ | ||||
|         cmd.push(0x02); // VARIABLE SIZED BLOCKS, SILI=1 | ||||
|         //cmd.push(0x00); // VARIABLE SIZED BLOCKS, SILI=0 | ||||
|                         //cmd.push(0x00); // VARIABLE SIZED BLOCKS, SILI=0 | ||||
|         cmd.push(((transfer_len >> 16) & 0xff) as u8); | ||||
|         cmd.push(((transfer_len >> 8) & 0xff) as u8); | ||||
|         cmd.push((transfer_len & 0xff) as u8); | ||||
| @ -683,23 +679,34 @@ impl SgTape { | ||||
|  | ||||
|         let data = match sg_raw.do_in_command(&cmd, buffer) { | ||||
|             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); | ||||
|             } | ||||
|             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); | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 return Err(BlockReadError::Error( | ||||
|                     proxmox_lang::io_format_err!("read failed - {}", err) | ||||
|                 )); | ||||
|                 return Err(BlockReadError::Error(proxmox_lang::io_format_err!( | ||||
|                     "read failed - {}", | ||||
|                     err | ||||
|                 ))); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         if data.len() != transfer_len { | ||||
|             return Err(BlockReadError::Error( | ||||
|                 proxmox_lang::io_format_err!("read failed - unexpected block len ({} != {})", data.len(), buffer.len()) | ||||
|             )); | ||||
|             return Err(BlockReadError::Error(proxmox_lang::io_format_err!( | ||||
|                 "read failed - unexpected block len ({} != {})", | ||||
|                 data.len(), | ||||
|                 buffer.len() | ||||
|             ))); | ||||
|         } | ||||
|  | ||||
|         Ok(transfer_len) | ||||
| @ -717,7 +724,6 @@ impl SgTape { | ||||
|  | ||||
|     /// Set all options we need/want | ||||
|     pub fn set_default_options(&mut self) -> Result<(), Error> { | ||||
|  | ||||
|         let compression = Some(true); | ||||
|         let block_length = Some(0); // variable length mode | ||||
|         let buffer_mode = Some(true); // Always use drive buffer | ||||
| @ -734,7 +740,6 @@ impl SgTape { | ||||
|         block_length: Option<u32>, | ||||
|         buffer_mode: Option<bool>, | ||||
|     ) -> Result<(), Error> { | ||||
|  | ||||
|         // Note: Read/Modify/Write | ||||
|  | ||||
|         let (mut head, mut block_descriptor, mut page) = self.read_compression_page()?; | ||||
| @ -766,7 +771,7 @@ impl SgTape { | ||||
|         let mut cmd = Vec::new(); | ||||
|         cmd.push(0x55); // MODE SELECT(10) | ||||
|         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; | ||||
|         cmd.extend(¶m_list_len.to_be_bytes()); | ||||
| @ -776,7 +781,8 @@ impl SgTape { | ||||
|  | ||||
|         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))?; | ||||
|  | ||||
|         Ok(()) | ||||
| @ -784,10 +790,16 @@ impl SgTape { | ||||
|  | ||||
|     fn read_medium_configuration_page( | ||||
|         &mut self, | ||||
|     ) -> Result<(ModeParameterHeader, ModeBlockDescriptor, MediumConfigurationModePage), Error> { | ||||
|  | ||||
|         let (head, block_descriptor, page): (_,_, MediumConfigurationModePage) | ||||
|             = scsi_mode_sense(&mut self.file, false, 0x1d, 0)?; | ||||
|     ) -> Result< | ||||
|         ( | ||||
|             ModeParameterHeader, | ||||
|             ModeBlockDescriptor, | ||||
|             MediumConfigurationModePage, | ||||
|         ), | ||||
|         Error, | ||||
|     > { | ||||
|         let (head, block_descriptor, page): (_, _, MediumConfigurationModePage) = | ||||
|             scsi_mode_sense(&mut self.file, false, 0x1d, 0)?; | ||||
|  | ||||
|         proxmox_lang::try_block!({ | ||||
|             if (page.page_code & 0b0011_1111) != 0x1d { | ||||
| @ -803,15 +815,22 @@ impl SgTape { | ||||
|             }; | ||||
|  | ||||
|             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( | ||||
|         &mut self, | ||||
|     ) -> Result<(ModeParameterHeader, ModeBlockDescriptor, DataCompressionModePage), Error> { | ||||
|  | ||||
|         let (head, block_descriptor, page): (_,_, DataCompressionModePage) | ||||
|             = scsi_mode_sense(&mut self.file, false, 0x0f, 0)?; | ||||
|     ) -> Result< | ||||
|         ( | ||||
|             ModeParameterHeader, | ||||
|             ModeBlockDescriptor, | ||||
|             DataCompressionModePage, | ||||
|         ), | ||||
|         Error, | ||||
|     > { | ||||
|         let (head, block_descriptor, page): (_, _, DataCompressionModePage) = | ||||
|             scsi_mode_sense(&mut self.file, false, 0x0f, 0)?; | ||||
|  | ||||
|         proxmox_lang::try_block!({ | ||||
|             if (page.page_code & 0b0011_1111) != 0x0f { | ||||
| @ -827,7 +846,8 @@ impl SgTape { | ||||
|             }; | ||||
|  | ||||
|             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 | ||||
| @ -835,7 +855,6 @@ impl SgTape { | ||||
|     /// We read the drive compression page, including the | ||||
|     /// block_descriptor. This is all information we need for now. | ||||
|     pub fn read_drive_status(&mut self) -> Result<LtoTapeStatus, Error> { | ||||
|  | ||||
|         // We do a Request Sense, but ignore the result. | ||||
|         // This clears deferred error or media changed events. | ||||
|         let _ = scsi_request_sense(&mut self.file); | ||||
| @ -852,11 +871,11 @@ impl SgTape { | ||||
|     } | ||||
|  | ||||
|     /// 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 alert_flags = self.tape_alert_flags() | ||||
|         let alert_flags = self | ||||
|             .tape_alert_flags() | ||||
|             .map(|flags| format!("{:?}", flags)) | ||||
|             .ok(); | ||||
|  | ||||
| @ -881,7 +900,6 @@ impl SgTape { | ||||
|         }; | ||||
|  | ||||
|         if self.test_unit_ready().is_ok() { | ||||
|  | ||||
|             if 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); | ||||
|  | ||||
|             if let Ok(mam) = self.cartridge_memory() { | ||||
|  | ||||
|                 let usage = mam_extract_media_usage(&mam)?; | ||||
|  | ||||
|                 status.manufactured = Some(usage.manufactured); | ||||
| @ -900,7 +917,6 @@ impl SgTape { | ||||
|                 status.bytes_written = Some(usage.bytes_written); | ||||
|  | ||||
|                 if let Ok(volume_stats) = self.volume_statistics() { | ||||
|  | ||||
|                     let passes = std::cmp::max( | ||||
|                         volume_stats.beginning_of_medium_passes, | ||||
|                         volume_stats.middle_of_tape_passes, | ||||
| @ -908,7 +924,7 @@ impl SgTape { | ||||
|  | ||||
|                     // assume max. 16000 medium passes | ||||
|                     // 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_wearout = Some(wearout); | ||||
| @ -920,7 +936,6 @@ impl SgTape { | ||||
|  | ||||
|         Ok(status) | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| impl Drop for SgTape { | ||||
| @ -932,31 +947,33 @@ impl Drop for SgTape { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| pub struct SgTapeReader<'a> { | ||||
|     sg_tape: &'a mut SgTape, | ||||
|     end_of_file: bool, | ||||
| } | ||||
|  | ||||
| impl <'a> SgTapeReader<'a> { | ||||
|  | ||||
| impl<'a> SgTapeReader<'a> { | ||||
|     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> { | ||||
|         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) { | ||||
|             Ok(usize) => Ok(usize), | ||||
|             Err(BlockReadError::EndOfFile) => { | ||||
|                 self.end_of_file = true; | ||||
|                 Err(BlockReadError::EndOfFile) | ||||
|             }, | ||||
|             } | ||||
|             Err(err) => Err(err), | ||||
|         } | ||||
|     } | ||||
| @ -967,15 +984,16 @@ pub struct SgTapeWriter<'a> { | ||||
|     _leom_sent: bool, | ||||
| } | ||||
|  | ||||
| impl <'a> SgTapeWriter<'a> { | ||||
|  | ||||
| impl<'a> SgTapeWriter<'a> { | ||||
|     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> { | ||||
|         self.sg_tape.write_block(buffer) | ||||
|     } | ||||
|  | ||||
| @ -1,20 +1,17 @@ | ||||
| use std::os::unix::prelude::AsRawFd; | ||||
| use std::io::Write; | ||||
| use std::os::unix::prelude::AsRawFd; | ||||
|  | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use endian_trait::Endian; | ||||
|  | ||||
| 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 | ||||
| /// | ||||
| /// We search for AES_GCM algorithm with 256bits key. | ||||
| pub fn has_encryption<F: AsRawFd>( | ||||
|     file: &mut F, | ||||
| ) -> bool { | ||||
|  | ||||
| pub fn has_encryption<F: AsRawFd>(file: &mut F) -> bool { | ||||
|     let data = match sg_spin_data_encryption_caps(file) { | ||||
|         Ok(data) => data, | ||||
|         Err(_) => return false, | ||||
| @ -25,11 +22,7 @@ pub fn has_encryption<F: AsRawFd>( | ||||
| /// Set or clear encryption key | ||||
| /// | ||||
| /// We always use mixed mode, | ||||
| pub fn set_encryption<F: AsRawFd>( | ||||
|     file: &mut F, | ||||
|     key: Option<[u8; 32]>, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
| pub fn set_encryption<F: AsRawFd>(file: &mut F, key: Option<[u8; 32]>) -> Result<(), Error> { | ||||
|     let data = match sg_spin_data_encryption_caps(file) { | ||||
|         Ok(data) => data, | ||||
|         Err(_) if key.is_none() => { | ||||
| @ -85,7 +78,6 @@ fn sg_spout_set_encryption<F: AsRawFd>( | ||||
|     algorythm_index: u8, | ||||
|     key: Option<[u8; 32]>, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     let mut sg_raw = SgRaw::new(file, 0)?; | ||||
|  | ||||
|     let mut outbuf_len = std::mem::size_of::<SspSetDataEncryptionPage>(); | ||||
| @ -106,7 +98,11 @@ fn sg_spout_set_encryption<F: AsRawFd>( | ||||
|         algorythm_index, | ||||
|         key_format: 0, | ||||
|         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[..]; | ||||
| @ -119,58 +115,72 @@ fn sg_spout_set_encryption<F: AsRawFd>( | ||||
|     let mut cmd = Vec::new(); | ||||
|     cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT) | ||||
|     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.extend(&(outbuf_len as u32).to_be_bytes()); // data out len | ||||
|     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)) | ||||
| } | ||||
|  | ||||
| // 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> { | ||||
|  | ||||
|     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 cmd = Vec::new(); | ||||
|     cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN) | ||||
|     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.extend(&allocation_len.to_be_bytes()); | ||||
|     cmd.push(0); | ||||
|     cmd.push(0); | ||||
|  | ||||
|     sg_raw.do_command(&cmd) | ||||
|         .map_err(|err| format_err!("read data encryption status SPIN(20h[0020h]) failed - {}", err)) | ||||
|     sg_raw | ||||
|         .do_command(&cmd) | ||||
|         .map_err(|err| { | ||||
|             format_err!( | ||||
|                 "read data encryption status SPIN(20h[0020h]) failed - {}", | ||||
|                 err | ||||
|             ) | ||||
|         }) | ||||
|         .map(|v| v.to_vec()) | ||||
| } | ||||
|  | ||||
| // 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> { | ||||
|  | ||||
|     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 cmd = Vec::new(); | ||||
|     cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN) | ||||
|     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.extend(&allocation_len.to_be_bytes()); | ||||
|     cmd.push(0); | ||||
|     cmd.push(0); | ||||
|  | ||||
|     sg_raw.do_command(&cmd) | ||||
|         .map_err(|err| format_err!("read data encryption caps SPIN(20h[0010h]) failed - {}", err)) | ||||
|     sg_raw | ||||
|         .do_command(&cmd) | ||||
|         .map_err(|err| { | ||||
|             format_err!( | ||||
|                 "read data encryption caps SPIN(20h[0010h]) failed - {}", | ||||
|                 err | ||||
|             ) | ||||
|         }) | ||||
|         .map(|v| v.to_vec()) | ||||
| } | ||||
|  | ||||
| @ -215,7 +225,6 @@ struct SspDataEncryptionAlgorithmDescriptor { | ||||
|  | ||||
| // Returns the algorythm_index for AES-GCM | ||||
| fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> { | ||||
|  | ||||
|     proxmox_lang::try_block!({ | ||||
|         let mut reader = data; | ||||
|         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; | ||||
|  | ||||
|         loop { | ||||
|             if reader.is_empty() { break; }; | ||||
|             let desc: SspDataEncryptionAlgorithmDescriptor = | ||||
|                 unsafe { reader.read_be_value()? }; | ||||
|             if reader.is_empty() { | ||||
|                 break; | ||||
|             }; | ||||
|             let desc: SspDataEncryptionAlgorithmDescriptor = unsafe { reader.read_be_value()? }; | ||||
|             if desc.descriptor_len != 0x14 { | ||||
|                 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), | ||||
|             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)] | ||||
| @ -266,7 +276,6 @@ struct SspDataEncryptionStatusPage { | ||||
| } | ||||
|  | ||||
| fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatus, Error> { | ||||
|  | ||||
|     proxmox_lang::try_block!({ | ||||
|         let mut reader = data; | ||||
|         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"), | ||||
|         }; | ||||
|  | ||||
|         let status = DataEncryptionStatus { | ||||
|             mode, | ||||
|         }; | ||||
|         let status = DataEncryptionStatus { mode }; | ||||
|  | ||||
|         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)) | ||||
| } | ||||
|  | ||||
| @ -17,7 +17,7 @@ use super::TapeAlertFlags; | ||||
| // see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1 | ||||
|  | ||||
| #[derive(Endian)] | ||||
| #[repr(C,packed)] | ||||
| #[repr(C, packed)] | ||||
| struct MamAttributeHeader { | ||||
|     id: u16, | ||||
|     flags: u8, | ||||
| @ -30,8 +30,13 @@ enum MamFormat { | ||||
|     DEC, | ||||
| } | ||||
|  | ||||
| static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[ | ||||
|     (0x00_00, 8, MamFormat::DEC, "Remaining Capacity In Partition"), | ||||
| static MAM_ATTRIBUTES: &[(u16, u16, MamFormat, &str)] = &[ | ||||
|     ( | ||||
|         0x00_00, | ||||
|         8, | ||||
|         MamFormat::DEC, | ||||
|         "Remaining Capacity In Partition", | ||||
|     ), | ||||
|     (0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"), | ||||
|     (0x00_02, 8, MamFormat::DEC, "Tapealert Flags"), | ||||
|     (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_07, 2, MamFormat::DEC, "Initialization Count"), | ||||
|     (0x00_09, 4, MamFormat::BINARY, "Volume Change Reference"), | ||||
|  | ||||
|     (0x02_0A, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Last Load"), | ||||
|     (0x02_0B, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-1"), | ||||
|     (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"), | ||||
|  | ||||
|     ( | ||||
|         0x02_0A, | ||||
|         40, | ||||
|         MamFormat::ASCII, | ||||
|         "Device Vendor/Serial Number at Last Load", | ||||
|     ), | ||||
|     ( | ||||
|         0x02_0B, | ||||
|         40, | ||||
|         MamFormat::ASCII, | ||||
|         "Device Vendor/Serial Number at Load-1", | ||||
|     ), | ||||
|     ( | ||||
|         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_01, 32, MamFormat::ASCII, "Medium Serial Number"), | ||||
|     (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_09, 2, MamFormat::BINARY, "Medium Type Information"), | ||||
|     (0x04_0B, 10, MamFormat::BINARY, "Supported Density Codes"), | ||||
|  | ||||
|     (0x08_00, 8, MamFormat::ASCII, "Application Vendor"), | ||||
|     (0x08_01, 32, MamFormat::ASCII, "Application Name"), | ||||
|     (0x08_02, 8, MamFormat::ASCII, "Application Version"), | ||||
|     (0x08_03, 160, MamFormat::ASCII, "User Medium Text Label"), | ||||
|     (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_07, 80, MamFormat::ASCII, "Owning Host Textual Name"), | ||||
|     (0x08_08, 160, MamFormat::ASCII, "Media Pool"), | ||||
|     (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_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)"), | ||||
|  | ||||
|     ( | ||||
|         0x08_0C, | ||||
|         50, | ||||
|         MamFormat::ASCII, | ||||
|         "Volume Coherency Information", | ||||
|     ), | ||||
|     ( | ||||
|         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)> = { | ||||
|         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> { | ||||
|  | ||||
|     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 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(&[0u8, 0u8]); | ||||
|  | ||||
|     sg_raw.do_command(&cmd) | ||||
|     sg_raw | ||||
|         .do_command(&cmd) | ||||
|         .map_err(|err| format_err!("read cartidge memory failed - {}", err)) | ||||
|         .map(|v| v.to_vec()) | ||||
| } | ||||
|  | ||||
| /// 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> { | ||||
|  | ||||
|     let data = read_tape_mam(file)?; | ||||
|  | ||||
|     decode_mam_attributes(&data) | ||||
| } | ||||
|  | ||||
| fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> { | ||||
|  | ||||
|     let mut reader = data; | ||||
|  | ||||
|     let data_len: u32 = unsafe { reader.read_be_value()? }; | ||||
|  | ||||
|     let expected_len = data_len as usize; | ||||
|  | ||||
|  | ||||
|     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 { | ||||
|         // 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(); | ||||
| @ -143,7 +223,7 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> { | ||||
|         if reader.is_empty() { | ||||
|             break; | ||||
|         } | ||||
|         let head: MamAttributeHeader =  unsafe { reader.read_be_value()? }; | ||||
|         let head: MamAttributeHeader = unsafe { reader.read_be_value()? }; | ||||
|         //println!("GOT ID {:04X} {:08b} {}", head.id, head.flags, head.len); | ||||
|  | ||||
|         let head_id = head.id; | ||||
| @ -164,7 +244,8 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> { | ||||
|                         } else if info.1 == 4 { | ||||
|                             format!("{}", u32::from_be_bytes(data[0..4].try_into()?)) | ||||
|                         } 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 flags = TapeAlertFlags::from_bits_truncate(value); | ||||
|                                 format!("{:?}", flags) | ||||
| @ -174,7 +255,7 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> { | ||||
|                         } else { | ||||
|                             unreachable!(); | ||||
|                         } | ||||
|                     }, | ||||
|                     } | ||||
|                     MamFormat::BINARY => hex::encode(&data), | ||||
|                 }; | ||||
|                 list.push(MamAttribute { | ||||
| @ -183,7 +264,10 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> { | ||||
|                     value, | ||||
|                 }); | ||||
|             } 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 { | ||||
|             // skip unknown IDs | ||||
| @ -201,8 +285,11 @@ pub struct MediaUsageInfo { | ||||
|  | ||||
| /// Extract Media Usage Information from Cartridge Memory | ||||
| pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, Error> { | ||||
|  | ||||
|    let manufactured: i64 = match mam.iter().find(|v| v.id == 0x04_06).map(|v| v.value.clone()) { | ||||
|     let manufactured: i64 = match mam | ||||
|         .iter() | ||||
|         .find(|v| v.id == 0x04_06) | ||||
|         .map(|v| v.value.clone()) | ||||
|     { | ||||
|         Some(date_str) => { | ||||
|             if date_str.len() != 8 { | ||||
|                 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'"), | ||||
|     }; | ||||
|  | ||||
|     let bytes_written: u64 = match mam.iter().find(|v| v.id == 0x02_20).map(|v| v.value.clone()) { | ||||
|         Some(read_str) => read_str.parse::<u64>()? * 1024*1024, | ||||
|     let bytes_written: u64 = match mam | ||||
|         .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'"), | ||||
|     }; | ||||
|  | ||||
|     let bytes_read: u64 = match mam.iter().find(|v| v.id == 0x02_21).map(|v| v.value.clone()) { | ||||
|         Some(read_str) => read_str.parse::<u64>()? * 1024*1024, | ||||
|     let bytes_read: u64 = match mam | ||||
|         .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'"), | ||||
|     }; | ||||
|  | ||||
|     Ok(MediaUsageInfo { manufactured, bytes_written, bytes_read }) | ||||
|     Ok(MediaUsageInfo { | ||||
|         manufactured, | ||||
|         bytes_written, | ||||
|         bytes_read, | ||||
|     }) | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use std::io::Read; | ||||
| use endian_trait::Endian; | ||||
| use std::io::Read; | ||||
| use std::os::unix::io::AsRawFd; | ||||
|  | ||||
| use proxmox_io::ReadExt; | ||||
| @ -26,14 +26,15 @@ struct DesnityDescriptorBlock { | ||||
| // Returns the maximum supported drive density code | ||||
| pub fn report_density<F: AsRawFd>(file: &mut F) -> Result<u8, Error> { | ||||
|     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(); | ||||
|     cmd.extend(&[0x44, 0, 0, 0, 0, 0, 0]); // REPORT DENSITY SUPPORT (MEDIA=0) | ||||
|     cmd.extend(&alloc_len.to_be_bytes()); // alloc len | ||||
|     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))?; | ||||
|  | ||||
|     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()); | ||||
|         } else { | ||||
|             // 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]; | ||||
|         reader.read_exact(&mut reserved)?; | ||||
|  | ||||
|         loop { | ||||
|             if reader.is_empty() { break; } | ||||
|             if reader.is_empty() { | ||||
|                 break; | ||||
|             } | ||||
|             let block: DesnityDescriptorBlock = unsafe { reader.read_be_value()? }; | ||||
|             if block.primary_density_code > max_density { | ||||
|                 max_density = block.primary_density_code; | ||||
| @ -62,8 +65,8 @@ pub fn report_density<F: AsRawFd>(file: &mut F) -> Result<u8, Error> { | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|  | ||||
|     }).map_err(|err| format_err!("decode report density failed - {}", err))?; | ||||
|     }) | ||||
|     .map_err(|err| format_err!("decode report density failed - {}", err))?; | ||||
|  | ||||
|     Ok(max_density) | ||||
| } | ||||
|  | ||||
| @ -7,7 +7,7 @@ use proxmox_io::ReadExt; | ||||
|  | ||||
| use crate::sgutils2::SgRaw; | ||||
|  | ||||
| bitflags::bitflags!{ | ||||
| bitflags::bitflags! { | ||||
|  | ||||
|     /// Tape Alert Flags | ||||
|     /// | ||||
| @ -73,16 +73,13 @@ bitflags::bitflags!{ | ||||
| } | ||||
|  | ||||
| /// 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)?; | ||||
|  | ||||
|     decode_tape_alert_flags(&data) | ||||
| } | ||||
|  | ||||
|  | ||||
| fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> { | ||||
|  | ||||
|     let mut sg_raw = SgRaw::new(file, 512)?; | ||||
|  | ||||
|     // 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(); | ||||
|     cmd.push(0x4D); // LOG SENSE | ||||
|     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); | ||||
| @ -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.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(|v| v.to_vec()) | ||||
| } | ||||
|  | ||||
| fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> { | ||||
|  | ||||
|     proxmox_lang::try_block!({ | ||||
|         if !((data[0] & 0x7f) == 0x12 && data[1] == 0) { | ||||
|             bail!("invalid response"); | ||||
| @ -130,36 +127,36 @@ fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> { | ||||
|             bail!("invalid parameter length"); | ||||
|         } | ||||
|  | ||||
|         let mut value: u64 =  unsafe { reader.read_be_value()? }; | ||||
|         let mut value: u64 = unsafe { reader.read_be_value()? }; | ||||
|  | ||||
|         // bits are in wrong order, reverse them | ||||
|         value = value.reverse_bits(); | ||||
|  | ||||
|         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 = | ||||
| TapeAlertFlags::MEDIA.bits() | | ||||
| TapeAlertFlags::WRITE_FAILURE.bits() | | ||||
| TapeAlertFlags::READ_FAILURE.bits() | | ||||
| TapeAlertFlags::WRITE_PROTECT.bits() | | ||||
| TapeAlertFlags::UNRECOVERABLE_SNAPPED_TAPE.bits() | | ||||
| TapeAlertFlags::FORCED_EJECT.bits() | | ||||
| TapeAlertFlags::EXPIRED_CLEANING_MEDIA.bits() | | ||||
| TapeAlertFlags::INVALID_CLEANING_TAPE.bits() | | ||||
| TapeAlertFlags::HARDWARE_A.bits() | | ||||
| TapeAlertFlags::HARDWARE_B.bits() | | ||||
| TapeAlertFlags::EJECT_MEDIA.bits() | | ||||
| TapeAlertFlags::PREDICTIVE_FAILURE.bits() | | ||||
| TapeAlertFlags::LOADER_STRAY_TAPE.bits() | | ||||
| TapeAlertFlags::LOADER_MAGAZINE.bits() | | ||||
| TapeAlertFlags::TAPE_SYSTEM_AREA_WRITE_FAILURE.bits() | | ||||
| TapeAlertFlags::TAPE_SYSTEM_AREA_READ_FAILURE.bits() | | ||||
| TapeAlertFlags::NO_START_OF_DATA.bits() | | ||||
| TapeAlertFlags::LOADING_FAILURE.bits() | | ||||
| TapeAlertFlags::UNRECOVERABLE_UNLOAD_FAILURE.bits() | | ||||
| TapeAlertFlags::AUTOMATION_INTERFACE_FAILURE.bits(); | ||||
| const CRITICAL_FLAG_MASK: u64 = TapeAlertFlags::MEDIA.bits() | ||||
|     | TapeAlertFlags::WRITE_FAILURE.bits() | ||||
|     | TapeAlertFlags::READ_FAILURE.bits() | ||||
|     | TapeAlertFlags::WRITE_PROTECT.bits() | ||||
|     | TapeAlertFlags::UNRECOVERABLE_SNAPPED_TAPE.bits() | ||||
|     | TapeAlertFlags::FORCED_EJECT.bits() | ||||
|     | TapeAlertFlags::EXPIRED_CLEANING_MEDIA.bits() | ||||
|     | TapeAlertFlags::INVALID_CLEANING_TAPE.bits() | ||||
|     | TapeAlertFlags::HARDWARE_A.bits() | ||||
|     | TapeAlertFlags::HARDWARE_B.bits() | ||||
|     | TapeAlertFlags::EJECT_MEDIA.bits() | ||||
|     | TapeAlertFlags::PREDICTIVE_FAILURE.bits() | ||||
|     | TapeAlertFlags::LOADER_STRAY_TAPE.bits() | ||||
|     | TapeAlertFlags::LOADER_MAGAZINE.bits() | ||||
|     | TapeAlertFlags::TAPE_SYSTEM_AREA_WRITE_FAILURE.bits() | ||||
|     | TapeAlertFlags::TAPE_SYSTEM_AREA_READ_FAILURE.bits() | ||||
|     | TapeAlertFlags::NO_START_OF_DATA.bits() | ||||
|     | TapeAlertFlags::LOADING_FAILURE.bits() | ||||
|     | TapeAlertFlags::UNRECOVERABLE_UNLOAD_FAILURE.bits() | ||||
|     | TapeAlertFlags::AUTOMATION_INTERFACE_FAILURE.bits(); | ||||
|  | ||||
| /// Check if tape-alert-flags contains critial errors. | ||||
| 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 = | ||||
| TapeAlertFlags::MEDIA_LIFE.bits() | | ||||
| TapeAlertFlags::NEARING_MEDIA_LIFE.bits(); | ||||
|     TapeAlertFlags::MEDIA_LIFE.bits() | TapeAlertFlags::NEARING_MEDIA_LIFE.bits(); | ||||
|  | ||||
| /// Check if tape-alert-flags indicates media-life end | ||||
| 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 = | ||||
| TapeAlertFlags::CLEAN_NOW.bits() | | ||||
| TapeAlertFlags::CLEAN_PERIODIC.bits(); | ||||
|     TapeAlertFlags::CLEAN_NOW.bits() | TapeAlertFlags::CLEAN_PERIODIC.bits(); | ||||
|  | ||||
| /// Check if tape-alert-flags indicates media cleaning request | ||||
| pub fn tape_alert_flags_cleaning_request(flags: TapeAlertFlags) -> bool { | ||||
|  | ||||
| @ -16,22 +16,20 @@ use crate::sgutils2::SgRaw; | ||||
| /// | ||||
| /// The Volume Statistics log page is included in Ultrium 5 and later | ||||
| /// 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)?; | ||||
|  | ||||
|     decode_volume_statistics(&data) | ||||
| } | ||||
|  | ||||
| fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> { | ||||
|  | ||||
|     let alloc_len: u16 = 8192; | ||||
|     let mut sg_raw = SgRaw::new(file, alloc_len as usize)?; | ||||
|  | ||||
|     let mut cmd = Vec::new(); | ||||
|     cmd.push(0x4D); // LOG SENSE | ||||
|     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); | ||||
|     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.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(|v| v.to_vec()) | ||||
| } | ||||
| @ -53,8 +52,6 @@ struct LpParameterHeader { | ||||
| } | ||||
|  | ||||
| fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> { | ||||
|  | ||||
|  | ||||
|     let read_be_counter = |reader: &mut &[u8], len: u8| { | ||||
|         let len = len as usize; | ||||
|         if len == 0 || len > 8 { | ||||
| @ -86,7 +83,7 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> | ||||
|             bail!("invalid page length"); | ||||
|         } else { | ||||
|             // 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(); | ||||
| @ -101,14 +98,13 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> | ||||
|             match head.parameter_code { | ||||
|                 0x0000 => { | ||||
|                     let value: u64 = read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                      if value == 0 { | ||||
|                          bail!("page-valid flag not set"); | ||||
|                     if value == 0 { | ||||
|                         bail!("page-valid flag not set"); | ||||
|                     } | ||||
|                     page_valid = true; | ||||
|                 } | ||||
|                 0x0001 => { | ||||
|                     stat.volume_mounts = | ||||
|                         read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                     stat.volume_mounts = read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                 } | ||||
|                 0x0002 => { | ||||
|                     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)?; | ||||
|                 } | ||||
|                 0x0007 => { | ||||
|                     stat.volume_datasets_read = | ||||
|                         read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                     stat.volume_datasets_read = read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                 } | ||||
|                 0x0008 => { | ||||
|                     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)?; | ||||
|                 } | ||||
|                 0x0014 => { | ||||
|                     stat.medium_mount_time = | ||||
|                         read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                     stat.medium_mount_time = read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                 } | ||||
|                 0x0015 => { | ||||
|                     stat.medium_ready_time = | ||||
|                         read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                     stat.medium_ready_time = read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                 } | ||||
|                 0x0016 => { | ||||
|                     stat.total_native_capacity = | ||||
| @ -207,12 +200,11 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> | ||||
|                     } | ||||
|                 } | ||||
|                 0x0101 => { | ||||
|                    stat.beginning_of_medium_passes = | ||||
|                     stat.beginning_of_medium_passes = | ||||
|                         read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                 } | ||||
|                 0x0102 => { | ||||
|                    stat.middle_of_tape_passes = | ||||
|                         read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                     stat.middle_of_tape_passes = read_be_counter(&mut reader, head.parameter_len)?; | ||||
|                 } | ||||
|                 _ => { | ||||
|                     reader.read_exact_allocated(head.parameter_len as usize)?; | ||||
| @ -225,6 +217,6 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> | ||||
|         } | ||||
|  | ||||
|         Ok(stat) | ||||
|  | ||||
|     }).map_err(|err| format_err!("decode volume statistics failed - {}", err)) | ||||
|     }) | ||||
|     .map_err(|err| format_err!("decode volume statistics failed - {}", err)) | ||||
| } | ||||
|  | ||||
| @ -25,9 +25,7 @@ pub struct SenseInfo { | ||||
| } | ||||
|  | ||||
| impl std::fmt::Display for SenseInfo { | ||||
|  | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|  | ||||
|         let sense_text = SENSE_KEY_DESCRIPTIONS | ||||
|             .get(self.sense_key as usize) | ||||
|             .map(|s| String::from(*s)) | ||||
| @ -58,7 +56,9 @@ impl From<std::io::Error> for ScsiError { | ||||
|  | ||||
| // Opaque wrapper for sg_pt_base | ||||
| #[repr(C)] | ||||
| struct SgPtBase { _private: [u8; 0] } | ||||
| struct SgPtBase { | ||||
|     _private: [u8; 0], | ||||
| } | ||||
|  | ||||
| #[repr(transparent)] | ||||
| struct SgPt { | ||||
| @ -97,7 +97,7 @@ pub const PERIPHERAL_DEVICE_TYPE_TEXT: [&str; 32] = [ | ||||
|     "Printer", | ||||
|     "Processor", | ||||
|     "Write-once", | ||||
|     "CD-ROM",  // 05h | ||||
|     "CD-ROM", // 05h | ||||
|     "Scanner", | ||||
|     "Optical", | ||||
|     "Medium Changer", // 08h | ||||
| @ -127,19 +127,19 @@ pub const PERIPHERAL_DEVICE_TYPE_TEXT: [&str; 32] = [ | ||||
| ]; | ||||
|  | ||||
| //  SENSE KEYS | ||||
| pub const SENSE_KEY_NO_SENSE: u8        = 0x00; | ||||
| pub const SENSE_KEY_NO_SENSE: u8 = 0x00; | ||||
| pub const SENSE_KEY_RECOVERED_ERROR: u8 = 0x01; | ||||
| pub const SENSE_KEY_NOT_READY: u8       = 0x02; | ||||
| pub const SENSE_KEY_MEDIUM_ERROR: u8    = 0x03; | ||||
| pub const SENSE_KEY_HARDWARE_ERROR: u8  = 0x04; | ||||
| pub const SENSE_KEY_NOT_READY: u8 = 0x02; | ||||
| pub const SENSE_KEY_MEDIUM_ERROR: u8 = 0x03; | ||||
| pub const SENSE_KEY_HARDWARE_ERROR: u8 = 0x04; | ||||
| pub const SENSE_KEY_ILLEGAL_REQUEST: u8 = 0x05; | ||||
| pub const SENSE_KEY_UNIT_ATTENTION: u8  = 0x06; | ||||
| pub const SENSE_KEY_DATA_PROTECT: u8    = 0x07; | ||||
| pub const SENSE_KEY_BLANK_CHECK: u8     = 0x08; | ||||
| pub const SENSE_KEY_COPY_ABORTED: u8    = 0x0a; | ||||
| pub const SENSE_KEY_UNIT_ATTENTION: u8 = 0x06; | ||||
| pub const SENSE_KEY_DATA_PROTECT: u8 = 0x07; | ||||
| pub const SENSE_KEY_BLANK_CHECK: u8 = 0x08; | ||||
| pub const SENSE_KEY_COPY_ABORTED: u8 = 0x0a; | ||||
| pub const SENSE_KEY_ABORTED_COMMAND: u8 = 0x0b; | ||||
| pub const SENSE_KEY_VOLUME_OVERFLOW: u8 = 0x0d; | ||||
| pub const SENSE_KEY_MISCOMPARE: u8      = 0x0e; | ||||
| pub const SENSE_KEY_MISCOMPARE: u8 = 0x0e; | ||||
|  | ||||
| /// Sense Key Descriptions | ||||
| pub const SENSE_KEY_DESCRIPTIONS: [&str; 16] = [ | ||||
| @ -184,9 +184,9 @@ pub struct RequestSenseFixed { | ||||
|     pub response_code: u8, | ||||
|     obsolete: u8, | ||||
|     pub flags2: u8, | ||||
|     pub information: [u8;4], | ||||
|     pub information: [u8; 4], | ||||
|     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_qualifier: u8, | ||||
|     pub field_replacable_unit_code: u8, | ||||
| @ -195,12 +195,12 @@ pub struct RequestSenseFixed { | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| #[derive(Endian, Debug)] | ||||
| struct RequestSenseDescriptor{ | ||||
| struct RequestSenseDescriptor { | ||||
|     response_code: u8, | ||||
|     sense_key: u8, | ||||
|     additional_sense_code: u8, | ||||
|     additional_sense_code_qualifier: u8, | ||||
|     reserved: [u8;4], | ||||
|     reserved: [u8; 4], | ||||
|     additional_sense_len: u8, | ||||
| } | ||||
|  | ||||
| @ -228,12 +228,11 @@ pub struct ModeParameterHeader { | ||||
|     // not compatible with IBM. | ||||
|     pub medium_type: u8, | ||||
|     pub flags3: u8, | ||||
|     reserved4: [u8;2], | ||||
|     reserved4: [u8; 2], | ||||
|     pub block_descriptior_len: u16, | ||||
| } | ||||
|  | ||||
| impl ModeParameterHeader { | ||||
|  | ||||
|     pub fn buffer_mode(&self) -> u8 { | ||||
|         (self.flags3 & 0b0111_0000) >> 4 | ||||
|     } | ||||
| @ -256,18 +255,16 @@ impl ModeParameterHeader { | ||||
| /// SCSI ModeBlockDescriptor for Tape devices | ||||
| pub struct ModeBlockDescriptor { | ||||
|     pub density_code: u8, | ||||
|     pub number_of_blocks: [u8;3], | ||||
|     pub number_of_blocks: [u8; 3], | ||||
|     reserverd: u8, | ||||
|     pub block_length: [u8; 3], | ||||
| } | ||||
|  | ||||
| impl ModeBlockDescriptor { | ||||
|  | ||||
|     pub fn block_length(&self) -> u32 { | ||||
|         ((self.block_length[0] as u32) << 16) + | ||||
|             ((self.block_length[1] as u32) << 8) + | ||||
|             (self.block_length[2] as u32) | ||||
|  | ||||
|         ((self.block_length[0] as u32) << 16) | ||||
|             + ((self.block_length[1] as u32) << 8) | ||||
|             + (self.block_length[2] as u32) | ||||
|     } | ||||
|  | ||||
|     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_BAD_PARAMS:c_int = 1; | ||||
| pub const SCSI_PT_DO_TIMEOUT:c_int = 2; | ||||
| 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_TIMEOUT: c_int = 2; | ||||
|  | ||||
| pub const SCSI_PT_RESULT_GOOD:c_int = 0; | ||||
| pub const SCSI_PT_RESULT_STATUS:c_int = 1; | ||||
| pub const SCSI_PT_RESULT_SENSE:c_int = 2; | ||||
| 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_GOOD: c_int = 0; | ||||
| pub const SCSI_PT_RESULT_STATUS: c_int = 1; | ||||
| pub const SCSI_PT_RESULT_SENSE: c_int = 2; | ||||
| pub const SCSI_PT_RESULT_TRANSPORT_ERR: c_int = 3; | ||||
| pub const SCSI_PT_RESULT_OS_ERR: c_int = 4; | ||||
|  | ||||
| #[link(name = "sgutils2")] | ||||
| extern "C" { | ||||
|  | ||||
|     #[allow(dead_code)] | ||||
|     fn scsi_pt_open_device( | ||||
|         device_name: * const c_char, | ||||
|         read_only: bool, | ||||
|         verbose: c_int, | ||||
|     ) -> c_int; | ||||
|     fn scsi_pt_open_device(device_name: *const c_char, read_only: bool, verbose: c_int) -> c_int; | ||||
|  | ||||
|     fn sg_is_scsi_cdb( | ||||
|         cdbp: *const u8, | ||||
|         clen: c_int, | ||||
|     ) -> bool; | ||||
|     fn sg_is_scsi_cdb(cdbp: *const u8, clen: c_int) -> bool; | ||||
|  | ||||
|     fn construct_scsi_pt_obj() -> *mut SgPtBase; | ||||
|     fn destruct_scsi_pt_obj(objp: *mut SgPtBase); | ||||
|  | ||||
|     fn set_scsi_pt_data_in( | ||||
|         objp: *mut SgPtBase, | ||||
|         dxferp: *mut u8, | ||||
|         dxfer_ilen: c_int, | ||||
|     ); | ||||
|     fn set_scsi_pt_data_in(objp: *mut SgPtBase, dxferp: *mut u8, dxfer_ilen: c_int); | ||||
|  | ||||
|     fn set_scsi_pt_data_out( | ||||
|         objp: *mut SgPtBase, | ||||
|         dxferp: *const u8, | ||||
|         dxfer_olen: c_int, | ||||
|     ); | ||||
|     fn set_scsi_pt_data_out(objp: *mut SgPtBase, dxferp: *const u8, dxfer_olen: c_int); | ||||
|  | ||||
|     fn set_scsi_pt_cdb( | ||||
|         objp: *mut SgPtBase, | ||||
|         cdb: *const u8, | ||||
|         cdb_len: c_int, | ||||
|     ); | ||||
|     fn set_scsi_pt_cdb(objp: *mut SgPtBase, cdb: *const u8, cdb_len: c_int); | ||||
|  | ||||
|     fn set_scsi_pt_sense( | ||||
|         objp: *mut SgPtBase, | ||||
|         sense: *mut u8, | ||||
|         max_sense_len: c_int, | ||||
|     ); | ||||
|     fn set_scsi_pt_sense(objp: *mut SgPtBase, sense: *mut u8, max_sense_len: c_int); | ||||
|  | ||||
|     fn do_scsi_pt( | ||||
|         objp: *mut SgPtBase, | ||||
|         fd: c_int, | ||||
|         timeout_secs: c_int, | ||||
|         verbose: c_int, | ||||
|     ) -> c_int; | ||||
|     fn do_scsi_pt(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; | ||||
|  | ||||
| @ -352,10 +321,10 @@ extern "C" { | ||||
|  | ||||
|     fn sg_get_asc_ascq_str( | ||||
|         asc: c_int, | ||||
|         ascq:c_int, | ||||
|         ascq: c_int, | ||||
|         buff_len: c_int, | ||||
|         buffer: *mut c_char, | ||||
|     ) -> * const c_char; | ||||
|     ) -> *const c_char; | ||||
| } | ||||
|  | ||||
| /// Safe interface to run RAW SCSI commands | ||||
| @ -368,31 +337,30 @@ pub struct SgRaw<'a, F> { | ||||
|  | ||||
| /// Get the string associated with ASC/ASCQ values | ||||
| pub fn get_asc_ascq_string(asc: u8, ascq: u8) -> String { | ||||
|  | ||||
|     let mut buffer = [0u8; 1024]; | ||||
|     let res = unsafe { | ||||
|         sg_get_asc_ascq_str( | ||||
|             asc as c_int, | ||||
|             ascq 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!({ | ||||
|         if res.is_null() { // just to be safe | ||||
|         if res.is_null() { | ||||
|             // just to be safe | ||||
|             bail!("unexpected NULL ptr"); | ||||
|         } | ||||
|         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 | ||||
| /// | ||||
| /// 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 layout = std::alloc::Layout::from_size_align(buffer_size, page_size)?; | ||||
|     let dinp = unsafe { std::alloc::alloc_zeroed(layout) }; | ||||
| @ -400,28 +368,31 @@ pub fn alloc_page_aligned_buffer(buffer_size: usize) -> Result<Box<[u8]> , Error | ||||
|         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) }) | ||||
| } | ||||
|  | ||||
| impl <'a, F: AsRawFd> SgRaw<'a, F> { | ||||
|  | ||||
| impl<'a, F: AsRawFd> SgRaw<'a, F> { | ||||
|     /// Create a new instance to run commands | ||||
|     /// | ||||
|     /// The file must be a handle to a SCSI device. | ||||
|     pub fn new(file: &'a mut F, buffer_size: usize) -> Result<Self, Error> { | ||||
|  | ||||
|         let buffer; | ||||
|  | ||||
|         if buffer_size > 0 { | ||||
|             buffer = alloc_page_aligned_buffer(buffer_size)?; | ||||
|         } else { | ||||
|             buffer =  Box::new([]); | ||||
|             buffer = Box::new([]); | ||||
|         } | ||||
|  | ||||
|         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)) | ||||
| @ -435,7 +406,6 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> { | ||||
|  | ||||
|     // create new object with initialized data_in and sense buffer | ||||
|     fn create_scsi_pt_obj(&mut self) -> Result<SgPt, Error> { | ||||
|  | ||||
|         let mut ptvp = SgPt::new()?; | ||||
|  | ||||
|         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> { | ||||
|  | ||||
|         let res = unsafe { do_scsi_pt(ptvp.as_mut_ptr(), self.file.as_raw_fd(), self.timeout, 0) }; | ||||
|         match res { | ||||
|             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_START_OK => { /* Ok */ } | ||||
|             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()), | ||||
|             code if code < 0 => { | ||||
|                 let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) }; | ||||
|                 let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno)); | ||||
|                 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 { | ||||
| @ -490,7 +463,9 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> { | ||||
|             SCSI_PT_RESULT_STATUS => { | ||||
|                 let status = unsafe { get_scsi_pt_status_response(ptvp.as_ptr()) }; | ||||
|                 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(()) | ||||
|             } | ||||
| @ -521,28 +496,39 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> { | ||||
|                         } | ||||
|                     } | ||||
|                     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 => { | ||||
|                         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)) | ||||
|             } | ||||
|             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 => { | ||||
|                 let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) }; | ||||
|                 let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno)); | ||||
|                 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 | ||||
|     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) } { | ||||
|             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()?; | ||||
|  | ||||
|         unsafe { | ||||
|             set_scsi_pt_cdb( | ||||
|                 ptvp.as_mut_ptr(), | ||||
|                 cmd.as_ptr(), | ||||
|                 cmd.len() as c_int, | ||||
|             ) | ||||
|         }; | ||||
|         unsafe { set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int) }; | ||||
|  | ||||
|         self.do_scsi_pt_checked(&mut ptvp)?; | ||||
|  | ||||
|         let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize; | ||||
|         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; | ||||
|  | ||||
| @ -573,8 +555,11 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> { | ||||
|     } | ||||
|  | ||||
|     /// 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) } { | ||||
|             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()?; | ||||
|  | ||||
|         unsafe { | ||||
|             set_scsi_pt_data_in( | ||||
|                 ptvp.as_mut_ptr(), | ||||
|                 data.as_mut_ptr(), | ||||
|                 data.len() as c_int, | ||||
|             ); | ||||
|             set_scsi_pt_data_in(ptvp.as_mut_ptr(), data.as_mut_ptr(), data.len() as c_int); | ||||
|  | ||||
|             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)?; | ||||
| @ -604,7 +581,9 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> { | ||||
|         let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize; | ||||
|  | ||||
|         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; | ||||
|  | ||||
| @ -615,30 +594,21 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> { | ||||
|     /// | ||||
|     /// Note: use alloc_page_aligned_buffer to alloc data transfer buffer | ||||
|     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) } { | ||||
|             return Err(format_err!("no valid SCSI command").into()); | ||||
|         } | ||||
|  | ||||
|         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()); | ||||
|         } | ||||
|  | ||||
|         let mut ptvp = self.create_scsi_pt_obj()?; | ||||
|  | ||||
|         unsafe { | ||||
|             set_scsi_pt_data_out( | ||||
|                 ptvp.as_mut_ptr(), | ||||
|                 data.as_ptr(), | ||||
|                 data.len() as c_int, | ||||
|             ); | ||||
|             set_scsi_pt_data_out(ptvp.as_mut_ptr(), data.as_ptr(), data.len() as c_int); | ||||
|  | ||||
|             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)?; | ||||
| @ -660,10 +630,7 @@ pub fn scsi_ascii_to_string(data: &[u8]) -> String { | ||||
| /// Read SCSI Inquiry page | ||||
| /// | ||||
| /// Returns Product/Vendor/Revision and device type. | ||||
| pub fn scsi_inquiry<F: AsRawFd>( | ||||
|     file: &mut F, | ||||
| ) -> Result<InquiryInfo, Error> { | ||||
|  | ||||
| pub fn scsi_inquiry<F: AsRawFd>(file: &mut F) -> Result<InquiryInfo, Error> { | ||||
|     let allocation_len: u8 = std::mem::size_of::<InquiryPage>() as u8; | ||||
|  | ||||
|     let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; | ||||
| @ -672,13 +639,14 @@ pub fn scsi_inquiry<F: AsRawFd>( | ||||
|     let mut cmd = Vec::new(); | ||||
|     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))?; | ||||
|  | ||||
|     proxmox_lang::try_block!({ | ||||
|         let mut reader = data; | ||||
|  | ||||
|         let page: InquiryPage  = unsafe { reader.read_be_value()? }; | ||||
|         let page: InquiryPage = unsafe { reader.read_be_value()? }; | ||||
|  | ||||
|         let peripheral_type = page.peripheral_type & 31; | ||||
|  | ||||
| @ -691,7 +659,8 @@ pub fn scsi_inquiry<F: AsRawFd>( | ||||
|         }; | ||||
|  | ||||
|         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 | ||||
| @ -703,7 +672,6 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>( | ||||
|     page_code: u8, | ||||
|     sub_page_code: u8, | ||||
| ) -> Result<(ModeParameterHeader, Option<ModeBlockDescriptor>, P), Error> { | ||||
|  | ||||
|     let allocation_len: u16 = 4096; | ||||
|     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.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))?; | ||||
|  | ||||
|     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; | ||||
|  | ||||
|         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 { | ||||
|             // Note: Some hh7 drives returns the allocation_length | ||||
|             // instead of real data_len | ||||
| @ -758,23 +731,22 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>( | ||||
|         let page: P = unsafe { reader.read_be_value()? }; | ||||
|  | ||||
|         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 | ||||
| pub fn scsi_request_sense<F: AsRawFd>( | ||||
|     file: &mut F, | ||||
| ) -> Result<RequestSenseFixed, ScsiError> { | ||||
|  | ||||
| pub fn scsi_request_sense<F: AsRawFd>(file: &mut F) -> Result<RequestSenseFixed, ScsiError> { | ||||
|     // request 252 bytes, as mentioned in the Seagate SCSI reference | ||||
|     let allocation_len: u8 = 252; | ||||
|  | ||||
|     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(30); // use short timeout | ||||
|     let mut cmd = Vec::new(); | ||||
|     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))?; | ||||
|  | ||||
|     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()? }; | ||||
|  | ||||
|         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) | ||||
| } | ||||
|  | ||||
| @ -38,12 +38,16 @@ pub trait TapeWrite { | ||||
|         } | ||||
|         let header = header.to_le(); | ||||
|  | ||||
|         let res = self.write_all(unsafe { std::slice::from_raw_parts( | ||||
|             &header as *const MediaContentHeader as *const u8, | ||||
|             std::mem::size_of::<MediaContentHeader>(), | ||||
|         )})?; | ||||
|         let res = self.write_all(unsafe { | ||||
|             std::slice::from_raw_parts( | ||||
|                 &header as *const MediaContentHeader as *const u8, | ||||
|                 std::mem::size_of::<MediaContentHeader>(), | ||||
|             ) | ||||
|         })?; | ||||
|  | ||||
|         if data.is_empty() { return Ok(res); } | ||||
|         if data.is_empty() { | ||||
|             return Ok(res); | ||||
|         } | ||||
|  | ||||
|         self.write_all(data) | ||||
|     } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user