pbs tape: rust fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
		@ -12,46 +12,39 @@
 | 
				
			|||||||
/// - support tape alert flags
 | 
					/// - support tape alert flags
 | 
				
			||||||
/// - support volume statistics
 | 
					/// - support volume statistics
 | 
				
			||||||
/// - read cartridge memory
 | 
					/// - read cartridge memory
 | 
				
			||||||
 | 
					 | 
				
			||||||
use std::convert::TryInto;
 | 
					use std::convert::TryInto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::{bail, Error};
 | 
					use anyhow::{bail, Error};
 | 
				
			||||||
use serde_json::Value;
 | 
					use serde_json::Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use proxmox_schema::{api, ArraySchema, IntegerSchema, Schema, StringSchema};
 | 
					 | 
				
			||||||
use proxmox_router::cli::*;
 | 
					use proxmox_router::cli::*;
 | 
				
			||||||
use proxmox_router::RpcEnvironment;
 | 
					use proxmox_router::RpcEnvironment;
 | 
				
			||||||
 | 
					use proxmox_schema::{api, ArraySchema, IntegerSchema, Schema, StringSchema};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use pbs_api_types::{
 | 
					use pbs_api_types::{LtoTapeDrive, DRIVE_NAME_SCHEMA, LTO_DRIVE_PATH_SCHEMA};
 | 
				
			||||||
    LTO_DRIVE_PATH_SCHEMA, DRIVE_NAME_SCHEMA, LtoTapeDrive,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use pbs_config::drive::complete_drive_name;
 | 
					use pbs_config::drive::complete_drive_name;
 | 
				
			||||||
use pbs_tape::{
 | 
					use pbs_tape::{
 | 
				
			||||||
    sg_tape::SgTape,
 | 
					 | 
				
			||||||
    linux_list_drives::{complete_drive_path, lto_tape_device_list, open_lto_tape_device},
 | 
					    linux_list_drives::{complete_drive_path, lto_tape_device_list, open_lto_tape_device},
 | 
				
			||||||
 | 
					    sg_tape::SgTape,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const FILE_MARK_COUNT_SCHEMA: Schema =
 | 
					pub const FILE_MARK_COUNT_SCHEMA: Schema = IntegerSchema::new("File mark count.")
 | 
				
			||||||
    IntegerSchema::new("File mark count.")
 | 
					 | 
				
			||||||
    .minimum(1)
 | 
					    .minimum(1)
 | 
				
			||||||
    .maximum(i32::MAX as isize)
 | 
					    .maximum(i32::MAX as isize)
 | 
				
			||||||
    .schema();
 | 
					    .schema();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const FILE_MARK_POSITION_SCHEMA: Schema =
 | 
					pub const FILE_MARK_POSITION_SCHEMA: Schema = IntegerSchema::new("File mark position (0 is BOT).")
 | 
				
			||||||
    IntegerSchema::new("File mark position (0 is BOT).")
 | 
					 | 
				
			||||||
    .minimum(0)
 | 
					    .minimum(0)
 | 
				
			||||||
    .maximum(i32::MAX as isize)
 | 
					    .maximum(i32::MAX as isize)
 | 
				
			||||||
    .schema();
 | 
					    .schema();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const RECORD_COUNT_SCHEMA: Schema =
 | 
					pub const RECORD_COUNT_SCHEMA: Schema = IntegerSchema::new("Record count.")
 | 
				
			||||||
    IntegerSchema::new("Record count.")
 | 
					 | 
				
			||||||
    .minimum(1)
 | 
					    .minimum(1)
 | 
				
			||||||
    .maximum(i32::MAX as isize)
 | 
					    .maximum(i32::MAX as isize)
 | 
				
			||||||
    .schema();
 | 
					    .schema();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const DRIVE_OPTION_SCHEMA: Schema = StringSchema::new(
 | 
					pub const DRIVE_OPTION_SCHEMA: Schema =
 | 
				
			||||||
    "Lto Tape Driver Option, either numeric value or option name.")
 | 
					    StringSchema::new("Lto Tape Driver Option, either numeric value or option name.").schema();
 | 
				
			||||||
    .schema();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
 | 
					pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
 | 
				
			||||||
    ArraySchema::new("Drive Option List.", &DRIVE_OPTION_SCHEMA)
 | 
					    ArraySchema::new("Drive Option List.", &DRIVE_OPTION_SCHEMA)
 | 
				
			||||||
@ -59,7 +52,6 @@ pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
 | 
				
			|||||||
        .schema();
 | 
					        .schema();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_tape_handle(param: &Value) -> Result<SgTape, Error> {
 | 
					fn get_tape_handle(param: &Value) -> Result<SgTape, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if let Some(name) = param["drive"].as_str() {
 | 
					    if let Some(name) = param["drive"].as_str() {
 | 
				
			||||||
        let (config, _digest) = pbs_config::drive::config()?;
 | 
					        let (config, _digest) = pbs_config::drive::config()?;
 | 
				
			||||||
        let drive: LtoTapeDrive = config.lookup("lto", name)?;
 | 
					        let drive: LtoTapeDrive = config.lookup("lto", name)?;
 | 
				
			||||||
@ -88,7 +80,9 @@ fn get_tape_handle(param: &Value) -> Result<SgTape, Error> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let mut drive_names = Vec::new();
 | 
					    let mut drive_names = Vec::new();
 | 
				
			||||||
    for (name, (section_type, _)) in config.sections.iter() {
 | 
					    for (name, (section_type, _)) in config.sections.iter() {
 | 
				
			||||||
        if section_type != "lto" { continue; }
 | 
					        if section_type != "lto" {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        drive_names.push(name);
 | 
					        drive_names.push(name);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -122,7 +116,6 @@ fn get_tape_handle(param: &Value) -> Result<SgTape, Error> {
 | 
				
			|||||||
/// Position the tape at the beginning of the count file (after
 | 
					/// Position the tape at the beginning of the count file (after
 | 
				
			||||||
/// filemark count)
 | 
					/// filemark count)
 | 
				
			||||||
fn asf(count: u64, param: Value) -> Result<(), Error> {
 | 
					fn asf(count: u64, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handle.locate_file(count)?;
 | 
					    handle.locate_file(count)?;
 | 
				
			||||||
@ -130,7 +123,6 @@ fn asf(count: u64, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -152,7 +144,6 @@ fn asf(count: u64, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
///
 | 
					///
 | 
				
			||||||
/// The tape is positioned on the last block of the previous file.
 | 
					/// The tape is positioned on the last block of the previous file.
 | 
				
			||||||
fn bsf(count: usize, param: Value) -> Result<(), Error> {
 | 
					fn bsf(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handle.space_filemarks(-count.try_into()?)?;
 | 
					    handle.space_filemarks(-count.try_into()?)?;
 | 
				
			||||||
@ -160,7 +151,6 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -183,7 +173,6 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
/// This leaves the tape positioned at the first block of the file
 | 
					/// This leaves the tape positioned at the first block of the file
 | 
				
			||||||
/// that is count - 1 files before the current file.
 | 
					/// that is count - 1 files before the current file.
 | 
				
			||||||
fn bsfm(count: usize, param: Value) -> Result<(), Error> {
 | 
					fn bsfm(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handle.space_filemarks(-count.try_into()?)?;
 | 
					    handle.space_filemarks(-count.try_into()?)?;
 | 
				
			||||||
@ -192,7 +181,6 @@ fn bsfm(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -212,7 +200,6 @@ fn bsfm(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Backward space records.
 | 
					/// Backward space records.
 | 
				
			||||||
fn bsr(count: usize, param: Value) -> Result<(), Error> {
 | 
					fn bsr(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handle.space_blocks(-count.try_into()?)?;
 | 
					    handle.space_blocks(-count.try_into()?)?;
 | 
				
			||||||
@ -220,7 +207,6 @@ fn bsr(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -241,7 +227,6 @@ fn bsr(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Read Cartridge Memory
 | 
					/// Read Cartridge Memory
 | 
				
			||||||
fn cartridge_memory(param: Value) -> Result<(), Error> {
 | 
					fn cartridge_memory(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let output_format = get_output_format(¶m);
 | 
					    let output_format = get_output_format(¶m);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
@ -292,11 +277,11 @@ fn cartridge_memory(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Read Tape Alert Flags
 | 
					/// Read Tape Alert Flags
 | 
				
			||||||
fn tape_alert_flags(param: Value) -> Result<(), Error> {
 | 
					fn tape_alert_flags(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let output_format = get_output_format(¶m);
 | 
					    let output_format = get_output_format(¶m);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
    let result = handle.tape_alert_flags()
 | 
					    let result = handle
 | 
				
			||||||
 | 
					        .tape_alert_flags()
 | 
				
			||||||
        .map(|flags| format!("{:?}", flags));
 | 
					        .map(|flags| format!("{:?}", flags));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if output_format == "json-pretty" {
 | 
					    if output_format == "json-pretty" {
 | 
				
			||||||
@ -337,14 +322,12 @@ fn tape_alert_flags(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Eject drive media
 | 
					/// Eject drive media
 | 
				
			||||||
fn eject(param: Value) -> Result<(), Error> {
 | 
					fn eject(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
    handle.eject()?;
 | 
					    handle.eject()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -361,14 +344,12 @@ fn eject(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Move to end of media
 | 
					/// Move to end of media
 | 
				
			||||||
fn eod(param: Value) -> Result<(), Error> {
 | 
					fn eod(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
    handle.move_to_eom(false)?;
 | 
					    handle.move_to_eom(false)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -391,7 +372,6 @@ fn eod(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Erase media (from current position)
 | 
					/// Erase media (from current position)
 | 
				
			||||||
fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
 | 
					fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
    handle.erase_media(fast.unwrap_or(true))?;
 | 
					    handle.erase_media(fast.unwrap_or(true))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -420,7 +400,6 @@ fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Format media,  single partition
 | 
					/// Format media,  single partition
 | 
				
			||||||
fn format(fast: Option<bool>, param: Value) -> Result<(), Error> {
 | 
					fn format(fast: Option<bool>, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
    handle.format_media(fast.unwrap_or(true))?;
 | 
					    handle.format_media(fast.unwrap_or(true))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -448,7 +427,6 @@ fn format(fast: Option<bool>, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
///
 | 
					///
 | 
				
			||||||
/// The tape is positioned on the first block of the next file.
 | 
					/// The tape is positioned on the first block of the next file.
 | 
				
			||||||
fn fsf(count: usize, param: Value) -> Result<(), Error> {
 | 
					fn fsf(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handle.space_filemarks(count.try_into()?)?;
 | 
					    handle.space_filemarks(count.try_into()?)?;
 | 
				
			||||||
@ -478,7 +456,6 @@ fn fsf(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
/// This leaves the tape positioned at the last block of the file that
 | 
					/// This leaves the tape positioned at the last block of the file that
 | 
				
			||||||
/// is count - 1 files past the current file.
 | 
					/// is count - 1 files past the current file.
 | 
				
			||||||
fn fsfm(count: usize, param: Value) -> Result<(), Error> {
 | 
					fn fsfm(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handle.space_filemarks(count.try_into()?)?;
 | 
					    handle.space_filemarks(count.try_into()?)?;
 | 
				
			||||||
@ -487,7 +464,6 @@ fn fsfm(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -507,7 +483,6 @@ fn fsfm(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Forward space records.
 | 
					/// Forward space records.
 | 
				
			||||||
fn fsr(count: usize, param: Value) -> Result<(), Error> {
 | 
					fn fsr(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handle.space_blocks(count.try_into()?)?;
 | 
					    handle.space_blocks(count.try_into()?)?;
 | 
				
			||||||
@ -515,7 +490,6 @@ fn fsr(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -532,14 +506,12 @@ fn fsr(count: usize, param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Load media
 | 
					/// Load media
 | 
				
			||||||
fn load(param: Value) -> Result<(), Error> {
 | 
					fn load(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
    handle.load()?;
 | 
					    handle.load()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -556,7 +528,6 @@ fn load(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Lock the tape drive door
 | 
					/// Lock the tape drive door
 | 
				
			||||||
fn lock(param: Value) -> Result<(), Error> {
 | 
					fn lock(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handle.set_medium_removal(false)?;
 | 
					    handle.set_medium_removal(false)?;
 | 
				
			||||||
@ -564,7 +535,6 @@ fn lock(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -581,14 +551,12 @@ fn lock(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Rewind the tape
 | 
					/// Rewind the tape
 | 
				
			||||||
fn rewind(param: Value) -> Result<(), Error> {
 | 
					fn rewind(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
    handle.rewind()?;
 | 
					    handle.rewind()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -601,7 +569,6 @@ fn rewind(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Scan for existing tape changer devices
 | 
					/// Scan for existing tape changer devices
 | 
				
			||||||
fn scan(param: Value) -> Result<(), Error> {
 | 
					fn scan(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let output_format = get_output_format(¶m);
 | 
					    let output_format = get_output_format(¶m);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let list = lto_tape_device_list();
 | 
					    let list = lto_tape_device_list();
 | 
				
			||||||
@ -621,7 +588,10 @@ fn scan(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for item in list.iter() {
 | 
					    for item in list.iter() {
 | 
				
			||||||
        println!("{} ({}/{}/{})", item.path, item.vendor, item.model, item.serial);
 | 
					        println!(
 | 
				
			||||||
 | 
					            "{} ({}/{}/{})",
 | 
				
			||||||
 | 
					            item.path, item.vendor, item.model, item.serial
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
@ -647,7 +617,6 @@ fn scan(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Drive Status
 | 
					/// Drive Status
 | 
				
			||||||
fn status(param: Value) -> Result<(), Error> {
 | 
					fn status(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let output_format = get_output_format(¶m);
 | 
					    let output_format = get_output_format(¶m);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
@ -677,7 +646,6 @@ fn status(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
   input: {
 | 
					   input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -694,7 +662,6 @@ fn status(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Unlock the tape drive door
 | 
					/// Unlock the tape drive door
 | 
				
			||||||
fn unlock(param: Value) -> Result<(), Error> {
 | 
					fn unlock(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handle.set_medium_removal(true)?;
 | 
					    handle.set_medium_removal(true)?;
 | 
				
			||||||
@ -702,7 +669,6 @@ fn unlock(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
@ -723,7 +689,6 @@ fn unlock(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Volume Statistics
 | 
					/// Volume Statistics
 | 
				
			||||||
fn volume_statistics(param: Value) -> Result<(), Error> {
 | 
					fn volume_statistics(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let output_format = get_output_format(¶m);
 | 
					    let output_format = get_output_format(¶m);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut handle = get_tape_handle(¶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.
 | 
					/// Write count (default 1) EOF marks at current position.
 | 
				
			||||||
fn weof(count: Option<usize>, param: Value) -> Result<(), Error> {
 | 
					fn weof(count: Option<usize>, param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let count = count.unwrap_or(1);
 | 
					    let count = count.unwrap_or(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
@ -825,7 +789,6 @@ fn options(
 | 
				
			|||||||
    defaults: Option<bool>,
 | 
					    defaults: Option<bool>,
 | 
				
			||||||
    param: Value,
 | 
					    param: Value,
 | 
				
			||||||
) -> Result<(), Error> {
 | 
					) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut handle = get_tape_handle(¶m)?;
 | 
					    let mut handle = get_tape_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Some(true) = defaults {
 | 
					    if let Some(true) = defaults {
 | 
				
			||||||
@ -838,7 +801,6 @@ fn options(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() -> Result<(), Error> {
 | 
					fn main() -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let uid = nix::unistd::Uid::current();
 | 
					    let uid = nix::unistd::Uid::current();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let username = match nix::unistd::User::from_uid(uid)? {
 | 
					    let username = match nix::unistd::User::from_uid(uid)? {
 | 
				
			||||||
@ -875,8 +837,7 @@ fn main() -> Result<(), Error> {
 | 
				
			|||||||
        .insert("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS))
 | 
					        .insert("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS))
 | 
				
			||||||
        .insert("unlock", std_cmd(&API_METHOD_UNLOCK))
 | 
					        .insert("unlock", std_cmd(&API_METHOD_UNLOCK))
 | 
				
			||||||
        .insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
 | 
					        .insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
 | 
				
			||||||
        .insert("weof", std_cmd(&API_METHOD_WEOF).arg_param(&["count"]))
 | 
					        .insert("weof", std_cmd(&API_METHOD_WEOF).arg_param(&["count"]));
 | 
				
			||||||
        ;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut rpcenv = CliEnvironment::new();
 | 
					    let mut rpcenv = CliEnvironment::new();
 | 
				
			||||||
    rpcenv.set_auth_id(Some(format!("{}@pam", username)));
 | 
					    rpcenv.set_auth_id(Some(format!("{}@pam", username)));
 | 
				
			||||||
 | 
				
			|||||||
@ -11,29 +11,25 @@
 | 
				
			|||||||
///
 | 
					///
 | 
				
			||||||
/// - list serial number for attached drives, so that it is possible
 | 
					/// - list serial number for attached drives, so that it is possible
 | 
				
			||||||
///   to associate drive numbers with drives.
 | 
					///   to associate drive numbers with drives.
 | 
				
			||||||
 | 
					 | 
				
			||||||
use std::fs::File;
 | 
					use std::fs::File;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::{bail, Error};
 | 
					use anyhow::{bail, Error};
 | 
				
			||||||
use serde_json::Value;
 | 
					use serde_json::Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use proxmox_schema::api;
 | 
					 | 
				
			||||||
use proxmox_router::cli::*;
 | 
					use proxmox_router::cli::*;
 | 
				
			||||||
use proxmox_router::RpcEnvironment;
 | 
					use proxmox_router::RpcEnvironment;
 | 
				
			||||||
 | 
					use proxmox_schema::api;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use pbs_api_types::{LtoTapeDrive, ScsiTapeChanger, CHANGER_NAME_SCHEMA, SCSI_CHANGER_PATH_SCHEMA};
 | 
				
			||||||
use pbs_config::drive::complete_changer_name;
 | 
					use pbs_config::drive::complete_changer_name;
 | 
				
			||||||
use pbs_api_types::{
 | 
					 | 
				
			||||||
    SCSI_CHANGER_PATH_SCHEMA, CHANGER_NAME_SCHEMA, ScsiTapeChanger, LtoTapeDrive,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use pbs_tape::{
 | 
					use pbs_tape::{
 | 
				
			||||||
 | 
					    linux_list_drives::{complete_changer_path, linux_tape_changer_list},
 | 
				
			||||||
 | 
					    sg_pt_changer,
 | 
				
			||||||
    sgutils2::scsi_inquiry,
 | 
					    sgutils2::scsi_inquiry,
 | 
				
			||||||
    ElementStatus,
 | 
					    ElementStatus,
 | 
				
			||||||
    sg_pt_changer,
 | 
					 | 
				
			||||||
    linux_list_drives::{complete_changer_path, linux_tape_changer_list},
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_changer_handle(param: &Value) -> Result<File, Error> {
 | 
					fn get_changer_handle(param: &Value) -> Result<File, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if let Some(name) = param["changer"].as_str() {
 | 
					    if let Some(name) = param["changer"].as_str() {
 | 
				
			||||||
        let (config, _digest) = pbs_config::drive::config()?;
 | 
					        let (config, _digest) = pbs_config::drive::config()?;
 | 
				
			||||||
        let changer_config: ScsiTapeChanger = config.lookup("changer", name)?;
 | 
					        let changer_config: ScsiTapeChanger = config.lookup("changer", name)?;
 | 
				
			||||||
@ -83,10 +79,7 @@ fn get_changer_handle(param: &Value) -> Result<File, Error> {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
/// Inquiry
 | 
					/// Inquiry
 | 
				
			||||||
fn inquiry(
 | 
					fn inquiry(param: Value) -> Result<(), Error> {
 | 
				
			||||||
    param: Value,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let output_format = get_output_format(¶m);
 | 
					    let output_format = get_output_format(¶m);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let result: Result<_, Error> = proxmox_lang::try_block!({
 | 
					    let result: Result<_, Error> = proxmox_lang::try_block!({
 | 
				
			||||||
@ -113,7 +106,10 @@ fn inquiry(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let info = result?;
 | 
					    let info = result?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    println!("Type:     {} ({})", info.peripheral_type_text, info.peripheral_type);
 | 
					    println!(
 | 
				
			||||||
 | 
					        "Type:     {} ({})",
 | 
				
			||||||
 | 
					        info.peripheral_type_text, info.peripheral_type
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    println!("Vendor:   {}", info.vendor);
 | 
					    println!("Vendor:   {}", info.vendor);
 | 
				
			||||||
    println!("Product:  {}", info.product);
 | 
					    println!("Product:  {}", info.product);
 | 
				
			||||||
    println!("Revision: {}", info.revision);
 | 
					    println!("Revision: {}", info.revision);
 | 
				
			||||||
@ -136,10 +132,7 @@ fn inquiry(
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
/// Inventory
 | 
					/// Inventory
 | 
				
			||||||
fn inventory(
 | 
					fn inventory(param: Value) -> Result<(), Error> {
 | 
				
			||||||
    param: Value,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut file = get_changer_handle(¶m)?;
 | 
					    let mut file = get_changer_handle(¶m)?;
 | 
				
			||||||
    sg_pt_changer::initialize_element_status(&mut file)?;
 | 
					    sg_pt_changer::initialize_element_status(&mut file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -170,12 +163,7 @@ fn inventory(
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
/// Load
 | 
					/// Load
 | 
				
			||||||
fn load(
 | 
					fn load(param: Value, slot: u64, drivenum: Option<u64>) -> Result<(), Error> {
 | 
				
			||||||
    param: Value,
 | 
					 | 
				
			||||||
    slot: u64,
 | 
					 | 
				
			||||||
    drivenum: Option<u64>,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut file = get_changer_handle(¶m)?;
 | 
					    let mut file = get_changer_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let drivenum = drivenum.unwrap_or(0);
 | 
					    let drivenum = drivenum.unwrap_or(0);
 | 
				
			||||||
@ -210,12 +198,7 @@ fn load(
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
/// Unload
 | 
					/// Unload
 | 
				
			||||||
fn unload(
 | 
					fn unload(param: Value, slot: Option<u64>, drivenum: Option<u64>) -> Result<(), Error> {
 | 
				
			||||||
    param: Value,
 | 
					 | 
				
			||||||
    slot: Option<u64>,
 | 
					 | 
				
			||||||
    drivenum: Option<u64>,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut file = get_changer_handle(¶m)?;
 | 
					    let mut file = get_changer_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let drivenum = drivenum.unwrap_or(0);
 | 
					    let drivenum = drivenum.unwrap_or(0);
 | 
				
			||||||
@ -271,10 +254,7 @@ fn unload(
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
/// Changer Status
 | 
					/// Changer Status
 | 
				
			||||||
fn status(
 | 
					fn status(param: Value) -> Result<(), Error> {
 | 
				
			||||||
    param: Value,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let output_format = get_output_format(¶m);
 | 
					    let output_format = get_output_format(¶m);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let result: Result<_, Error> = proxmox_lang::try_block!({
 | 
					    let result: Result<_, Error> = proxmox_lang::try_block!({
 | 
				
			||||||
@ -302,7 +282,10 @@ fn status(
 | 
				
			|||||||
    let status = result?;
 | 
					    let status = result?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (i, transport) in status.transports.iter().enumerate() {
 | 
					    for (i, transport) in status.transports.iter().enumerate() {
 | 
				
			||||||
        println!("Transport Element (Griper)    {:>3}: {:?}",i, transport.status);
 | 
					        println!(
 | 
				
			||||||
 | 
					            "Transport Element (Griper)    {:>3}: {:?}",
 | 
				
			||||||
 | 
					            i, transport.status
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (i, drive) in status.drives.iter().enumerate() {
 | 
					    for (i, drive) in status.drives.iter().enumerate() {
 | 
				
			||||||
@ -323,9 +306,9 @@ fn status(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for (i, slot) in status.slots.iter().enumerate() {
 | 
					    for (i, slot) in status.slots.iter().enumerate() {
 | 
				
			||||||
        if slot.import_export {
 | 
					        if slot.import_export {
 | 
				
			||||||
            println!("  Import/Export   {:>3}: {:?}", i+1, slot.status);
 | 
					            println!("  Import/Export   {:>3}: {:?}", i + 1, slot.status);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            println!("  Storage Element {:>3}: {:?}", i+1, slot.status);
 | 
					            println!("  Storage Element {:>3}: {:?}", i + 1, slot.status);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -355,12 +338,7 @@ fn status(
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
/// Transfer
 | 
					/// Transfer
 | 
				
			||||||
fn transfer(
 | 
					fn transfer(param: Value, from: u64, to: u64) -> Result<(), Error> {
 | 
				
			||||||
    param: Value,
 | 
					 | 
				
			||||||
    from: u64,
 | 
					 | 
				
			||||||
    to: u64,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut file = get_changer_handle(¶m)?;
 | 
					    let mut file = get_changer_handle(¶m)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_pt_changer::transfer_medium(&mut file, from, to)?;
 | 
					    sg_pt_changer::transfer_medium(&mut file, from, to)?;
 | 
				
			||||||
@ -380,7 +358,6 @@ fn transfer(
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Scan for existing tape changer devices
 | 
					/// Scan for existing tape changer devices
 | 
				
			||||||
fn scan(param: Value) -> Result<(), Error> {
 | 
					fn scan(param: Value) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let output_format = get_output_format(¶m);
 | 
					    let output_format = get_output_format(¶m);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let list = linux_tape_changer_list();
 | 
					    let list = linux_tape_changer_list();
 | 
				
			||||||
@ -400,14 +377,16 @@ fn scan(param: Value) -> Result<(), Error> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for item in list.iter() {
 | 
					    for item in list.iter() {
 | 
				
			||||||
        println!("{} ({}/{}/{})", item.path, item.vendor, item.model, item.serial);
 | 
					        println!(
 | 
				
			||||||
 | 
					            "{} ({}/{}/{})",
 | 
				
			||||||
 | 
					            item.path, item.vendor, item.model, item.serial
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() -> Result<(), Error> {
 | 
					fn main() -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let uid = nix::unistd::Uid::current();
 | 
					    let uid = nix::unistd::Uid::current();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let username = match nix::unistd::User::from_uid(uid)? {
 | 
					    let username = match nix::unistd::User::from_uid(uid)? {
 | 
				
			||||||
@ -415,49 +394,47 @@ fn main() -> Result<(), Error> {
 | 
				
			|||||||
        None => bail!("unable to get user name"),
 | 
					        None => bail!("unable to get user name"),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let cmd_def = CliCommandMap::new()
 | 
					    let cmd_def = CliCommandMap::new()
 | 
				
			||||||
        .usage_skip_options(&["device", "changer", "output-format"])
 | 
					        .usage_skip_options(&["device", "changer", "output-format"])
 | 
				
			||||||
        .insert(
 | 
					        .insert(
 | 
				
			||||||
            "inquiry",
 | 
					            "inquiry",
 | 
				
			||||||
            CliCommand::new(&API_METHOD_INQUIRY)
 | 
					            CliCommand::new(&API_METHOD_INQUIRY)
 | 
				
			||||||
                .completion_cb("changer", complete_changer_name)
 | 
					                .completion_cb("changer", complete_changer_name)
 | 
				
			||||||
                .completion_cb("device", complete_changer_path)
 | 
					                .completion_cb("device", complete_changer_path),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .insert(
 | 
					        .insert(
 | 
				
			||||||
            "inventory",
 | 
					            "inventory",
 | 
				
			||||||
            CliCommand::new(&API_METHOD_INVENTORY)
 | 
					            CliCommand::new(&API_METHOD_INVENTORY)
 | 
				
			||||||
                .completion_cb("changer", complete_changer_name)
 | 
					                .completion_cb("changer", complete_changer_name)
 | 
				
			||||||
                .completion_cb("device", complete_changer_path)
 | 
					                .completion_cb("device", complete_changer_path),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .insert(
 | 
					        .insert(
 | 
				
			||||||
            "load",
 | 
					            "load",
 | 
				
			||||||
            CliCommand::new(&API_METHOD_LOAD)
 | 
					            CliCommand::new(&API_METHOD_LOAD)
 | 
				
			||||||
                .arg_param(&["slot"])
 | 
					                .arg_param(&["slot"])
 | 
				
			||||||
                .completion_cb("changer", complete_changer_name)
 | 
					                .completion_cb("changer", complete_changer_name)
 | 
				
			||||||
                .completion_cb("device", complete_changer_path)
 | 
					                .completion_cb("device", complete_changer_path),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .insert(
 | 
					        .insert(
 | 
				
			||||||
            "unload",
 | 
					            "unload",
 | 
				
			||||||
            CliCommand::new(&API_METHOD_UNLOAD)
 | 
					            CliCommand::new(&API_METHOD_UNLOAD)
 | 
				
			||||||
                .completion_cb("changer", complete_changer_name)
 | 
					                .completion_cb("changer", complete_changer_name)
 | 
				
			||||||
                .completion_cb("device", complete_changer_path)
 | 
					                .completion_cb("device", complete_changer_path),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .insert("scan", CliCommand::new(&API_METHOD_SCAN))
 | 
					        .insert("scan", CliCommand::new(&API_METHOD_SCAN))
 | 
				
			||||||
        .insert(
 | 
					        .insert(
 | 
				
			||||||
            "status",
 | 
					            "status",
 | 
				
			||||||
            CliCommand::new(&API_METHOD_STATUS)
 | 
					            CliCommand::new(&API_METHOD_STATUS)
 | 
				
			||||||
                .completion_cb("changer", complete_changer_name)
 | 
					                .completion_cb("changer", complete_changer_name)
 | 
				
			||||||
                .completion_cb("device", complete_changer_path)
 | 
					                .completion_cb("device", complete_changer_path),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .insert(
 | 
					        .insert(
 | 
				
			||||||
            "transfer",
 | 
					            "transfer",
 | 
				
			||||||
            CliCommand::new(&API_METHOD_TRANSFER)
 | 
					            CliCommand::new(&API_METHOD_TRANSFER)
 | 
				
			||||||
                .arg_param(&["from", "to"])
 | 
					                .arg_param(&["from", "to"])
 | 
				
			||||||
                .completion_cb("changer", complete_changer_name)
 | 
					                .completion_cb("changer", complete_changer_name)
 | 
				
			||||||
                .completion_cb("device", complete_changer_path)
 | 
					                .completion_cb("device", complete_changer_path),
 | 
				
			||||||
        )
 | 
					        );
 | 
				
			||||||
         ;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut rpcenv = CliEnvironment::new();
 | 
					    let mut rpcenv = CliEnvironment::new();
 | 
				
			||||||
    rpcenv.set_auth_id(Some(format!("{}@pam", username)));
 | 
					    rpcenv.set_auth_id(Some(format!("{}@pam", username)));
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,8 @@
 | 
				
			|||||||
use std::io::Read;
 | 
					use std::io::Read;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    TapeRead,
 | 
					    BlockHeader, BlockHeaderFlags, BlockRead, BlockReadError, TapeRead,
 | 
				
			||||||
    BlockRead,
 | 
					 | 
				
			||||||
    BlockReadError,
 | 
					 | 
				
			||||||
    PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0,
 | 
					    PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0,
 | 
				
			||||||
    BlockHeader,
 | 
					 | 
				
			||||||
    BlockHeaderFlags,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Read a block stream generated by 'BlockWriter'.
 | 
					/// Read a block stream generated by 'BlockWriter'.
 | 
				
			||||||
@ -31,14 +27,12 @@ pub struct BlockedReader<R> {
 | 
				
			|||||||
    read_pos: usize,
 | 
					    read_pos: usize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <R: BlockRead> BlockedReader<R> {
 | 
					impl<R: BlockRead> BlockedReader<R> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Create a new BlockedReader instance.
 | 
					    /// Create a new BlockedReader instance.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// This tries to read the first block. Please inspect the error
 | 
					    /// This tries to read the first block. Please inspect the error
 | 
				
			||||||
    /// to detect EOF and EOT.
 | 
					    /// to detect EOF and EOT.
 | 
				
			||||||
    pub fn open(mut reader: R) -> Result<Self, BlockReadError> {
 | 
					    pub fn open(mut reader: R) -> Result<Self, BlockReadError> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut buffer = BlockHeader::new();
 | 
					        let mut buffer = BlockHeader::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self::read_block_frame(&mut buffer, &mut reader)?;
 | 
					        Self::read_block_frame(&mut buffer, &mut reader)?;
 | 
				
			||||||
@ -67,32 +61,37 @@ impl <R: BlockRead> BlockedReader<R> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn check_buffer(buffer: &BlockHeader, seq_nr: u32) -> Result<(usize, bool), std::io::Error> {
 | 
					    fn check_buffer(buffer: &BlockHeader, seq_nr: u32) -> Result<(usize, bool), std::io::Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if buffer.magic != PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0 {
 | 
					        if buffer.magic != PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0 {
 | 
				
			||||||
            proxmox_lang::io_bail!("detected tape block with wrong magic number - not written by proxmox tape");
 | 
					            proxmox_lang::io_bail!(
 | 
				
			||||||
 | 
					                "detected tape block with wrong magic number - not written by proxmox tape"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if seq_nr != buffer.seq_nr() {
 | 
					        if seq_nr != buffer.seq_nr() {
 | 
				
			||||||
            proxmox_lang::io_bail!(
 | 
					            proxmox_lang::io_bail!(
 | 
				
			||||||
                "detected tape block with wrong sequence number ({} != {})",
 | 
					                "detected tape block with wrong sequence number ({} != {})",
 | 
				
			||||||
                seq_nr, buffer.seq_nr())
 | 
					                seq_nr,
 | 
				
			||||||
 | 
					                buffer.seq_nr()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let size = buffer.size();
 | 
					        let size = buffer.size();
 | 
				
			||||||
        let found_end_marker = buffer.flags.contains(BlockHeaderFlags::END_OF_STREAM);
 | 
					        let found_end_marker = buffer.flags.contains(BlockHeaderFlags::END_OF_STREAM);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if size > buffer.payload.len() {
 | 
					        if size > buffer.payload.len() {
 | 
				
			||||||
            proxmox_lang::io_bail!("detected tape block with wrong payload size ({} > {}", size, buffer.payload.len());
 | 
					            proxmox_lang::io_bail!(
 | 
				
			||||||
 | 
					                "detected tape block with wrong payload size ({} > {}",
 | 
				
			||||||
 | 
					                size,
 | 
				
			||||||
 | 
					                buffer.payload.len()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        } else if size == 0 && !found_end_marker {
 | 
					        } else if size == 0 && !found_end_marker {
 | 
				
			||||||
            proxmox_lang::io_bail!("detected tape block with zero payload size");
 | 
					            proxmox_lang::io_bail!("detected tape block with zero payload size");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok((size, found_end_marker))
 | 
					        Ok((size, found_end_marker))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn read_block_frame(buffer: &mut BlockHeader, reader: &mut R) -> Result<(), BlockReadError> {
 | 
					    fn read_block_frame(buffer: &mut BlockHeader, reader: &mut R) -> Result<(), BlockReadError> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let data = unsafe {
 | 
					        let data = unsafe {
 | 
				
			||||||
            std::slice::from_raw_parts_mut(
 | 
					            std::slice::from_raw_parts_mut(
 | 
				
			||||||
                (buffer as *mut BlockHeader) as *mut u8,
 | 
					                (buffer as *mut BlockHeader) as *mut u8,
 | 
				
			||||||
@ -115,20 +114,15 @@ impl <R: BlockRead> BlockedReader<R> {
 | 
				
			|||||||
            Ok(_) => {
 | 
					            Ok(_) => {
 | 
				
			||||||
                proxmox_lang::io_bail!("detected tape block after block-stream end marker");
 | 
					                proxmox_lang::io_bail!("detected tape block after block-stream end marker");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(BlockReadError::EndOfFile) => {
 | 
					            Err(BlockReadError::EndOfFile) => Ok(()),
 | 
				
			||||||
                Ok(())
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Err(BlockReadError::EndOfStream) => {
 | 
					            Err(BlockReadError::EndOfStream) => {
 | 
				
			||||||
                proxmox_lang::io_bail!("got unexpected end of tape");
 | 
					                proxmox_lang::io_bail!("got unexpected end of tape");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(BlockReadError::Error(err)) => {
 | 
					            Err(BlockReadError::Error(err)) => Err(err),
 | 
				
			||||||
                Err(err)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn read_block(&mut self, check_end_marker: bool) -> Result<usize, std::io::Error> {
 | 
					    fn read_block(&mut self, check_end_marker: bool) -> Result<usize, std::io::Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        match Self::read_block_frame(&mut self.buffer, &mut self.reader) {
 | 
					        match Self::read_block_frame(&mut self.buffer, &mut self.reader) {
 | 
				
			||||||
            Ok(()) => { /* ok */ }
 | 
					            Ok(()) => { /* ok */ }
 | 
				
			||||||
            Err(BlockReadError::EndOfFile) => {
 | 
					            Err(BlockReadError::EndOfFile) => {
 | 
				
			||||||
@ -150,7 +144,8 @@ impl <R: BlockRead> BlockedReader<R> {
 | 
				
			|||||||
        let (size, found_end_marker) = Self::check_buffer(&self.buffer, self.seq_nr)?;
 | 
					        let (size, found_end_marker) = Self::check_buffer(&self.buffer, self.seq_nr)?;
 | 
				
			||||||
        self.seq_nr += 1;
 | 
					        self.seq_nr += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if found_end_marker { // consume EOF mark
 | 
					        if found_end_marker {
 | 
				
			||||||
 | 
					            // consume EOF mark
 | 
				
			||||||
            self.found_end_marker = true;
 | 
					            self.found_end_marker = true;
 | 
				
			||||||
            self.incomplete = self.buffer.flags.contains(BlockHeaderFlags::INCOMPLETE);
 | 
					            self.incomplete = self.buffer.flags.contains(BlockHeaderFlags::INCOMPLETE);
 | 
				
			||||||
            Self::consume_eof_marker(&mut self.reader)?;
 | 
					            Self::consume_eof_marker(&mut self.reader)?;
 | 
				
			||||||
@ -163,8 +158,7 @@ impl <R: BlockRead> BlockedReader<R> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <R: BlockRead> TapeRead for BlockedReader<R> {
 | 
					impl<R: BlockRead> TapeRead for BlockedReader<R> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn is_incomplete(&self) -> Result<bool, std::io::Error> {
 | 
					    fn is_incomplete(&self) -> Result<bool, std::io::Error> {
 | 
				
			||||||
        if !self.got_eod {
 | 
					        if !self.got_eod {
 | 
				
			||||||
            proxmox_lang::io_bail!("is_incomplete failed: EOD not reached");
 | 
					            proxmox_lang::io_bail!("is_incomplete failed: EOD not reached");
 | 
				
			||||||
@ -202,10 +196,8 @@ impl <R: BlockRead> TapeRead for BlockedReader<R> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <R: BlockRead> Read for BlockedReader<R> {
 | 
					impl<R: BlockRead> Read for BlockedReader<R> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn read(&mut self, buffer: &mut [u8]) -> Result<usize, std::io::Error> {
 | 
					    fn read(&mut self, buffer: &mut [u8]) -> Result<usize, std::io::Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self.read_error {
 | 
					        if self.read_error {
 | 
				
			||||||
            proxmox_lang::io_bail!("detected read after error - internal error");
 | 
					            proxmox_lang::io_bail!("detected read after error - internal error");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -213,7 +205,8 @@ impl <R: BlockRead> Read for BlockedReader<R> {
 | 
				
			|||||||
        let mut buffer_size = self.buffer.size();
 | 
					        let mut buffer_size = self.buffer.size();
 | 
				
			||||||
        let mut rest = (buffer_size as isize) - (self.read_pos as isize);
 | 
					        let mut rest = (buffer_size as isize) - (self.read_pos as isize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if rest <= 0 && !self.got_eod { // try to refill buffer
 | 
					        if rest <= 0 && !self.got_eod {
 | 
				
			||||||
 | 
					            // try to refill buffer
 | 
				
			||||||
            buffer_size = match self.read_block(true) {
 | 
					            buffer_size = match self.read_block(true) {
 | 
				
			||||||
                Ok(len) => len,
 | 
					                Ok(len) => len,
 | 
				
			||||||
                err => {
 | 
					                err => {
 | 
				
			||||||
@ -232,8 +225,8 @@ impl <R: BlockRead> Read for BlockedReader<R> {
 | 
				
			|||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                rest as usize
 | 
					                rest as usize
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            buffer[..copy_len].copy_from_slice(
 | 
					            buffer[..copy_len]
 | 
				
			||||||
                &self.buffer.payload[self.read_pos..(self.read_pos + copy_len)]);
 | 
					                .copy_from_slice(&self.buffer.payload[self.read_pos..(self.read_pos + copy_len)]);
 | 
				
			||||||
            self.read_pos += copy_len;
 | 
					            self.read_pos += copy_len;
 | 
				
			||||||
            Ok(copy_len)
 | 
					            Ok(copy_len)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -242,24 +235,18 @@ impl <R: BlockRead> Read for BlockedReader<R> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod test {
 | 
					mod test {
 | 
				
			||||||
    use std::io::Read;
 | 
					 | 
				
			||||||
    use anyhow::{bail, Error};
 | 
					 | 
				
			||||||
    use crate::{
 | 
					    use crate::{
 | 
				
			||||||
        TapeWrite,
 | 
					        BlockReadError, BlockedReader, BlockedWriter, EmulateTapeReader, EmulateTapeWriter,
 | 
				
			||||||
        BlockReadError,
 | 
					        TapeWrite, PROXMOX_TAPE_BLOCK_SIZE,
 | 
				
			||||||
        EmulateTapeReader,
 | 
					 | 
				
			||||||
        EmulateTapeWriter,
 | 
					 | 
				
			||||||
        PROXMOX_TAPE_BLOCK_SIZE,
 | 
					 | 
				
			||||||
        BlockedReader,
 | 
					 | 
				
			||||||
        BlockedWriter,
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    use anyhow::{bail, Error};
 | 
				
			||||||
 | 
					    use std::io::Read;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn write_and_verify(data: &[u8]) -> Result<(), Error> {
 | 
					    fn write_and_verify(data: &[u8]) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut tape_data = Vec::new();
 | 
					        let mut tape_data = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024*10);
 | 
					            let writer = EmulateTapeWriter::new(&mut tape_data, 1024 * 1024 * 10);
 | 
				
			||||||
            let mut writer = BlockedWriter::new(writer);
 | 
					            let mut writer = BlockedWriter::new(writer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            writer.write_all(data)?;
 | 
					            writer.write_all(data)?;
 | 
				
			||||||
@ -269,8 +256,8 @@ mod test {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            tape_data.len(),
 | 
					            tape_data.len(),
 | 
				
			||||||
            ((data.len() + PROXMOX_TAPE_BLOCK_SIZE)/PROXMOX_TAPE_BLOCK_SIZE)
 | 
					            ((data.len() + PROXMOX_TAPE_BLOCK_SIZE) / PROXMOX_TAPE_BLOCK_SIZE)
 | 
				
			||||||
                *PROXMOX_TAPE_BLOCK_SIZE
 | 
					                * PROXMOX_TAPE_BLOCK_SIZE
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let reader = &mut &tape_data[..];
 | 
					        let reader = &mut &tape_data[..];
 | 
				
			||||||
@ -299,7 +286,7 @@ mod test {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn large_data() -> Result<(), Error> {
 | 
					    fn large_data() -> Result<(), Error> {
 | 
				
			||||||
        let data = proxmox_sys::linux::random_data(1024*1024*5)?;
 | 
					        let data = proxmox_sys::linux::random_data(1024 * 1024 * 5)?;
 | 
				
			||||||
        write_and_verify(&data)
 | 
					        write_and_verify(&data)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -309,7 +296,7 @@ mod test {
 | 
				
			|||||||
        let reader = &mut &tape_data[..];
 | 
					        let reader = &mut &tape_data[..];
 | 
				
			||||||
        let reader = EmulateTapeReader::new(reader);
 | 
					        let reader = EmulateTapeReader::new(reader);
 | 
				
			||||||
        match BlockedReader::open(reader) {
 | 
					        match BlockedReader::open(reader) {
 | 
				
			||||||
            Err(BlockReadError::EndOfFile) => { /* OK */ },
 | 
					            Err(BlockReadError::EndOfFile) => { /* OK */ }
 | 
				
			||||||
            _ => bail!("expected EOF"),
 | 
					            _ => bail!("expected EOF"),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -320,7 +307,7 @@ mod test {
 | 
				
			|||||||
    fn no_end_marker() -> Result<(), Error> {
 | 
					    fn no_end_marker() -> Result<(), Error> {
 | 
				
			||||||
        let mut tape_data = Vec::new();
 | 
					        let mut tape_data = Vec::new();
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024);
 | 
					            let writer = EmulateTapeWriter::new(&mut tape_data, 1024 * 1024);
 | 
				
			||||||
            let mut writer = BlockedWriter::new(writer);
 | 
					            let mut writer = BlockedWriter::new(writer);
 | 
				
			||||||
            // write at least one block
 | 
					            // write at least one block
 | 
				
			||||||
            let data = proxmox_sys::linux::random_data(PROXMOX_TAPE_BLOCK_SIZE)?;
 | 
					            let data = proxmox_sys::linux::random_data(PROXMOX_TAPE_BLOCK_SIZE)?;
 | 
				
			||||||
@ -343,7 +330,7 @@ mod test {
 | 
				
			|||||||
        let mut tape_data = Vec::new();
 | 
					        let mut tape_data = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024);
 | 
					            let writer = EmulateTapeWriter::new(&mut tape_data, 1024 * 1024);
 | 
				
			||||||
            let mut writer = BlockedWriter::new(writer);
 | 
					            let mut writer = BlockedWriter::new(writer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            writer.write_all(b"ABC")?;
 | 
					            writer.write_all(b"ABC")?;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,6 @@
 | 
				
			|||||||
use proxmox_io::vec;
 | 
					use proxmox_io::vec;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{BlockHeader, BlockHeaderFlags, BlockWrite, TapeWrite};
 | 
				
			||||||
    TapeWrite,
 | 
					 | 
				
			||||||
    BlockWrite,
 | 
					 | 
				
			||||||
    BlockHeader,
 | 
					 | 
				
			||||||
    BlockHeaderFlags,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Assemble and write blocks of data
 | 
					/// Assemble and write blocks of data
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
@ -22,8 +17,7 @@ pub struct BlockedWriter<W: BlockWrite> {
 | 
				
			|||||||
    wrote_eof: bool,
 | 
					    wrote_eof: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <W: BlockWrite> Drop for BlockedWriter<W> {
 | 
					impl<W: BlockWrite> Drop for BlockedWriter<W> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Try to make sure to end the file with a filemark
 | 
					    // Try to make sure to end the file with a filemark
 | 
				
			||||||
    fn drop(&mut self) {
 | 
					    fn drop(&mut self) {
 | 
				
			||||||
        if !self.wrote_eof {
 | 
					        if !self.wrote_eof {
 | 
				
			||||||
@ -32,8 +26,7 @@ impl <W: BlockWrite> Drop for BlockedWriter<W> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <W: BlockWrite> BlockedWriter<W> {
 | 
					impl<W: BlockWrite> BlockedWriter<W> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Allow access to underlying writer
 | 
					    /// Allow access to underlying writer
 | 
				
			||||||
    pub fn writer_ref_mut(&mut self) -> &mut W {
 | 
					    pub fn writer_ref_mut(&mut self) -> &mut W {
 | 
				
			||||||
        &mut self.writer
 | 
					        &mut self.writer
 | 
				
			||||||
@ -53,7 +46,6 @@ impl <W: BlockWrite> BlockedWriter<W> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn write_block(buffer: &BlockHeader, writer: &mut W) -> Result<bool, std::io::Error> {
 | 
					    fn write_block(buffer: &BlockHeader, writer: &mut W) -> Result<bool, std::io::Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let data = unsafe {
 | 
					        let data = unsafe {
 | 
				
			||||||
            std::slice::from_raw_parts(
 | 
					            std::slice::from_raw_parts(
 | 
				
			||||||
                (buffer as *const BlockHeader) as *const u8,
 | 
					                (buffer as *const BlockHeader) as *const u8,
 | 
				
			||||||
@ -73,12 +65,13 @@ impl <W: BlockWrite> BlockedWriter<W> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
 | 
					    fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
 | 
				
			||||||
 | 
					        if data.is_empty() {
 | 
				
			||||||
        if data.is_empty() { return Ok(0); }
 | 
					            return Ok(0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let rest = self.buffer.payload.len() - self.buffer_pos;
 | 
					        let rest = self.buffer.payload.len() - self.buffer_pos;
 | 
				
			||||||
        let bytes = if data.len() < rest { data.len() } else { rest };
 | 
					        let bytes = if data.len() < rest { data.len() } else { rest };
 | 
				
			||||||
        self.buffer.payload[self.buffer_pos..(self.buffer_pos+bytes)]
 | 
					        self.buffer.payload[self.buffer_pos..(self.buffer_pos + bytes)]
 | 
				
			||||||
            .copy_from_slice(&data[..bytes]);
 | 
					            .copy_from_slice(&data[..bytes]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let rest = rest - bytes;
 | 
					        let rest = rest - bytes;
 | 
				
			||||||
@ -89,21 +82,20 @@ impl <W: BlockWrite> BlockedWriter<W> {
 | 
				
			|||||||
            self.buffer.set_seq_nr(self.seq_nr);
 | 
					            self.buffer.set_seq_nr(self.seq_nr);
 | 
				
			||||||
            self.seq_nr += 1;
 | 
					            self.seq_nr += 1;
 | 
				
			||||||
            let leom = Self::write_block(&self.buffer, &mut self.writer)?;
 | 
					            let leom = Self::write_block(&self.buffer, &mut self.writer)?;
 | 
				
			||||||
            if leom { self.logical_end_of_media = true; }
 | 
					            if leom {
 | 
				
			||||||
 | 
					                self.logical_end_of_media = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            self.buffer_pos = 0;
 | 
					            self.buffer_pos = 0;
 | 
				
			||||||
            self.bytes_written += BlockHeader::SIZE;
 | 
					            self.bytes_written += BlockHeader::SIZE;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            self.buffer_pos += bytes;
 | 
					            self.buffer_pos += bytes;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(bytes)
 | 
					        Ok(bytes)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <W: BlockWrite> TapeWrite for BlockedWriter<W> {
 | 
					impl<W: BlockWrite> TapeWrite for BlockedWriter<W> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn write_all(&mut self, mut data: &[u8]) -> Result<bool, std::io::Error> {
 | 
					    fn write_all(&mut self, mut data: &[u8]) -> Result<bool, std::io::Error> {
 | 
				
			||||||
        while !data.is_empty() {
 | 
					        while !data.is_empty() {
 | 
				
			||||||
            match self.write(data) {
 | 
					            match self.write(data) {
 | 
				
			||||||
@ -125,7 +117,9 @@ impl <W: BlockWrite> TapeWrite for BlockedWriter<W> {
 | 
				
			|||||||
    fn finish(&mut self, incomplete: bool) -> Result<bool, std::io::Error> {
 | 
					    fn finish(&mut self, incomplete: bool) -> Result<bool, std::io::Error> {
 | 
				
			||||||
        vec::clear(&mut self.buffer.payload[self.buffer_pos..]);
 | 
					        vec::clear(&mut self.buffer.payload[self.buffer_pos..]);
 | 
				
			||||||
        self.buffer.flags = BlockHeaderFlags::END_OF_STREAM;
 | 
					        self.buffer.flags = BlockHeaderFlags::END_OF_STREAM;
 | 
				
			||||||
        if incomplete { self.buffer.flags |= BlockHeaderFlags::INCOMPLETE; }
 | 
					        if incomplete {
 | 
				
			||||||
 | 
					            self.buffer.flags |= BlockHeaderFlags::INCOMPLETE;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        self.buffer.set_size(self.buffer_pos);
 | 
					        self.buffer.set_size(self.buffer_pos);
 | 
				
			||||||
        self.buffer.set_seq_nr(self.seq_nr);
 | 
					        self.buffer.set_seq_nr(self.seq_nr);
 | 
				
			||||||
        self.seq_nr += 1;
 | 
					        self.seq_nr += 1;
 | 
				
			||||||
@ -139,5 +133,4 @@ impl <W: BlockWrite> TapeWrite for BlockedWriter<W> {
 | 
				
			|||||||
    fn logical_end_of_media(&self) -> bool {
 | 
					    fn logical_end_of_media(&self) -> bool {
 | 
				
			||||||
        self.logical_end_of_media
 | 
					        self.logical_end_of_media
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -12,17 +12,21 @@ pub struct EmulateTapeReader<R: Read> {
 | 
				
			|||||||
    got_eof: bool,
 | 
					    got_eof: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <R: Read> EmulateTapeReader<R> {
 | 
					impl<R: Read> EmulateTapeReader<R> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn new(reader: R) -> Self {
 | 
					    pub fn new(reader: R) -> Self {
 | 
				
			||||||
        Self { reader, got_eof: false }
 | 
					        Self {
 | 
				
			||||||
 | 
					            reader,
 | 
				
			||||||
 | 
					            got_eof: false,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <R: Read> BlockRead for EmulateTapeReader<R> {
 | 
					impl<R: Read> BlockRead for EmulateTapeReader<R> {
 | 
				
			||||||
    fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
 | 
					    fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
 | 
				
			||||||
        if self.got_eof {
 | 
					        if self.got_eof {
 | 
				
			||||||
             return Err(BlockReadError::Error(proxmox_lang::io_format_err!("detected read after EOF!")));
 | 
					            return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
 | 
				
			||||||
 | 
					                "detected read after EOF!"
 | 
				
			||||||
 | 
					            )));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        match self.reader.read_exact_or_eof(buffer)? {
 | 
					        match self.reader.read_exact_or_eof(buffer)? {
 | 
				
			||||||
            false => {
 | 
					            false => {
 | 
				
			||||||
@ -32,13 +36,11 @@ impl <R: Read> BlockRead for EmulateTapeReader<R> {
 | 
				
			|||||||
            true => {
 | 
					            true => {
 | 
				
			||||||
                // test buffer len after EOF test (to allow EOF test with small buffers in BufferedReader)
 | 
					                // test buffer len after EOF test (to allow EOF test with small buffers in BufferedReader)
 | 
				
			||||||
                if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE {
 | 
					                if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE {
 | 
				
			||||||
                    return Err(BlockReadError::Error(
 | 
					                    return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
 | 
				
			||||||
                        proxmox_lang::io_format_err!(
 | 
					 | 
				
			||||||
                        "EmulateTapeReader: read_block with wrong block size ({} != {})",
 | 
					                        "EmulateTapeReader: read_block with wrong block size ({} != {})",
 | 
				
			||||||
                        buffer.len(),
 | 
					                        buffer.len(),
 | 
				
			||||||
                        PROXMOX_TAPE_BLOCK_SIZE,
 | 
					                        PROXMOX_TAPE_BLOCK_SIZE,
 | 
				
			||||||
                        )
 | 
					                    )));
 | 
				
			||||||
                    ));
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                Ok(buffer.len())
 | 
					                Ok(buffer.len())
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -14,12 +14,10 @@ pub struct EmulateTapeWriter<W> {
 | 
				
			|||||||
    wrote_eof: bool,
 | 
					    wrote_eof: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <W: Write> EmulateTapeWriter<W> {
 | 
					impl<W: Write> EmulateTapeWriter<W> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Create a new instance allowing to write about max_size bytes
 | 
					    /// Create a new instance allowing to write about max_size bytes
 | 
				
			||||||
    pub fn new(writer: W, max_size: usize) -> Self {
 | 
					    pub fn new(writer: W, max_size: usize) -> Self {
 | 
				
			||||||
 | 
					        let mut max_blocks = max_size / PROXMOX_TAPE_BLOCK_SIZE;
 | 
				
			||||||
        let mut max_blocks = max_size/PROXMOX_TAPE_BLOCK_SIZE;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if max_blocks < 2 {
 | 
					        if max_blocks < 2 {
 | 
				
			||||||
            max_blocks = 2; // at least 2 blocks
 | 
					            max_blocks = 2; // at least 2 blocks
 | 
				
			||||||
@ -34,17 +32,20 @@ impl <W: Write> EmulateTapeWriter<W> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <W: Write> BlockWrite for EmulateTapeWriter<W> {
 | 
					impl<W: Write> BlockWrite for EmulateTapeWriter<W> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn write_block(&mut self, buffer: &[u8]) -> Result<bool, io::Error> {
 | 
					    fn write_block(&mut self, buffer: &[u8]) -> Result<bool, io::Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE {
 | 
					        if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE {
 | 
				
			||||||
            proxmox_lang::io_bail!("EmulateTapeWriter: got write with wrong block size ({} != {}",
 | 
					            proxmox_lang::io_bail!(
 | 
				
			||||||
                              buffer.len(), PROXMOX_TAPE_BLOCK_SIZE);
 | 
					                "EmulateTapeWriter: got write with wrong block size ({} != {}",
 | 
				
			||||||
 | 
					                buffer.len(),
 | 
				
			||||||
 | 
					                PROXMOX_TAPE_BLOCK_SIZE
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.block_nr >= self.max_blocks + 2 {
 | 
					        if self.block_nr >= self.max_blocks + 2 {
 | 
				
			||||||
            return Err(io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32));
 | 
					            return Err(io::Error::from_raw_os_error(
 | 
				
			||||||
 | 
					                nix::errno::Errno::ENOSPC as i32,
 | 
				
			||||||
 | 
					            ));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.writer.write_all(buffer)?;
 | 
					        self.writer.write_all(buffer)?;
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ use std::collections::HashSet;
 | 
				
			|||||||
use anyhow::{bail, Error};
 | 
					use anyhow::{bail, Error};
 | 
				
			||||||
use bitflags::bitflags;
 | 
					use bitflags::bitflags;
 | 
				
			||||||
use endian_trait::Endian;
 | 
					use endian_trait::Endian;
 | 
				
			||||||
use serde::{Serialize, Deserialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use serde_json::Value;
 | 
					use serde_json::Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use proxmox_uuid::Uuid;
 | 
					use proxmox_uuid::Uuid;
 | 
				
			||||||
@ -37,7 +37,7 @@ pub mod sg_tape;
 | 
				
			|||||||
pub mod sg_pt_changer;
 | 
					pub mod sg_pt_changer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// We use 256KB blocksize (always)
 | 
					/// We use 256KB blocksize (always)
 | 
				
			||||||
pub const PROXMOX_TAPE_BLOCK_SIZE: usize = 256*1024;
 | 
					pub const PROXMOX_TAPE_BLOCK_SIZE: usize = 256 * 1024;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// openssl::sha::sha256(b"Proxmox Tape Block Header v1.0")[0..8]
 | 
					// openssl::sha::sha256(b"Proxmox Tape Block Header v1.0")[0..8]
 | 
				
			||||||
pub const PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0: [u8; 8] = [220, 189, 175, 202, 235, 160, 165, 40];
 | 
					pub const PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0: [u8; 8] = [220, 189, 175, 202, 235, 160, 165, 40];
 | 
				
			||||||
@ -61,7 +61,7 @@ pub const PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0: [u8; 8] = [8, 96, 99, 249, 4
 | 
				
			|||||||
/// header has an additional size field. For streams of blocks, there
 | 
					/// header has an additional size field. For streams of blocks, there
 | 
				
			||||||
/// is a sequence number (`seq_nr`) which may be use for additional
 | 
					/// is a sequence number (`seq_nr`) which may be use for additional
 | 
				
			||||||
/// error checking.
 | 
					/// error checking.
 | 
				
			||||||
#[repr(C,packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
pub struct BlockHeader {
 | 
					pub struct BlockHeader {
 | 
				
			||||||
    /// fixed value `PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0`
 | 
					    /// fixed value `PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0`
 | 
				
			||||||
    pub magic: [u8; 8],
 | 
					    pub magic: [u8; 8],
 | 
				
			||||||
@ -84,7 +84,7 @@ bitflags! {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Endian, Copy, Clone, Debug)]
 | 
					#[derive(Endian, Copy, Clone, Debug)]
 | 
				
			||||||
#[repr(C,packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
/// Media Content Header
 | 
					/// Media Content Header
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// All tape files start with this header. The header may contain some
 | 
					/// All tape files start with this header. The header may contain some
 | 
				
			||||||
@ -115,11 +115,9 @@ pub struct MediaContentHeader {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MediaContentHeader {
 | 
					impl MediaContentHeader {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Create a new instance with autogenerated Uuid
 | 
					    /// Create a new instance with autogenerated Uuid
 | 
				
			||||||
    pub fn new(content_magic: [u8; 8], size: u32) -> Self {
 | 
					    pub fn new(content_magic: [u8; 8], size: u32) -> Self {
 | 
				
			||||||
        let uuid = *Uuid::generate()
 | 
					        let uuid = *Uuid::generate().into_inner();
 | 
				
			||||||
            .into_inner();
 | 
					 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            magic: PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
 | 
					            magic: PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
 | 
				
			||||||
            content_magic,
 | 
					            content_magic,
 | 
				
			||||||
@ -153,9 +151,7 @@ impl MediaContentHeader {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
impl BlockHeader {
 | 
					impl BlockHeader {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub const SIZE: usize = PROXMOX_TAPE_BLOCK_SIZE;
 | 
					    pub const SIZE: usize = PROXMOX_TAPE_BLOCK_SIZE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Allocates a new instance on the heap
 | 
					    /// Allocates a new instance on the heap
 | 
				
			||||||
@ -166,13 +162,9 @@ impl BlockHeader {
 | 
				
			|||||||
        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
 | 
					        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut buffer = unsafe {
 | 
					        let mut buffer = unsafe {
 | 
				
			||||||
            let ptr = alloc_zeroed(
 | 
					            let ptr = alloc_zeroed(Layout::from_size_align(Self::SIZE, page_size).unwrap());
 | 
				
			||||||
                 Layout::from_size_align(Self::SIZE, page_size)
 | 
					 | 
				
			||||||
                    .unwrap(),
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            Box::from_raw(
 | 
					            Box::from_raw(
 | 
				
			||||||
                std::slice::from_raw_parts_mut(ptr, Self::SIZE - 16)
 | 
					                std::slice::from_raw_parts_mut(ptr, Self::SIZE - 16) as *mut [u8] as *mut Self,
 | 
				
			||||||
                    as *mut [u8] as *mut Self
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        buffer.magic = PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0;
 | 
					        buffer.magic = PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0;
 | 
				
			||||||
@ -187,7 +179,7 @@ impl BlockHeader {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Returns the `size` field
 | 
					    /// Returns the `size` field
 | 
				
			||||||
    pub fn size(&self) -> usize {
 | 
					    pub fn size(&self) -> usize {
 | 
				
			||||||
        (self.size[0] as usize) + ((self.size[1] as usize)<<8) + ((self.size[2] as usize)<<16)
 | 
					        (self.size[0] as usize) + ((self.size[1] as usize) << 8) + ((self.size[2] as usize) << 16)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Set the `seq_nr` field
 | 
					    /// Set the `seq_nr` field
 | 
				
			||||||
@ -263,16 +255,19 @@ pub struct MtxStatus {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MtxStatus {
 | 
					impl MtxStatus {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn slot_address(&self, slot: u64) -> Result<u16, Error> {
 | 
					    pub fn slot_address(&self, slot: u64) -> Result<u16, Error> {
 | 
				
			||||||
        if slot == 0 {
 | 
					        if slot == 0 {
 | 
				
			||||||
            bail!("invalid slot number '{}' (slots numbers starts at 1)", slot);
 | 
					            bail!("invalid slot number '{}' (slots numbers starts at 1)", slot);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if slot > (self.slots.len() as u64) {
 | 
					        if slot > (self.slots.len() as u64) {
 | 
				
			||||||
            bail!("invalid slot number '{}' (max {} slots)", slot, self.slots.len());
 | 
					            bail!(
 | 
				
			||||||
 | 
					                "invalid slot number '{}' (max {} slots)",
 | 
				
			||||||
 | 
					                slot,
 | 
				
			||||||
 | 
					                self.slots.len()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(self.slots[(slot -1) as usize].element_address)
 | 
					        Ok(self.slots[(slot - 1) as usize].element_address)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn drive_address(&self, drivenum: u64) -> Result<u16, Error> {
 | 
					    pub fn drive_address(&self, drivenum: u64) -> Result<u16, Error> {
 | 
				
			||||||
@ -287,8 +282,7 @@ impl MtxStatus {
 | 
				
			|||||||
        // simply use first transport
 | 
					        // simply use first transport
 | 
				
			||||||
        // (are there changers exposing more than one?)
 | 
					        // (are there changers exposing more than one?)
 | 
				
			||||||
        // defaults to 0 for changer that do not report transports
 | 
					        // defaults to 0 for changer that do not report transports
 | 
				
			||||||
        self
 | 
					        self.transports
 | 
				
			||||||
            .transports
 | 
					 | 
				
			||||||
            .get(0)
 | 
					            .get(0)
 | 
				
			||||||
            .map(|t| t.element_address)
 | 
					            .map(|t| t.element_address)
 | 
				
			||||||
            .unwrap_or(0u16)
 | 
					            .unwrap_or(0u16)
 | 
				
			||||||
@ -301,14 +295,14 @@ impl MtxStatus {
 | 
				
			|||||||
                continue; // skip slots of wrong type
 | 
					                continue; // skip slots of wrong type
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if let ElementStatus::Empty = slot_info.status {
 | 
					            if let ElementStatus::Empty = slot_info.status {
 | 
				
			||||||
                free_slot = Some((i+1) as u64);
 | 
					                free_slot = Some((i + 1) as u64);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        free_slot
 | 
					        free_slot
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn mark_import_export_slots(&mut self, config: &ScsiTapeChanger) -> Result<(), Error>{
 | 
					    pub fn mark_import_export_slots(&mut self, config: &ScsiTapeChanger) -> Result<(), Error> {
 | 
				
			||||||
        let mut export_slots: HashSet<u64> = HashSet::new();
 | 
					        let mut export_slots: HashSet<u64> = HashSet::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(slots) = &config.export_slots {
 | 
					        if let Some(slots) = &config.export_slots {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,8 @@
 | 
				
			|||||||
use std::path::{Path, PathBuf};
 | 
					 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::fs::{OpenOptions, File};
 | 
					use std::fs::{File, OpenOptions};
 | 
				
			||||||
use std::os::unix::fs::OpenOptionsExt;
 | 
					use std::os::unix::fs::OpenOptionsExt;
 | 
				
			||||||
use std::os::unix::io::AsRawFd;
 | 
					use std::os::unix::io::AsRawFd;
 | 
				
			||||||
 | 
					use std::path::{Path, PathBuf};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::{bail, format_err, Error};
 | 
					use anyhow::{bail, format_err, Error};
 | 
				
			||||||
use nix::fcntl::{fcntl, FcntlArg, OFlag};
 | 
					use nix::fcntl::{fcntl, FcntlArg, OFlag};
 | 
				
			||||||
@ -12,21 +12,20 @@ use proxmox_sys::fs::scan_subdir;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use pbs_api_types::{DeviceKind, OptionalDeviceIdentification, TapeDeviceInfo};
 | 
					use pbs_api_types::{DeviceKind, OptionalDeviceIdentification, TapeDeviceInfo};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lazy_static::lazy_static!{
 | 
					lazy_static::lazy_static! {
 | 
				
			||||||
    static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
 | 
					    static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
 | 
				
			||||||
        regex::Regex::new(r"^sg\d+$").unwrap();
 | 
					        regex::Regex::new(r"^sg\d+$").unwrap();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// List linux tape changer devices
 | 
					/// List linux tape changer devices
 | 
				
			||||||
pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
 | 
					pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut list = Vec::new();
 | 
					    let mut list = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let dir_iter = match scan_subdir(
 | 
					    let dir_iter = match scan_subdir(
 | 
				
			||||||
        libc::AT_FDCWD,
 | 
					        libc::AT_FDCWD,
 | 
				
			||||||
        "/sys/class/scsi_generic",
 | 
					        "/sys/class/scsi_generic",
 | 
				
			||||||
        &SCSI_GENERIC_NAME_REGEX)
 | 
					        &SCSI_GENERIC_NAME_REGEX,
 | 
				
			||||||
    {
 | 
					    ) {
 | 
				
			||||||
        Err(_) => return list,
 | 
					        Err(_) => return list,
 | 
				
			||||||
        Ok(iter) => iter,
 | 
					        Ok(iter) => iter,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@ -63,7 +62,9 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
 | 
				
			|||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            _ => { continue; }
 | 
					            _ => {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // let mut test_path = sys_path.clone();
 | 
					        // let mut test_path = sys_path.clone();
 | 
				
			||||||
@ -75,22 +76,42 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
 | 
				
			|||||||
            Some(dev_path) => dev_path,
 | 
					            Some(dev_path) => dev_path,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let serial = match device.property_value("ID_SCSI_SERIAL")
 | 
					        let serial = match device
 | 
				
			||||||
 | 
					            .property_value("ID_SCSI_SERIAL")
 | 
				
			||||||
            .map(std::ffi::OsString::from)
 | 
					            .map(std::ffi::OsString::from)
 | 
				
			||||||
            .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
 | 
					            .and_then(|s| {
 | 
				
			||||||
        {
 | 
					                if let Ok(s) = s.into_string() {
 | 
				
			||||||
 | 
					                    Some(s)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    None
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }) {
 | 
				
			||||||
            None => continue,
 | 
					            None => continue,
 | 
				
			||||||
            Some(serial) => serial,
 | 
					            Some(serial) => serial,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let vendor = device.property_value("ID_VENDOR")
 | 
					        let vendor = device
 | 
				
			||||||
 | 
					            .property_value("ID_VENDOR")
 | 
				
			||||||
            .map(std::ffi::OsString::from)
 | 
					            .map(std::ffi::OsString::from)
 | 
				
			||||||
            .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
 | 
					            .and_then(|s| {
 | 
				
			||||||
 | 
					                if let Ok(s) = s.into_string() {
 | 
				
			||||||
 | 
					                    Some(s)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    None
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
            .unwrap_or_else(|| String::from("unknown"));
 | 
					            .unwrap_or_else(|| String::from("unknown"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let model = device.property_value("ID_MODEL")
 | 
					        let model = device
 | 
				
			||||||
 | 
					            .property_value("ID_MODEL")
 | 
				
			||||||
            .map(std::ffi::OsString::from)
 | 
					            .map(std::ffi::OsString::from)
 | 
				
			||||||
            .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
 | 
					            .and_then(|s| {
 | 
				
			||||||
 | 
					                if let Ok(s) = s.into_string() {
 | 
				
			||||||
 | 
					                    Some(s)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    None
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
            .unwrap_or_else(|| String::from("unknown"));
 | 
					            .unwrap_or_else(|| String::from("unknown"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let dev_path = format!("/dev/tape/by-id/scsi-{}", serial);
 | 
					        let dev_path = format!("/dev/tape/by-id/scsi-{}", serial);
 | 
				
			||||||
@ -113,14 +134,13 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// List LTO drives
 | 
					/// List LTO drives
 | 
				
			||||||
pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
 | 
					pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut list = Vec::new();
 | 
					    let mut list = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let dir_iter = match scan_subdir(
 | 
					    let dir_iter = match scan_subdir(
 | 
				
			||||||
        libc::AT_FDCWD,
 | 
					        libc::AT_FDCWD,
 | 
				
			||||||
        "/sys/class/scsi_generic",
 | 
					        "/sys/class/scsi_generic",
 | 
				
			||||||
        &SCSI_GENERIC_NAME_REGEX)
 | 
					        &SCSI_GENERIC_NAME_REGEX,
 | 
				
			||||||
    {
 | 
					    ) {
 | 
				
			||||||
        Err(_) => return list,
 | 
					        Err(_) => return list,
 | 
				
			||||||
        Ok(iter) => iter,
 | 
					        Ok(iter) => iter,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@ -157,7 +177,9 @@ pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
 | 
				
			|||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            _ => { continue; }
 | 
					            _ => {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // let mut test_path = sys_path.clone();
 | 
					        // let mut test_path = sys_path.clone();
 | 
				
			||||||
@ -169,22 +191,42 @@ pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
 | 
				
			|||||||
            Some(dev_path) => dev_path,
 | 
					            Some(dev_path) => dev_path,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let serial = match device.property_value("ID_SCSI_SERIAL")
 | 
					        let serial = match device
 | 
				
			||||||
 | 
					            .property_value("ID_SCSI_SERIAL")
 | 
				
			||||||
            .map(std::ffi::OsString::from)
 | 
					            .map(std::ffi::OsString::from)
 | 
				
			||||||
            .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
 | 
					            .and_then(|s| {
 | 
				
			||||||
        {
 | 
					                if let Ok(s) = s.into_string() {
 | 
				
			||||||
 | 
					                    Some(s)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    None
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }) {
 | 
				
			||||||
            None => continue,
 | 
					            None => continue,
 | 
				
			||||||
            Some(serial) => serial,
 | 
					            Some(serial) => serial,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let vendor = device.property_value("ID_VENDOR")
 | 
					        let vendor = device
 | 
				
			||||||
 | 
					            .property_value("ID_VENDOR")
 | 
				
			||||||
            .map(std::ffi::OsString::from)
 | 
					            .map(std::ffi::OsString::from)
 | 
				
			||||||
            .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
 | 
					            .and_then(|s| {
 | 
				
			||||||
 | 
					                if let Ok(s) = s.into_string() {
 | 
				
			||||||
 | 
					                    Some(s)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    None
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
            .unwrap_or_else(|| String::from("unknown"));
 | 
					            .unwrap_or_else(|| String::from("unknown"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let model = device.property_value("ID_MODEL")
 | 
					        let model = device
 | 
				
			||||||
 | 
					            .property_value("ID_MODEL")
 | 
				
			||||||
            .map(std::ffi::OsString::from)
 | 
					            .map(std::ffi::OsString::from)
 | 
				
			||||||
            .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
 | 
					            .and_then(|s| {
 | 
				
			||||||
 | 
					                if let Ok(s) = s.into_string() {
 | 
				
			||||||
 | 
					                    Some(s)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    None
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
            .unwrap_or_else(|| String::from("unknown"));
 | 
					            .unwrap_or_else(|| String::from("unknown"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let dev_path = format!("/dev/tape/by-id/scsi-{}-sg", serial);
 | 
					        let dev_path = format!("/dev/tape/by-id/scsi-{}-sg", serial);
 | 
				
			||||||
@ -206,17 +248,14 @@ pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Test if a device exists, and returns associated `TapeDeviceInfo`
 | 
					/// Test if a device exists, and returns associated `TapeDeviceInfo`
 | 
				
			||||||
pub fn lookup_device<'a>(
 | 
					pub fn lookup_device<'a>(devices: &'a [TapeDeviceInfo], path: &str) -> Option<&'a TapeDeviceInfo> {
 | 
				
			||||||
    devices: &'a[TapeDeviceInfo],
 | 
					 | 
				
			||||||
    path: &str,
 | 
					 | 
				
			||||||
) -> Option<&'a TapeDeviceInfo> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if let Ok(stat) = nix::sys::stat::stat(path) {
 | 
					    if let Ok(stat) = nix::sys::stat::stat(path) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let major = unsafe { libc::major(stat.st_rdev) };
 | 
					        let major = unsafe { libc::major(stat.st_rdev) };
 | 
				
			||||||
        let minor = unsafe { libc::minor(stat.st_rdev) };
 | 
					        let minor = unsafe { libc::minor(stat.st_rdev) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        devices.iter().find(|d| d.major == major && d.minor == minor)
 | 
					        devices
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .find(|d| d.major == major && d.minor == minor)
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        None
 | 
					        None
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -224,10 +263,9 @@ pub fn lookup_device<'a>(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Lookup optional drive identification attributes
 | 
					/// Lookup optional drive identification attributes
 | 
				
			||||||
pub fn lookup_device_identification<'a>(
 | 
					pub fn lookup_device_identification<'a>(
 | 
				
			||||||
    devices: &'a[TapeDeviceInfo],
 | 
					    devices: &'a [TapeDeviceInfo],
 | 
				
			||||||
    path: &str,
 | 
					    path: &str,
 | 
				
			||||||
) -> OptionalDeviceIdentification {
 | 
					) -> OptionalDeviceIdentification {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if let Some(info) = lookup_device(devices, path) {
 | 
					    if let Some(info) = lookup_device(devices, path) {
 | 
				
			||||||
        OptionalDeviceIdentification {
 | 
					        OptionalDeviceIdentification {
 | 
				
			||||||
            vendor: Some(info.vendor.clone()),
 | 
					            vendor: Some(info.vendor.clone()),
 | 
				
			||||||
@ -244,20 +282,15 @@ pub fn lookup_device_identification<'a>(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Make sure path is a lto tape device
 | 
					/// Make sure path is a lto tape device
 | 
				
			||||||
pub fn check_drive_path(
 | 
					pub fn check_drive_path(drives: &[TapeDeviceInfo], path: &str) -> Result<(), Error> {
 | 
				
			||||||
    drives: &[TapeDeviceInfo],
 | 
					 | 
				
			||||||
    path: &str,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
    if lookup_device(drives, path).is_none() {
 | 
					    if lookup_device(drives, path).is_none() {
 | 
				
			||||||
        bail!("path '{}' is not a lto SCSI-generic tape device", path);
 | 
					        bail!("path '{}' is not a lto SCSI-generic tape device", path);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Check for correct Major/Minor numbers
 | 
					/// Check for correct Major/Minor numbers
 | 
				
			||||||
pub fn check_tape_is_lto_tape_device(file: &File) -> Result<(), Error> {
 | 
					pub fn check_tape_is_lto_tape_device(file: &File) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let stat = nix::sys::stat::fstat(file.as_raw_fd())?;
 | 
					    let stat = nix::sys::stat::fstat(file.as_raw_fd())?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let devnum = stat.st_rdev;
 | 
					    let devnum = stat.st_rdev;
 | 
				
			||||||
@ -281,10 +314,7 @@ pub fn check_tape_is_lto_tape_device(file: &File) -> Result<(), Error> {
 | 
				
			|||||||
/// The open call use O_NONBLOCK, but that flag is cleard after open
 | 
					/// The open call use O_NONBLOCK, but that flag is cleard after open
 | 
				
			||||||
/// succeeded. This also checks if the device is a non-rewinding tape
 | 
					/// succeeded. This also checks if the device is a non-rewinding tape
 | 
				
			||||||
/// device.
 | 
					/// device.
 | 
				
			||||||
pub fn open_lto_tape_device(
 | 
					pub fn open_lto_tape_device(path: &str) -> Result<File, Error> {
 | 
				
			||||||
    path: &str,
 | 
					 | 
				
			||||||
) -> Result<File, Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let file = OpenOptions::new()
 | 
					    let file = OpenOptions::new()
 | 
				
			||||||
        .read(true)
 | 
					        .read(true)
 | 
				
			||||||
        .write(true)
 | 
					        .write(true)
 | 
				
			||||||
@ -293,14 +323,12 @@ pub fn open_lto_tape_device(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // clear O_NONBLOCK from now on.
 | 
					    // clear O_NONBLOCK from now on.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL)
 | 
					    let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL).into_io_result()?;
 | 
				
			||||||
        .into_io_result()?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut flags = OFlag::from_bits_truncate(flags);
 | 
					    let mut flags = OFlag::from_bits_truncate(flags);
 | 
				
			||||||
    flags.remove(OFlag::O_NONBLOCK);
 | 
					    flags.remove(OFlag::O_NONBLOCK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags))
 | 
					    fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)).into_io_result()?;
 | 
				
			||||||
        .into_io_result()?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    check_tape_is_lto_tape_device(&file)
 | 
					    check_tape_is_lto_tape_device(&file)
 | 
				
			||||||
        .map_err(|err| format_err!("device type check {:?} failed - {}", path, err))?;
 | 
					        .map_err(|err| format_err!("device type check {:?} failed - {}", path, err))?;
 | 
				
			||||||
@ -308,15 +336,20 @@ pub fn open_lto_tape_device(
 | 
				
			|||||||
    Ok(file)
 | 
					    Ok(file)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
// shell completion helper
 | 
					// shell completion helper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// List changer device paths
 | 
					/// List changer device paths
 | 
				
			||||||
pub fn complete_changer_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
 | 
					pub fn complete_changer_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
 | 
				
			||||||
    linux_tape_changer_list().iter().map(|v| v.path.clone()).collect()
 | 
					    linux_tape_changer_list()
 | 
				
			||||||
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .map(|v| v.path.clone())
 | 
				
			||||||
 | 
					        .collect()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// List tape device paths
 | 
					/// List tape device paths
 | 
				
			||||||
pub fn complete_drive_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
 | 
					pub fn complete_drive_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
 | 
				
			||||||
    lto_tape_device_list().iter().map(|v| v.path.clone()).collect()
 | 
					    lto_tape_device_list()
 | 
				
			||||||
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .map(|v| v.path.clone())
 | 
				
			||||||
 | 
					        .collect()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
//! SCSI changer implementation using libsgutil2
 | 
					//! SCSI changer implementation using libsgutil2
 | 
				
			||||||
use std::os::unix::prelude::AsRawFd;
 | 
					 | 
				
			||||||
use std::io::Read;
 | 
					 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					use std::fs::{File, OpenOptions};
 | 
				
			||||||
 | 
					use std::io::Read;
 | 
				
			||||||
 | 
					use std::os::unix::prelude::AsRawFd;
 | 
				
			||||||
use std::path::Path;
 | 
					use std::path::Path;
 | 
				
			||||||
use std::fs::{OpenOptions, File};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::{bail, format_err, Error};
 | 
					use anyhow::{bail, format_err, Error};
 | 
				
			||||||
use endian_trait::Endian;
 | 
					use endian_trait::Endian;
 | 
				
			||||||
@ -13,31 +13,25 @@ use proxmox_io::ReadExt;
 | 
				
			|||||||
use pbs_api_types::ScsiTapeChanger;
 | 
					use pbs_api_types::ScsiTapeChanger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    ElementStatus,MtxStatus,TransportElementStatus,DriveStatus,StorageElementStatus,
 | 
					    sgutils2::{scsi_ascii_to_string, scsi_inquiry, ScsiError, SgRaw, SENSE_KEY_NOT_READY},
 | 
				
			||||||
    sgutils2::{
 | 
					    DriveStatus, ElementStatus, MtxStatus, StorageElementStatus, TransportElementStatus,
 | 
				
			||||||
        SgRaw,
 | 
					 | 
				
			||||||
        SENSE_KEY_NOT_READY,
 | 
					 | 
				
			||||||
        ScsiError,
 | 
					 | 
				
			||||||
        scsi_ascii_to_string,
 | 
					 | 
				
			||||||
        scsi_inquiry,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SCSI_CHANGER_DEFAULT_TIMEOUT: usize = 60*5; // 5 minutes
 | 
					const SCSI_CHANGER_DEFAULT_TIMEOUT: usize = 60 * 5; // 5 minutes
 | 
				
			||||||
const SCSI_VOLUME_TAG_LEN: usize = 36;
 | 
					const SCSI_VOLUME_TAG_LEN: usize = 36;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Initialize element status (Inventory)
 | 
					/// Initialize element status (Inventory)
 | 
				
			||||||
pub fn initialize_element_status<F: AsRawFd>(file: &mut F) -> Result<(), Error> {
 | 
					pub fn initialize_element_status<F: AsRawFd>(file: &mut F) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, 64)?;
 | 
					    let mut sg_raw = SgRaw::new(file, 64)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // like mtx(1), set a very long timeout (30 minutes)
 | 
					    // like mtx(1), set a very long timeout (30 minutes)
 | 
				
			||||||
    sg_raw.set_timeout(30*60);
 | 
					    sg_raw.set_timeout(30 * 60);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.extend(&[0x07, 0, 0, 0, 0, 0]); // INITIALIZE ELEMENT STATUS (07h)
 | 
					    cmd.extend(&[0x07, 0, 0, 0, 0, 0]); // INITIALIZE ELEMENT STATUS (07h)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_command(&cmd)
 | 
					    sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("initializte element status (07h) failed - {}", err))?;
 | 
					        .map_err(|err| format_err!("initializte element status (07h) failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
@ -78,7 +72,6 @@ fn execute_scsi_command<F: AsRawFd>(
 | 
				
			|||||||
    error_prefix: &str,
 | 
					    error_prefix: &str,
 | 
				
			||||||
    retry: bool,
 | 
					    retry: bool,
 | 
				
			||||||
) -> Result<Vec<u8>, Error> {
 | 
					) -> Result<Vec<u8>, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let start = std::time::SystemTime::now();
 | 
					    let start = std::time::SystemTime::now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut last_msg: Option<String> = None;
 | 
					    let mut last_msg: Option<String> = None;
 | 
				
			||||||
@ -103,9 +96,12 @@ fn execute_scsi_command<F: AsRawFd>(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                if let ScsiError::Sense(ref sense) = err {
 | 
					                if let ScsiError::Sense(ref sense) = err {
 | 
				
			||||||
                    // Not Ready - becoming ready
 | 
					                    // Not Ready - becoming ready
 | 
				
			||||||
                    if sense.sense_key == SENSE_KEY_NOT_READY && sense.asc == 0x04 && sense.ascq == 1 {
 | 
					                    if sense.sense_key == SENSE_KEY_NOT_READY
 | 
				
			||||||
 | 
					                        && sense.asc == 0x04
 | 
				
			||||||
 | 
					                        && sense.ascq == 1
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
                        // wait up to 5 minutes, long enough to finish inventorize
 | 
					                        // wait up to 5 minutes, long enough to finish inventorize
 | 
				
			||||||
                        timeout = std::time::Duration::new(5*60, 0);
 | 
					                        timeout = std::time::Duration::new(5 * 60, 0);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -120,11 +116,9 @@ fn execute_scsi_command<F: AsRawFd>(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
fn read_element_address_assignment<F: AsRawFd>(
 | 
					fn read_element_address_assignment<F: AsRawFd>(
 | 
				
			||||||
    file: &mut F,
 | 
					    file: &mut F,
 | 
				
			||||||
) -> Result<AddressAssignmentPage, Error> {
 | 
					) -> Result<AddressAssignmentPage, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let allocation_len: u8 = u8::MAX;
 | 
					    let allocation_len: u8 = u8::MAX;
 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
					    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
				
			||||||
    sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
 | 
					    sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
 | 
				
			||||||
@ -148,7 +142,8 @@ fn read_element_address_assignment<F: AsRawFd>(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(page)
 | 
					        Ok(page)
 | 
				
			||||||
    }).map_err(|err: Error| format_err!("decode element address assignment page failed - {}", err))
 | 
					    })
 | 
				
			||||||
 | 
					    .map_err(|err: Error| format_err!("decode element address assignment page failed - {}", err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn scsi_move_medium_cdb(
 | 
					fn scsi_move_medium_cdb(
 | 
				
			||||||
@ -156,7 +151,6 @@ fn scsi_move_medium_cdb(
 | 
				
			|||||||
    source_element_address: u16,
 | 
					    source_element_address: u16,
 | 
				
			||||||
    destination_element_address: u16,
 | 
					    destination_element_address: u16,
 | 
				
			||||||
) -> Vec<u8> {
 | 
					) -> Vec<u8> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.push(0xA5); // MOVE MEDIUM (A5h)
 | 
					    cmd.push(0xA5); // MOVE MEDIUM (A5h)
 | 
				
			||||||
    cmd.push(0); // reserved
 | 
					    cmd.push(0); // reserved
 | 
				
			||||||
@ -172,11 +166,7 @@ fn scsi_move_medium_cdb(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Load media from storage slot into drive
 | 
					/// Load media from storage slot into drive
 | 
				
			||||||
pub fn load_slot(
 | 
					pub fn load_slot(file: &mut File, from_slot: u64, drivenum: u64) -> Result<(), Error> {
 | 
				
			||||||
    file: &mut File,
 | 
					 | 
				
			||||||
    from_slot: u64,
 | 
					 | 
				
			||||||
    drivenum: u64,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
    let status = read_element_status(file)?;
 | 
					    let status = read_element_status(file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let transport_address = status.transport_address();
 | 
					    let transport_address = status.transport_address();
 | 
				
			||||||
@ -192,19 +182,15 @@ pub fn load_slot(
 | 
				
			|||||||
    let mut sg_raw = SgRaw::new(file, 64)?;
 | 
					    let mut sg_raw = SgRaw::new(file, 64)?;
 | 
				
			||||||
    sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
 | 
					    sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_command(&cmd)
 | 
					    sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("load drive failed - {}", err))?;
 | 
					        .map_err(|err| format_err!("load drive failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Unload media from drive into a storage slot
 | 
					/// Unload media from drive into a storage slot
 | 
				
			||||||
pub fn unload(
 | 
					pub fn unload(file: &mut File, to_slot: u64, drivenum: u64) -> Result<(), Error> {
 | 
				
			||||||
    file: &mut File,
 | 
					 | 
				
			||||||
    to_slot: u64,
 | 
					 | 
				
			||||||
    drivenum: u64,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let status = read_element_status(file)?;
 | 
					    let status = read_element_status(file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let transport_address = status.transport_address();
 | 
					    let transport_address = status.transport_address();
 | 
				
			||||||
@ -220,7 +206,8 @@ pub fn unload(
 | 
				
			|||||||
    let mut sg_raw = SgRaw::new(file, 64)?;
 | 
					    let mut sg_raw = SgRaw::new(file, 64)?;
 | 
				
			||||||
    sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
 | 
					    sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_command(&cmd)
 | 
					    sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("unload drive failed - {}", err))?;
 | 
					        .map_err(|err| format_err!("unload drive failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
@ -232,7 +219,6 @@ pub fn transfer_medium<F: AsRawFd>(
 | 
				
			|||||||
    from_slot: u64,
 | 
					    from_slot: u64,
 | 
				
			||||||
    to_slot: u64,
 | 
					    to_slot: u64,
 | 
				
			||||||
) -> Result<(), Error> {
 | 
					) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let status = read_element_status(file)?;
 | 
					    let status = read_element_status(file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let transport_address = status.transport_address();
 | 
					    let transport_address = status.transport_address();
 | 
				
			||||||
@ -248,10 +234,13 @@ pub fn transfer_medium<F: AsRawFd>(
 | 
				
			|||||||
    let mut sg_raw = SgRaw::new(file, 64)?;
 | 
					    let mut sg_raw = SgRaw::new(file, 64)?;
 | 
				
			||||||
    sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
 | 
					    sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_command(&cmd)
 | 
					    sg_raw.do_command(&cmd).map_err(|err| {
 | 
				
			||||||
        .map_err(|err| {
 | 
					        format_err!(
 | 
				
			||||||
            format_err!("transfer medium from slot {} to slot {} failed - {}",
 | 
					            "transfer medium from slot {} to slot {} failed - {}",
 | 
				
			||||||
                        from_slot, to_slot, err)
 | 
					            from_slot,
 | 
				
			||||||
 | 
					            to_slot,
 | 
				
			||||||
 | 
					            err
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    })?;
 | 
					    })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
@ -293,7 +282,6 @@ fn scsi_read_element_status_cdb(
 | 
				
			|||||||
    element_type: ElementType,
 | 
					    element_type: ElementType,
 | 
				
			||||||
    allocation_len: u32,
 | 
					    allocation_len: u32,
 | 
				
			||||||
) -> Vec<u8> {
 | 
					) -> Vec<u8> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.push(0xB8); // READ ELEMENT STATUS (B8h)
 | 
					    cmd.push(0xB8); // READ ELEMENT STATUS (B8h)
 | 
				
			||||||
    cmd.push(element_type.byte1());
 | 
					    cmd.push(element_type.byte1());
 | 
				
			||||||
@ -315,7 +303,6 @@ fn get_element<F: AsRawFd>(
 | 
				
			|||||||
    allocation_len: u32,
 | 
					    allocation_len: u32,
 | 
				
			||||||
    mut retry: bool,
 | 
					    mut retry: bool,
 | 
				
			||||||
) -> Result<DecodedStatusPage, Error> {
 | 
					) -> Result<DecodedStatusPage, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut start_element_address = 0;
 | 
					    let mut start_element_address = 0;
 | 
				
			||||||
    let number_of_elements: u16 = 1000; // some changers limit the query
 | 
					    let number_of_elements: u16 = 1000; // some changers limit the query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -328,7 +315,12 @@ fn get_element<F: AsRawFd>(
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    loop {
 | 
					    loop {
 | 
				
			||||||
        let cmd = scsi_read_element_status_cdb(start_element_address, number_of_elements, element_type, allocation_len);
 | 
					        let cmd = scsi_read_element_status_cdb(
 | 
				
			||||||
 | 
					            start_element_address,
 | 
				
			||||||
 | 
					            number_of_elements,
 | 
				
			||||||
 | 
					            element_type,
 | 
				
			||||||
 | 
					            allocation_len,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let data = execute_scsi_command(sg_raw, &cmd, "read element status (B8h)", retry)?;
 | 
					        let data = execute_scsi_command(sg_raw, &cmd, "read element status (B8h)", retry)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -364,7 +356,6 @@ fn get_element<F: AsRawFd>(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Read element status.
 | 
					/// Read element status.
 | 
				
			||||||
pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error> {
 | 
					pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let inquiry = scsi_inquiry(file)?;
 | 
					    let inquiry = scsi_inquiry(file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if inquiry.peripheral_type != 8 {
 | 
					    if inquiry.peripheral_type != 8 {
 | 
				
			||||||
@ -387,15 +378,30 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error>
 | 
				
			|||||||
    let page = get_element(&mut sg_raw, ElementType::Storage, allocation_len, true)?;
 | 
					    let page = get_element(&mut sg_raw, ElementType::Storage, allocation_len, true)?;
 | 
				
			||||||
    storage_slots.extend(page.storage_slots);
 | 
					    storage_slots.extend(page.storage_slots);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let page = get_element(&mut sg_raw, ElementType::ImportExport, allocation_len, false)?;
 | 
					    let page = get_element(
 | 
				
			||||||
 | 
					        &mut sg_raw,
 | 
				
			||||||
 | 
					        ElementType::ImportExport,
 | 
				
			||||||
 | 
					        allocation_len,
 | 
				
			||||||
 | 
					        false,
 | 
				
			||||||
 | 
					    )?;
 | 
				
			||||||
    import_export_slots.extend(page.import_export_slots);
 | 
					    import_export_slots.extend(page.import_export_slots);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let page = get_element(&mut sg_raw, ElementType::DataTransfer, allocation_len, false)?;
 | 
					    let page = get_element(
 | 
				
			||||||
 | 
					        &mut sg_raw,
 | 
				
			||||||
 | 
					        ElementType::DataTransfer,
 | 
				
			||||||
 | 
					        allocation_len,
 | 
				
			||||||
 | 
					        false,
 | 
				
			||||||
 | 
					    )?;
 | 
				
			||||||
    drives.extend(page.drives);
 | 
					    drives.extend(page.drives);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // get the serial + vendor + model,
 | 
					    // get the serial + vendor + model,
 | 
				
			||||||
    // some changer require this to be an extra scsi command
 | 
					    // some changer require this to be an extra scsi command
 | 
				
			||||||
    let page = get_element(&mut sg_raw, ElementType::DataTransferWithDVCID, allocation_len, false)?;
 | 
					    let page = get_element(
 | 
				
			||||||
 | 
					        &mut sg_raw,
 | 
				
			||||||
 | 
					        ElementType::DataTransferWithDVCID,
 | 
				
			||||||
 | 
					        allocation_len,
 | 
				
			||||||
 | 
					        false,
 | 
				
			||||||
 | 
					    )?;
 | 
				
			||||||
    // should be in same order and same count, but be on the safe side.
 | 
					    // should be in same order and same count, but be on the safe side.
 | 
				
			||||||
    // there should not be too many drives normally
 | 
					    // there should not be too many drives normally
 | 
				
			||||||
    for drive in drives.iter_mut() {
 | 
					    for drive in drives.iter_mut() {
 | 
				
			||||||
@ -408,7 +414,12 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error>
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let page = get_element(&mut sg_raw, ElementType::MediumTransport, allocation_len, false)?;
 | 
					    let page = get_element(
 | 
				
			||||||
 | 
					        &mut sg_raw,
 | 
				
			||||||
 | 
					        ElementType::MediumTransport,
 | 
				
			||||||
 | 
					        allocation_len,
 | 
				
			||||||
 | 
					        false,
 | 
				
			||||||
 | 
					    )?;
 | 
				
			||||||
    transports.extend(page.transports);
 | 
					    transports.extend(page.transports);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let transport_count = setup.transport_element_count as usize;
 | 
					    let transport_count = setup.transport_element_count as usize;
 | 
				
			||||||
@ -451,7 +462,11 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error>
 | 
				
			|||||||
    let mut slots = storage_slots;
 | 
					    let mut slots = storage_slots;
 | 
				
			||||||
    slots.extend(import_export_slots);
 | 
					    slots.extend(import_export_slots);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut status = MtxStatus { transports, drives, slots };
 | 
					    let mut status = MtxStatus {
 | 
				
			||||||
 | 
					        transports,
 | 
				
			||||||
 | 
					        drives,
 | 
				
			||||||
 | 
					        slots,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // sanity checks
 | 
					    // sanity checks
 | 
				
			||||||
    if status.drives.is_empty() {
 | 
					    if status.drives.is_empty() {
 | 
				
			||||||
@ -482,8 +497,7 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error>
 | 
				
			|||||||
pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
 | 
					pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
 | 
				
			||||||
    let path = &config.path;
 | 
					    let path = &config.path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut file = open(path)
 | 
					    let mut file = open(path).map_err(|err| format_err!("error opening '{}': {}", path, err))?;
 | 
				
			||||||
        .map_err(|err| format_err!("error opening '{}': {}", path, err))?;
 | 
					 | 
				
			||||||
    let mut status = read_element_status(&mut file)
 | 
					    let mut status = read_element_status(&mut file)
 | 
				
			||||||
        .map_err(|err| format_err!("error reading element status: {}", err))?;
 | 
					        .map_err(|err| format_err!("error reading element status: {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -492,14 +506,13 @@ pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
 | 
				
			|||||||
    Ok(status)
 | 
					    Ok(status)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[repr(C, packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
#[derive(Endian)]
 | 
					#[derive(Endian)]
 | 
				
			||||||
struct ElementStatusHeader {
 | 
					struct ElementStatusHeader {
 | 
				
			||||||
    first_element_address_reported: u16,
 | 
					    first_element_address_reported: u16,
 | 
				
			||||||
    number_of_elements_available: u16,
 | 
					    number_of_elements_available: u16,
 | 
				
			||||||
    reserved: u8,
 | 
					    reserved: u8,
 | 
				
			||||||
    byte_count_of_report_available: [u8;3],
 | 
					    byte_count_of_report_available: [u8; 3],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[repr(C, packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
@ -509,18 +522,17 @@ struct SubHeader {
 | 
				
			|||||||
    flags: u8,
 | 
					    flags: u8,
 | 
				
			||||||
    descriptor_length: u16,
 | 
					    descriptor_length: u16,
 | 
				
			||||||
    reserved: u8,
 | 
					    reserved: u8,
 | 
				
			||||||
    byte_count_of_descriptor_data_available: [u8;3],
 | 
					    byte_count_of_descriptor_data_available: [u8; 3],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SubHeader {
 | 
					impl SubHeader {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn parse_optional_volume_tag<R: Read>(
 | 
					    fn parse_optional_volume_tag<R: Read>(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        reader: &mut R,
 | 
					        reader: &mut R,
 | 
				
			||||||
        full: bool,
 | 
					        full: bool,
 | 
				
			||||||
    ) -> Result<Option<String>, Error> {
 | 
					    ) -> Result<Option<String>, Error> {
 | 
				
			||||||
 | 
					        if (self.flags & 128) != 0 {
 | 
				
			||||||
        if (self.flags & 128) != 0 { // has PVolTag
 | 
					            // has PVolTag
 | 
				
			||||||
            let tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?;
 | 
					            let tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?;
 | 
				
			||||||
            if full {
 | 
					            if full {
 | 
				
			||||||
                let volume_tag = scsi_ascii_to_string(&tmp);
 | 
					                let volume_tag = scsi_ascii_to_string(&tmp);
 | 
				
			||||||
@ -532,12 +544,9 @@ impl SubHeader {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // AFAIK, tape changer do not use AlternateVolumeTag
 | 
					    // AFAIK, tape changer do not use AlternateVolumeTag
 | 
				
			||||||
    // but parse anyways, just to be sure
 | 
					    // but parse anyways, just to be sure
 | 
				
			||||||
    fn skip_alternate_volume_tag<R: Read>(
 | 
					    fn skip_alternate_volume_tag<R: Read>(&self, reader: &mut R) -> Result<Option<String>, Error> {
 | 
				
			||||||
        &self,
 | 
					        if (self.flags & 64) != 0 {
 | 
				
			||||||
        reader: &mut R,
 | 
					            // has AVolTag
 | 
				
			||||||
    ) -> Result<Option<String>, Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (self.flags & 64) != 0 { // has AVolTag
 | 
					 | 
				
			||||||
            let _tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?;
 | 
					            let _tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -547,13 +556,14 @@ impl SubHeader {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[repr(C, packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
#[derive(Endian)]
 | 
					#[derive(Endian)]
 | 
				
			||||||
struct TransportDescriptor { // Robot/Griper
 | 
					struct TransportDescriptor {
 | 
				
			||||||
 | 
					    // Robot/Griper
 | 
				
			||||||
    element_address: u16,
 | 
					    element_address: u16,
 | 
				
			||||||
    flags1: u8,
 | 
					    flags1: u8,
 | 
				
			||||||
    reserved_3: u8,
 | 
					    reserved_3: u8,
 | 
				
			||||||
    additional_sense_code: u8,
 | 
					    additional_sense_code: u8,
 | 
				
			||||||
    additional_sense_code_qualifier: u8,
 | 
					    additional_sense_code_qualifier: u8,
 | 
				
			||||||
    reserved_6: [u8;3],
 | 
					    reserved_6: [u8; 3],
 | 
				
			||||||
    flags2: u8,
 | 
					    flags2: u8,
 | 
				
			||||||
    source_storage_element_address: u16,
 | 
					    source_storage_element_address: u16,
 | 
				
			||||||
    // volume tag and Mixed media descriptor follows (depends on flags)
 | 
					    // volume tag and Mixed media descriptor follows (depends on flags)
 | 
				
			||||||
@ -561,7 +571,8 @@ struct TransportDescriptor { // Robot/Griper
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[repr(C, packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
#[derive(Endian)]
 | 
					#[derive(Endian)]
 | 
				
			||||||
struct TransferDescriptor { // Tape drive
 | 
					struct TransferDescriptor {
 | 
				
			||||||
 | 
					    // Tape drive
 | 
				
			||||||
    element_address: u16,
 | 
					    element_address: u16,
 | 
				
			||||||
    flags1: u8,
 | 
					    flags1: u8,
 | 
				
			||||||
    reserved_3: u8,
 | 
					    reserved_3: u8,
 | 
				
			||||||
@ -578,7 +589,8 @@ struct TransferDescriptor { // Tape drive
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[repr(C, packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
#[derive(Endian)]
 | 
					#[derive(Endian)]
 | 
				
			||||||
struct DvcidHead { // Drive Identifier Header
 | 
					struct DvcidHead {
 | 
				
			||||||
 | 
					    // Drive Identifier Header
 | 
				
			||||||
    code_set: u8,
 | 
					    code_set: u8,
 | 
				
			||||||
    identifier_type: u8,
 | 
					    identifier_type: u8,
 | 
				
			||||||
    reserved: u8,
 | 
					    reserved: u8,
 | 
				
			||||||
@ -588,13 +600,14 @@ struct DvcidHead { // Drive Identifier Header
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[repr(C, packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
#[derive(Endian)]
 | 
					#[derive(Endian)]
 | 
				
			||||||
struct StorageDescriptor { // Mail Slot
 | 
					struct StorageDescriptor {
 | 
				
			||||||
 | 
					    // Mail Slot
 | 
				
			||||||
    element_address: u16,
 | 
					    element_address: u16,
 | 
				
			||||||
    flags1: u8,
 | 
					    flags1: u8,
 | 
				
			||||||
    reserved_3: u8,
 | 
					    reserved_3: u8,
 | 
				
			||||||
    additional_sense_code: u8,
 | 
					    additional_sense_code: u8,
 | 
				
			||||||
    additional_sense_code_qualifier: u8,
 | 
					    additional_sense_code_qualifier: u8,
 | 
				
			||||||
    reserved_6: [u8;3],
 | 
					    reserved_6: [u8; 3],
 | 
				
			||||||
    flags2: u8,
 | 
					    flags2: u8,
 | 
				
			||||||
    source_storage_element_address: u16,
 | 
					    source_storage_element_address: u16,
 | 
				
			||||||
    // volume tag and Mixed media descriptor follows (depends on flags)
 | 
					    // volume tag and Mixed media descriptor follows (depends on flags)
 | 
				
			||||||
@ -630,7 +643,8 @@ fn decode_dvcid_info<R: Read>(reader: &mut R) -> Result<DvcidInfo, Error> {
 | 
				
			|||||||
    let dvcid: DvcidHead = unsafe { reader.read_be_value()? };
 | 
					    let dvcid: DvcidHead = unsafe { reader.read_be_value()? };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let (serial, vendor, model) = match (dvcid.code_set, dvcid.identifier_type) {
 | 
					    let (serial, vendor, model) = match (dvcid.code_set, dvcid.identifier_type) {
 | 
				
			||||||
        (2, 0) => { // Serial number only (Quantum Superloader3 uses this)
 | 
					        (2, 0) => {
 | 
				
			||||||
 | 
					            // Serial number only (Quantum Superloader3 uses this)
 | 
				
			||||||
            let serial = reader.read_exact_allocated(dvcid.identifier_len as usize)?;
 | 
					            let serial = reader.read_exact_allocated(dvcid.identifier_len as usize)?;
 | 
				
			||||||
            let serial = scsi_ascii_to_string(&serial);
 | 
					            let serial = scsi_ascii_to_string(&serial);
 | 
				
			||||||
            (Some(serial), None, None)
 | 
					            (Some(serial), None, None)
 | 
				
			||||||
@ -661,9 +675,7 @@ fn decode_element_status_page(
 | 
				
			|||||||
    data: &[u8],
 | 
					    data: &[u8],
 | 
				
			||||||
    start_element_address: u16,
 | 
					    start_element_address: u16,
 | 
				
			||||||
) -> Result<DecodedStatusPage, Error> {
 | 
					) -> Result<DecodedStatusPage, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    proxmox_lang::try_block!({
 | 
					    proxmox_lang::try_block!({
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut result = DecodedStatusPage {
 | 
					        let mut result = DecodedStatusPage {
 | 
				
			||||||
            last_element_address: None,
 | 
					            last_element_address: None,
 | 
				
			||||||
            transports: Vec::new(),
 | 
					            transports: Vec::new(),
 | 
				
			||||||
@ -690,7 +702,11 @@ fn decode_element_status_page(
 | 
				
			|||||||
        if len < reader.len() {
 | 
					        if len < reader.len() {
 | 
				
			||||||
            reader = &reader[..len];
 | 
					            reader = &reader[..len];
 | 
				
			||||||
        } else if len > reader.len() {
 | 
					        } else if len > reader.len() {
 | 
				
			||||||
            bail!("wrong amount of data: expected {}, got {}", len, reader.len());
 | 
					            bail!(
 | 
				
			||||||
 | 
					                "wrong amount of data: expected {}, got {}",
 | 
				
			||||||
 | 
					                len,
 | 
				
			||||||
 | 
					                reader.len()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        loop {
 | 
					        loop {
 | 
				
			||||||
@ -763,7 +779,8 @@ fn decode_element_status_page(
 | 
				
			|||||||
                    4 => {
 | 
					                    4 => {
 | 
				
			||||||
                        let desc: TransferDescriptor = unsafe { reader.read_be_value()? };
 | 
					                        let desc: TransferDescriptor = unsafe { reader.read_be_value()? };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        let loaded_slot = if (desc.flags2 & 128) != 0 { // SValid
 | 
					                        let loaded_slot = if (desc.flags2 & 128) != 0 {
 | 
				
			||||||
 | 
					                            // SValid
 | 
				
			||||||
                            Some(desc.source_storage_element_address as u64)
 | 
					                            Some(desc.source_storage_element_address as u64)
 | 
				
			||||||
                        } else {
 | 
					                        } else {
 | 
				
			||||||
                            None
 | 
					                            None
 | 
				
			||||||
@ -798,23 +815,21 @@ fn decode_element_status_page(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(result)
 | 
					        Ok(result)
 | 
				
			||||||
    }).map_err(|err: Error| format_err!("decode element status failed - {}", err))
 | 
					    })
 | 
				
			||||||
 | 
					    .map_err(|err: Error| format_err!("decode element status failed - {}", err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Open the device for read/write, returns the file handle
 | 
					/// Open the device for read/write, returns the file handle
 | 
				
			||||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<File, Error> {
 | 
					pub fn open<P: AsRef<Path>>(path: P) -> Result<File, Error> {
 | 
				
			||||||
    let file = OpenOptions::new()
 | 
					    let file = OpenOptions::new().read(true).write(true).open(path)?;
 | 
				
			||||||
        .read(true)
 | 
					 | 
				
			||||||
        .write(true)
 | 
					 | 
				
			||||||
        .open(path)?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(file)
 | 
					    Ok(file)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod test {
 | 
					mod test {
 | 
				
			||||||
    use anyhow::Error;
 | 
					 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
 | 
					    use anyhow::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    struct StorageDesc {
 | 
					    struct StorageDesc {
 | 
				
			||||||
        address: u16,
 | 
					        address: u16,
 | 
				
			||||||
@ -826,9 +841,10 @@ mod test {
 | 
				
			|||||||
        trailing: &[u8],
 | 
					        trailing: &[u8],
 | 
				
			||||||
        element_type: u8,
 | 
					        element_type: u8,
 | 
				
			||||||
    ) -> Vec<u8> {
 | 
					    ) -> Vec<u8> {
 | 
				
			||||||
        let descs: Vec<Vec<u8>> = descriptors.iter().map(|desc| {
 | 
					        let descs: Vec<Vec<u8>> = descriptors
 | 
				
			||||||
            build_storage_descriptor(desc, trailing)
 | 
					            .iter()
 | 
				
			||||||
        }).collect();
 | 
					            .map(|desc| build_storage_descriptor(desc, trailing))
 | 
				
			||||||
 | 
					            .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let (desc_len, address) = if let Some(el) = descs.get(0) {
 | 
					        let (desc_len, address) = if let Some(el) = descs.get(0) {
 | 
				
			||||||
            (el.len() as u16, descriptors[0].address)
 | 
					            (el.len() as u16, descriptors[0].address)
 | 
				
			||||||
@ -863,10 +879,7 @@ mod test {
 | 
				
			|||||||
        res
 | 
					        res
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn build_storage_descriptor(
 | 
					    fn build_storage_descriptor(desc: &StorageDesc, trailing: &[u8]) -> Vec<u8> {
 | 
				
			||||||
        desc: &StorageDesc,
 | 
					 | 
				
			||||||
        trailing: &[u8],
 | 
					 | 
				
			||||||
    ) -> Vec<u8> {
 | 
					 | 
				
			||||||
        let mut res = Vec::new();
 | 
					        let mut res = Vec::new();
 | 
				
			||||||
        res.push(((desc.address >> 8) & 0xFF) as u8);
 | 
					        res.push(((desc.address >> 8) & 0xFF) as u8);
 | 
				
			||||||
        res.push((desc.address & 0xFF) as u8);
 | 
					        res.push((desc.address & 0xFF) as u8);
 | 
				
			||||||
@ -876,7 +889,7 @@ mod test {
 | 
				
			|||||||
            res.push(0x00); // full
 | 
					            res.push(0x00); // full
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res.extend_from_slice(&[0,0,0,0,0,0,0x80]);
 | 
					        res.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0x80]);
 | 
				
			||||||
        res.push(((desc.address >> 8) & 0xFF) as u8);
 | 
					        res.push(((desc.address >> 8) & 0xFF) as u8);
 | 
				
			||||||
        res.push((desc.address & 0xFF) as u8);
 | 
					        res.push((desc.address & 0xFF) as u8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -942,7 +955,7 @@ mod test {
 | 
				
			|||||||
                pvoltag: Some("1234567890".to_string()),
 | 
					                pvoltag: Some("1234567890".to_string()),
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
        let test_data = build_element_status_page(descs, &[0,0,0,0,0], 0x2);
 | 
					        let test_data = build_element_status_page(descs, &[0, 0, 0, 0, 0], 0x2);
 | 
				
			||||||
        let page = decode_element_status_page(&test_data, 0)?;
 | 
					        let page = decode_element_status_page(&test_data, 0)?;
 | 
				
			||||||
        assert_eq!(page.storage_slots.len(), 2);
 | 
					        assert_eq!(page.storage_slots.len(), 2);
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
use std::time::SystemTime;
 | 
					use std::convert::TryFrom;
 | 
				
			||||||
 | 
					use std::convert::TryInto;
 | 
				
			||||||
use std::fs::{File, OpenOptions};
 | 
					use std::fs::{File, OpenOptions};
 | 
				
			||||||
use std::os::unix::fs::OpenOptionsExt;
 | 
					use std::os::unix::fs::OpenOptionsExt;
 | 
				
			||||||
use std::os::unix::io::AsRawFd;
 | 
					use std::os::unix::io::AsRawFd;
 | 
				
			||||||
use std::path::Path;
 | 
					use std::path::Path;
 | 
				
			||||||
use std::convert::TryFrom;
 | 
					use std::time::SystemTime;
 | 
				
			||||||
use std::convert::TryInto;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::{bail, format_err, Error};
 | 
					use anyhow::{bail, format_err, Error};
 | 
				
			||||||
use endian_trait::Endian;
 | 
					use endian_trait::Endian;
 | 
				
			||||||
@ -25,40 +25,28 @@ pub use mam::*;
 | 
				
			|||||||
mod report_density;
 | 
					mod report_density;
 | 
				
			||||||
pub use report_density::*;
 | 
					pub use report_density::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use proxmox_sys::error::SysResult;
 | 
					 | 
				
			||||||
use proxmox_io::{ReadExt, WriteExt};
 | 
					use proxmox_io::{ReadExt, WriteExt};
 | 
				
			||||||
 | 
					use proxmox_sys::error::SysResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use pbs_api_types::{MamAttribute, Lp17VolumeStatistics, LtoDriveAndMediaStatus};
 | 
					use pbs_api_types::{Lp17VolumeStatistics, LtoDriveAndMediaStatus, MamAttribute};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    BlockRead,
 | 
					 | 
				
			||||||
    BlockReadError,
 | 
					 | 
				
			||||||
    BlockWrite,
 | 
					 | 
				
			||||||
    BlockedWriter,
 | 
					 | 
				
			||||||
    BlockedReader,
 | 
					 | 
				
			||||||
    sgutils2::{
 | 
					    sgutils2::{
 | 
				
			||||||
        SgRaw,
 | 
					        alloc_page_aligned_buffer, scsi_inquiry, scsi_mode_sense, scsi_request_sense, InquiryInfo,
 | 
				
			||||||
        SenseInfo,
 | 
					        ModeBlockDescriptor, ModeParameterHeader, ScsiError, SenseInfo, SgRaw,
 | 
				
			||||||
        ScsiError,
 | 
					 | 
				
			||||||
        InquiryInfo,
 | 
					 | 
				
			||||||
        ModeParameterHeader,
 | 
					 | 
				
			||||||
        ModeBlockDescriptor,
 | 
					 | 
				
			||||||
        alloc_page_aligned_buffer,
 | 
					 | 
				
			||||||
        scsi_inquiry,
 | 
					 | 
				
			||||||
        scsi_mode_sense,
 | 
					 | 
				
			||||||
        scsi_request_sense,
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    BlockRead, BlockReadError, BlockWrite, BlockedReader, BlockedWriter,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[repr(C, packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
#[derive(Endian, Debug, Copy, Clone)]
 | 
					#[derive(Endian, Debug, Copy, Clone)]
 | 
				
			||||||
pub struct ReadPositionLongPage {
 | 
					pub struct ReadPositionLongPage {
 | 
				
			||||||
    flags: u8,
 | 
					    flags: u8,
 | 
				
			||||||
    reserved: [u8;3],
 | 
					    reserved: [u8; 3],
 | 
				
			||||||
    partition_number: u32,
 | 
					    partition_number: u32,
 | 
				
			||||||
    pub logical_object_number: u64,
 | 
					    pub logical_object_number: u64,
 | 
				
			||||||
    pub logical_file_id: u64,
 | 
					    pub logical_file_id: u64,
 | 
				
			||||||
    obsolete: [u8;8],
 | 
					    obsolete: [u8; 8],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[repr(C, packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
@ -70,11 +58,10 @@ struct DataCompressionModePage {
 | 
				
			|||||||
    flags3: u8,
 | 
					    flags3: u8,
 | 
				
			||||||
    compression_algorithm: u32,
 | 
					    compression_algorithm: u32,
 | 
				
			||||||
    decompression_algorithm: u32,
 | 
					    decompression_algorithm: u32,
 | 
				
			||||||
    reserved: [u8;4],
 | 
					    reserved: [u8; 4],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl DataCompressionModePage {
 | 
					impl DataCompressionModePage {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn set_compression(&mut self, enable: bool) {
 | 
					    pub fn set_compression(&mut self, enable: bool) {
 | 
				
			||||||
        if enable {
 | 
					        if enable {
 | 
				
			||||||
            self.flags2 |= 128;
 | 
					            self.flags2 |= 128;
 | 
				
			||||||
@ -94,15 +81,13 @@ struct MediumConfigurationModePage {
 | 
				
			|||||||
    page_code: u8,   // 0x1d
 | 
					    page_code: u8,   // 0x1d
 | 
				
			||||||
    page_length: u8, // 0x1e
 | 
					    page_length: u8, // 0x1e
 | 
				
			||||||
    flags2: u8,
 | 
					    flags2: u8,
 | 
				
			||||||
    reserved: [u8;29],
 | 
					    reserved: [u8; 29],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MediumConfigurationModePage {
 | 
					impl MediumConfigurationModePage {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn is_worm(&self) -> bool {
 | 
					    pub fn is_worm(&self) -> bool {
 | 
				
			||||||
        (self.flags2 & 1) == 1
 | 
					        (self.flags2 & 1) == 1
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
@ -122,18 +107,19 @@ pub struct SgTape {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SgTape {
 | 
					impl SgTape {
 | 
				
			||||||
 | 
					    const SCSI_TAPE_DEFAULT_TIMEOUT: usize = 60 * 10; // 10 minutes
 | 
				
			||||||
    const SCSI_TAPE_DEFAULT_TIMEOUT: usize = 60*10; // 10 minutes
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Create a new instance
 | 
					    /// Create a new instance
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Uses scsi_inquiry to check the device type.
 | 
					    /// Uses scsi_inquiry to check the device type.
 | 
				
			||||||
    pub fn new(mut file: File) -> Result<Self, Error> {
 | 
					    pub fn new(mut file: File) -> Result<Self, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let info = scsi_inquiry(&mut file)?;
 | 
					        let info = scsi_inquiry(&mut file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if info.peripheral_type != 1 {
 | 
					        if info.peripheral_type != 1 {
 | 
				
			||||||
            bail!("not a tape device (peripheral_type = {})", info.peripheral_type);
 | 
					            bail!(
 | 
				
			||||||
 | 
					                "not a tape device (peripheral_type = {})",
 | 
				
			||||||
 | 
					                info.peripheral_type
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(Self {
 | 
					        Ok(Self {
 | 
				
			||||||
@ -169,14 +155,12 @@ impl SgTape {
 | 
				
			|||||||
            .open(path)?;
 | 
					            .open(path)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // then clear O_NONBLOCK
 | 
					        // then clear O_NONBLOCK
 | 
				
			||||||
        let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL)
 | 
					        let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL).into_io_result()?;
 | 
				
			||||||
            .into_io_result()?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut flags = OFlag::from_bits_truncate(flags);
 | 
					        let mut flags = OFlag::from_bits_truncate(flags);
 | 
				
			||||||
        flags.remove(OFlag::O_NONBLOCK);
 | 
					        flags.remove(OFlag::O_NONBLOCK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags))
 | 
					        fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)).into_io_result()?;
 | 
				
			||||||
            .into_io_result()?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self::new(file)
 | 
					        Self::new(file)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -203,7 +187,8 @@ impl SgTape {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        cmd.extend(&[0, 0, 0, 0]);
 | 
					        cmd.extend(&[0, 0, 0, 0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.do_command(&cmd)
 | 
					        sg_raw
 | 
				
			||||||
 | 
					            .do_command(&cmd)
 | 
				
			||||||
            .map_err(|err| format_err!("erase failed - {}", err))?;
 | 
					            .map_err(|err| format_err!("erase failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
@ -211,7 +196,6 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Format media, single partition
 | 
					    /// Format media, single partition
 | 
				
			||||||
    pub fn format_media(&mut self, fast: bool) -> Result<(), Error> {
 | 
					    pub fn format_media(&mut self, fast: bool) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // try to get info about loaded media first
 | 
					        // try to get info about loaded media first
 | 
				
			||||||
        let (has_format, is_worm) = match self.read_medium_configuration_page() {
 | 
					        let (has_format, is_worm) = match self.read_medium_configuration_page() {
 | 
				
			||||||
            Ok((_head, block_descriptor, page)) => {
 | 
					            Ok((_head, block_descriptor, page)) => {
 | 
				
			||||||
@ -236,7 +220,6 @@ impl SgTape {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok(())
 | 
					            Ok(())
 | 
				
			||||||
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            self.rewind()?;
 | 
					            self.rewind()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -261,7 +244,6 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Lock/Unlock drive door
 | 
					    /// Lock/Unlock drive door
 | 
				
			||||||
    pub fn set_medium_removal(&mut self, allow: bool) -> Result<(), ScsiError> {
 | 
					    pub fn set_medium_removal(&mut self, allow: bool) -> Result<(), ScsiError> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
 | 
					        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
 | 
				
			||||||
        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
					        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
				
			||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
@ -279,13 +261,13 @@ impl SgTape {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn rewind(&mut self) -> Result<(), Error> {
 | 
					    pub fn rewind(&mut self) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
 | 
					        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
 | 
				
			||||||
        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
					        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
				
			||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
        cmd.extend(&[0x01, 0, 0, 0, 0, 0]); // REWIND
 | 
					        cmd.extend(&[0x01, 0, 0, 0, 0, 0]); // REWIND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.do_command(&cmd)
 | 
					        sg_raw
 | 
				
			||||||
 | 
					            .do_command(&cmd)
 | 
				
			||||||
            .map_err(|err| format_err!("rewind failed - {}", err))?;
 | 
					            .map_err(|err| format_err!("rewind failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
@ -303,7 +285,8 @@ impl SgTape {
 | 
				
			|||||||
            self.rewind()?;
 | 
					            self.rewind()?;
 | 
				
			||||||
            let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
 | 
					            let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
 | 
				
			||||||
            sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
					            sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
				
			||||||
            sg_raw.do_command(SPACE_ONE_FILEMARK)
 | 
					            sg_raw
 | 
				
			||||||
 | 
					                .do_command(SPACE_ONE_FILEMARK)
 | 
				
			||||||
                .map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
 | 
					                .map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
 | 
				
			||||||
            return Ok(());
 | 
					            return Ok(());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -334,20 +317,22 @@ impl SgTape {
 | 
				
			|||||||
        cmd.extend(&fixed_position.to_be_bytes());
 | 
					        cmd.extend(&fixed_position.to_be_bytes());
 | 
				
			||||||
        cmd.extend(&[0, 0, 0, 0]);
 | 
					        cmd.extend(&[0, 0, 0, 0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.do_command(&cmd)
 | 
					        sg_raw
 | 
				
			||||||
 | 
					            .do_command(&cmd)
 | 
				
			||||||
            .map_err(|err| format_err!("locate file {} failed - {}", position, err))?;
 | 
					            .map_err(|err| format_err!("locate file {} failed - {}", position, err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // LOCATE always position at the BOT side of the filemark, so
 | 
					        // LOCATE always position at the BOT side of the filemark, so
 | 
				
			||||||
        // we need to move to other side of filemark
 | 
					        // we need to move to other side of filemark
 | 
				
			||||||
        sg_raw.do_command(SPACE_ONE_FILEMARK)
 | 
					        sg_raw
 | 
				
			||||||
 | 
					            .do_command(SPACE_ONE_FILEMARK)
 | 
				
			||||||
            .map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
 | 
					            .map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.locate_offset.is_none() {
 | 
					        if self.locate_offset.is_none() {
 | 
				
			||||||
            // check if we landed at correct position
 | 
					            // check if we landed at correct position
 | 
				
			||||||
            let current_file = self.current_file_number()?;
 | 
					            let current_file = self.current_file_number()?;
 | 
				
			||||||
            if current_file != position {
 | 
					            if current_file != position {
 | 
				
			||||||
                let offset: i64 =
 | 
					                let offset: i64 = i64::try_from((position as i128) - (current_file as i128))
 | 
				
			||||||
                    i64::try_from((position as i128) - (current_file as i128)).map_err(|err| {
 | 
					                    .map_err(|err| {
 | 
				
			||||||
                        format_err!(
 | 
					                        format_err!(
 | 
				
			||||||
                            "locate_file: offset between {} and {} invalid: {}",
 | 
					                            "locate_file: offset between {} and {} invalid: {}",
 | 
				
			||||||
                            position,
 | 
					                            position,
 | 
				
			||||||
@ -370,7 +355,6 @@ impl SgTape {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn position(&mut self) -> Result<ReadPositionLongPage, Error> {
 | 
					    pub fn position(&mut self) -> Result<ReadPositionLongPage, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let expected_size = std::mem::size_of::<ReadPositionLongPage>();
 | 
					        let expected_size = std::mem::size_of::<ReadPositionLongPage>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut sg_raw = SgRaw::new(&mut self.file, 32)?;
 | 
					        let mut sg_raw = SgRaw::new(&mut self.file, 32)?;
 | 
				
			||||||
@ -381,12 +365,17 @@ impl SgTape {
 | 
				
			|||||||
        // reference manual.
 | 
					        // reference manual.
 | 
				
			||||||
        cmd.extend(&[0x34, 0x06, 0, 0, 0, 0, 0, 0, 0, 0]); // READ POSITION LONG FORM
 | 
					        cmd.extend(&[0x34, 0x06, 0, 0, 0, 0, 0, 0, 0, 0]); // READ POSITION LONG FORM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let data = sg_raw.do_command(&cmd)
 | 
					        let data = sg_raw
 | 
				
			||||||
 | 
					            .do_command(&cmd)
 | 
				
			||||||
            .map_err(|err| format_err!("read position failed - {}", err))?;
 | 
					            .map_err(|err| format_err!("read position failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let page = proxmox_lang::try_block!({
 | 
					        let page = proxmox_lang::try_block!({
 | 
				
			||||||
            if data.len() != expected_size {
 | 
					            if data.len() != expected_size {
 | 
				
			||||||
                bail!("got unexpected data len ({} != {}", data.len(), expected_size);
 | 
					                bail!(
 | 
				
			||||||
 | 
					                    "got unexpected data len ({} != {}",
 | 
				
			||||||
 | 
					                    data.len(),
 | 
				
			||||||
 | 
					                    expected_size
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let mut reader = data;
 | 
					            let mut reader = data;
 | 
				
			||||||
@ -394,7 +383,8 @@ impl SgTape {
 | 
				
			|||||||
            let page: ReadPositionLongPage = unsafe { reader.read_be_value()? };
 | 
					            let page: ReadPositionLongPage = unsafe { reader.read_be_value()? };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok(page)
 | 
					            Ok(page)
 | 
				
			||||||
        }).map_err(|err: Error| format_err!("decode position page failed - {}", err))?;
 | 
					        })
 | 
				
			||||||
 | 
					        .map_err(|err: Error| format_err!("decode position page failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if page.partition_number != 0 {
 | 
					        if page.partition_number != 0 {
 | 
				
			||||||
            bail!("detecthed partitioned tape - not supported");
 | 
					            bail!("detecthed partitioned tape - not supported");
 | 
				
			||||||
@ -410,7 +400,6 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Check if we are positioned after a filemark (or BOT)
 | 
					    /// Check if we are positioned after a filemark (or BOT)
 | 
				
			||||||
    pub fn check_filemark(&mut self) -> Result<bool, Error> {
 | 
					    pub fn check_filemark(&mut self) -> Result<bool, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let pos = self.position()?;
 | 
					        let pos = self.position()?;
 | 
				
			||||||
        if pos.logical_object_number == 0 {
 | 
					        if pos.logical_object_number == 0 {
 | 
				
			||||||
            // at BOT, Ok (no filemark required)
 | 
					            // at BOT, Ok (no filemark required)
 | 
				
			||||||
@ -421,13 +410,24 @@ impl SgTape {
 | 
				
			|||||||
        match self.space(-1, true) {
 | 
					        match self.space(-1, true) {
 | 
				
			||||||
            Ok(_) => {
 | 
					            Ok(_) => {
 | 
				
			||||||
                self.space(1, true) // move back to end
 | 
					                self.space(1, true) // move back to end
 | 
				
			||||||
                    .map_err(|err| format_err!("check_filemark failed (space forward) - {}", err))?;
 | 
					                    .map_err(|err| {
 | 
				
			||||||
 | 
					                        format_err!("check_filemark failed (space forward) - {}", err)
 | 
				
			||||||
 | 
					                    })?;
 | 
				
			||||||
                Ok(false)
 | 
					                Ok(false)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 })) => {
 | 
					            Err(ScsiError::Sense(SenseInfo {
 | 
				
			||||||
 | 
					                sense_key: 0,
 | 
				
			||||||
 | 
					                asc: 0,
 | 
				
			||||||
 | 
					                ascq: 1,
 | 
				
			||||||
 | 
					            })) => {
 | 
				
			||||||
                // Filemark detected - good
 | 
					                // Filemark detected - good
 | 
				
			||||||
                self.space(1, false) // move to EOT side of filemark
 | 
					                self.space(1, false) // move to EOT side of filemark
 | 
				
			||||||
                    .map_err(|err| format_err!("check_filemark failed (move to EOT side of filemark) - {}", err))?;
 | 
					                    .map_err(|err| {
 | 
				
			||||||
 | 
					                        format_err!(
 | 
				
			||||||
 | 
					                            "check_filemark failed (move to EOT side of filemark) - {}",
 | 
				
			||||||
 | 
					                            err
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })?;
 | 
				
			||||||
                Ok(true)
 | 
					                Ok(true)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(err) => {
 | 
					            Err(err) => {
 | 
				
			||||||
@ -442,7 +442,8 @@ impl SgTape {
 | 
				
			|||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
        cmd.extend(&[0x11, 0x03, 0, 0, 0, 0]); // SPACE(6) move to EOD
 | 
					        cmd.extend(&[0x11, 0x03, 0, 0, 0, 0]); // SPACE(6) move to EOD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.do_command(&cmd)
 | 
					        sg_raw
 | 
				
			||||||
 | 
					            .do_command(&cmd)
 | 
				
			||||||
            .map_err(|err| format_err!("move to EOD failed - {}", err))?;
 | 
					            .map_err(|err| format_err!("move to EOD failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if write_missing_eof && !self.check_filemark()? {
 | 
					        if write_missing_eof && !self.check_filemark()? {
 | 
				
			||||||
@ -503,7 +504,8 @@ impl SgTape {
 | 
				
			|||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
        cmd.extend(&[0x1B, 0, 0, 0, 0, 0]); // LODA/UNLOAD HOLD=0, LOAD=0
 | 
					        cmd.extend(&[0x1B, 0, 0, 0, 0, 0]); // LODA/UNLOAD HOLD=0, LOAD=0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.do_command(&cmd)
 | 
					        sg_raw
 | 
				
			||||||
 | 
					            .do_command(&cmd)
 | 
				
			||||||
            .map_err(|err| format_err!("eject failed - {}", err))?;
 | 
					            .map_err(|err| format_err!("eject failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
@ -515,24 +517,21 @@ impl SgTape {
 | 
				
			|||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
        cmd.extend(&[0x1B, 0, 0, 0, 0b0000_0001, 0]); // LODA/UNLOAD HOLD=0, LOAD=1
 | 
					        cmd.extend(&[0x1B, 0, 0, 0, 0b0000_0001, 0]); // LODA/UNLOAD HOLD=0, LOAD=1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.do_command(&cmd)
 | 
					        sg_raw
 | 
				
			||||||
 | 
					            .do_command(&cmd)
 | 
				
			||||||
            .map_err(|err| format_err!("load media failed - {}", err))?;
 | 
					            .map_err(|err| format_err!("load media failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn write_filemarks(
 | 
					    pub fn write_filemarks(&mut self, count: usize, immediate: bool) -> Result<(), std::io::Error> {
 | 
				
			||||||
        &mut self,
 | 
					 | 
				
			||||||
        count: usize,
 | 
					 | 
				
			||||||
        immediate: bool,
 | 
					 | 
				
			||||||
    ) ->  Result<(), std::io::Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if count > 255 {
 | 
					        if count > 255 {
 | 
				
			||||||
            proxmox_lang::io_bail!("write_filemarks failed: got strange count '{}'", count);
 | 
					            proxmox_lang::io_bail!("write_filemarks failed: got strange count '{}'", count);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut sg_raw = SgRaw::new(&mut self.file, 16)
 | 
					        let mut sg_raw = SgRaw::new(&mut self.file, 16).map_err(|err| {
 | 
				
			||||||
            .map_err(|err| proxmox_lang::io_format_err!("write_filemarks failed (alloc) - {}", err))?;
 | 
					            proxmox_lang::io_format_err!("write_filemarks failed (alloc) - {}", err)
 | 
				
			||||||
 | 
					        })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
					        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
				
			||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
@ -547,9 +546,11 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        match sg_raw.do_command(&cmd) {
 | 
					        match sg_raw.do_command(&cmd) {
 | 
				
			||||||
            Ok(_) => { /* OK */ }
 | 
					            Ok(_) => { /* OK */ }
 | 
				
			||||||
            Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => {
 | 
					            Err(ScsiError::Sense(SenseInfo {
 | 
				
			||||||
                /* LEOM - ignore */
 | 
					                sense_key: 0,
 | 
				
			||||||
            }
 | 
					                asc: 0,
 | 
				
			||||||
 | 
					                ascq: 2,
 | 
				
			||||||
 | 
					            })) => { /* LEOM - ignore */ }
 | 
				
			||||||
            Err(err) => {
 | 
					            Err(err) => {
 | 
				
			||||||
                proxmox_lang::io_bail!("write filemark  failed - {}", err);
 | 
					                proxmox_lang::io_bail!("write filemark  failed - {}", err);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -565,7 +566,6 @@ impl SgTape {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn test_unit_ready(&mut self) -> Result<(), Error> {
 | 
					    pub fn test_unit_ready(&mut self) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
 | 
					        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
 | 
				
			||||||
        sg_raw.set_timeout(30); // use short timeout
 | 
					        sg_raw.set_timeout(30); // use short timeout
 | 
				
			||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
@ -580,7 +580,6 @@ impl SgTape {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn wait_until_ready(&mut self) -> Result<(), Error> {
 | 
					    pub fn wait_until_ready(&mut self) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let start = SystemTime::now();
 | 
					        let start = SystemTime::now();
 | 
				
			||||||
        let max_wait = std::time::Duration::new(Self::SCSI_TAPE_DEFAULT_TIMEOUT as u64, 0);
 | 
					        let max_wait = std::time::Duration::new(Self::SCSI_TAPE_DEFAULT_TIMEOUT as u64, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -612,11 +611,7 @@ impl SgTape {
 | 
				
			|||||||
        read_volume_statistics(&mut self.file)
 | 
					        read_volume_statistics(&mut self.file)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn set_encryption(
 | 
					    pub fn set_encryption(&mut self, key: Option<[u8; 32]>) -> Result<(), Error> {
 | 
				
			||||||
        &mut self,
 | 
					 | 
				
			||||||
        key: Option<[u8; 32]>,
 | 
					 | 
				
			||||||
    ) -> Result<(), Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.encryption_key_loaded = key.is_some();
 | 
					        self.encryption_key_loaded = key.is_some();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        set_encryption(&mut self.file, key)
 | 
					        set_encryption(&mut self.file, key)
 | 
				
			||||||
@ -626,15 +621,13 @@ impl SgTape {
 | 
				
			|||||||
    //
 | 
					    //
 | 
				
			||||||
    // Returns true if the drive reached the Logical End Of Media (early warning)
 | 
					    // Returns true if the drive reached the Logical End Of Media (early warning)
 | 
				
			||||||
    fn write_block(&mut self, data: &[u8]) -> Result<bool, std::io::Error> {
 | 
					    fn write_block(&mut self, data: &[u8]) -> Result<bool, std::io::Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let transfer_len = data.len();
 | 
					        let transfer_len = data.len();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if transfer_len > 0x800000 {
 | 
					        if transfer_len > 0x800000 {
 | 
				
			||||||
            proxmox_lang::io_bail!("write failed - data too large");
 | 
					            proxmox_lang::io_bail!("write failed - data too large");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut sg_raw = SgRaw::new(&mut self.file, 0)
 | 
					        let mut sg_raw = SgRaw::new(&mut self.file, 0).unwrap(); // cannot fail with size 0
 | 
				
			||||||
            .unwrap(); // cannot fail with size 0
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
					        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
				
			||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
@ -649,8 +642,12 @@ impl SgTape {
 | 
				
			|||||||
        //println!("WRITE {:?}", data);
 | 
					        //println!("WRITE {:?}", data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match sg_raw.do_out_command(&cmd, data) {
 | 
					        match sg_raw.do_out_command(&cmd, data) {
 | 
				
			||||||
            Ok(()) => { Ok(false) }
 | 
					            Ok(()) => Ok(false),
 | 
				
			||||||
            Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => {
 | 
					            Err(ScsiError::Sense(SenseInfo {
 | 
				
			||||||
 | 
					                sense_key: 0,
 | 
				
			||||||
 | 
					                asc: 0,
 | 
				
			||||||
 | 
					                ascq: 2,
 | 
				
			||||||
 | 
					            })) => {
 | 
				
			||||||
                Ok(true) // LEOM
 | 
					                Ok(true) // LEOM
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(err) => {
 | 
					            Err(err) => {
 | 
				
			||||||
@ -663,13 +660,12 @@ impl SgTape {
 | 
				
			|||||||
        let transfer_len = buffer.len();
 | 
					        let transfer_len = buffer.len();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if transfer_len > 0xFFFFFF {
 | 
					        if transfer_len > 0xFFFFFF {
 | 
				
			||||||
            return Err(BlockReadError::Error(
 | 
					            return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
 | 
				
			||||||
                proxmox_lang::io_format_err!("read failed - buffer too large")
 | 
					                "read failed - buffer too large"
 | 
				
			||||||
            ));
 | 
					            )));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut sg_raw = SgRaw::new(&mut self.file, 0)
 | 
					        let mut sg_raw = SgRaw::new(&mut self.file, 0).unwrap(); // cannot fail with size 0
 | 
				
			||||||
            .unwrap(); // cannot fail with size 0
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
					        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
 | 
				
			||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
@ -683,23 +679,34 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let data = match sg_raw.do_in_command(&cmd, buffer) {
 | 
					        let data = match sg_raw.do_in_command(&cmd, buffer) {
 | 
				
			||||||
            Ok(data) => data,
 | 
					            Ok(data) => data,
 | 
				
			||||||
            Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 })) => {
 | 
					            Err(ScsiError::Sense(SenseInfo {
 | 
				
			||||||
 | 
					                sense_key: 0,
 | 
				
			||||||
 | 
					                asc: 0,
 | 
				
			||||||
 | 
					                ascq: 1,
 | 
				
			||||||
 | 
					            })) => {
 | 
				
			||||||
                return Err(BlockReadError::EndOfFile);
 | 
					                return Err(BlockReadError::EndOfFile);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(ScsiError::Sense(SenseInfo { sense_key: 8, asc: 0, ascq: 5 })) => {
 | 
					            Err(ScsiError::Sense(SenseInfo {
 | 
				
			||||||
 | 
					                sense_key: 8,
 | 
				
			||||||
 | 
					                asc: 0,
 | 
				
			||||||
 | 
					                ascq: 5,
 | 
				
			||||||
 | 
					            })) => {
 | 
				
			||||||
                return Err(BlockReadError::EndOfStream);
 | 
					                return Err(BlockReadError::EndOfStream);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(err) => {
 | 
					            Err(err) => {
 | 
				
			||||||
                return Err(BlockReadError::Error(
 | 
					                return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
 | 
				
			||||||
                    proxmox_lang::io_format_err!("read failed - {}", err)
 | 
					                    "read failed - {}",
 | 
				
			||||||
                ));
 | 
					                    err
 | 
				
			||||||
 | 
					                )));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if data.len() != transfer_len {
 | 
					        if data.len() != transfer_len {
 | 
				
			||||||
            return Err(BlockReadError::Error(
 | 
					            return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
 | 
				
			||||||
                proxmox_lang::io_format_err!("read failed - unexpected block len ({} != {})", data.len(), buffer.len())
 | 
					                "read failed - unexpected block len ({} != {})",
 | 
				
			||||||
            ));
 | 
					                data.len(),
 | 
				
			||||||
 | 
					                buffer.len()
 | 
				
			||||||
 | 
					            )));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(transfer_len)
 | 
					        Ok(transfer_len)
 | 
				
			||||||
@ -717,7 +724,6 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Set all options we need/want
 | 
					    /// Set all options we need/want
 | 
				
			||||||
    pub fn set_default_options(&mut self) -> Result<(), Error> {
 | 
					    pub fn set_default_options(&mut self) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let compression = Some(true);
 | 
					        let compression = Some(true);
 | 
				
			||||||
        let block_length = Some(0); // variable length mode
 | 
					        let block_length = Some(0); // variable length mode
 | 
				
			||||||
        let buffer_mode = Some(true); // Always use drive buffer
 | 
					        let buffer_mode = Some(true); // Always use drive buffer
 | 
				
			||||||
@ -734,7 +740,6 @@ impl SgTape {
 | 
				
			|||||||
        block_length: Option<u32>,
 | 
					        block_length: Option<u32>,
 | 
				
			||||||
        buffer_mode: Option<bool>,
 | 
					        buffer_mode: Option<bool>,
 | 
				
			||||||
    ) -> Result<(), Error> {
 | 
					    ) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Note: Read/Modify/Write
 | 
					        // Note: Read/Modify/Write
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let (mut head, mut block_descriptor, mut page) = self.read_compression_page()?;
 | 
					        let (mut head, mut block_descriptor, mut page) = self.read_compression_page()?;
 | 
				
			||||||
@ -766,7 +771,7 @@ impl SgTape {
 | 
				
			|||||||
        let mut cmd = Vec::new();
 | 
					        let mut cmd = Vec::new();
 | 
				
			||||||
        cmd.push(0x55); // MODE SELECT(10)
 | 
					        cmd.push(0x55); // MODE SELECT(10)
 | 
				
			||||||
        cmd.push(0b0001_0000); // PF=1
 | 
					        cmd.push(0b0001_0000); // PF=1
 | 
				
			||||||
        cmd.extend(&[0,0,0,0,0]); //reserved
 | 
					        cmd.extend(&[0, 0, 0, 0, 0]); //reserved
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let param_list_len: u16 = data.len() as u16;
 | 
					        let param_list_len: u16 = data.len() as u16;
 | 
				
			||||||
        cmd.extend(¶m_list_len.to_be_bytes());
 | 
					        cmd.extend(¶m_list_len.to_be_bytes());
 | 
				
			||||||
@ -776,7 +781,8 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        buffer[..data.len()].copy_from_slice(&data[..]);
 | 
					        buffer[..data.len()].copy_from_slice(&data[..]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sg_raw.do_out_command(&cmd, &buffer[..data.len()])
 | 
					        sg_raw
 | 
				
			||||||
 | 
					            .do_out_command(&cmd, &buffer[..data.len()])
 | 
				
			||||||
            .map_err(|err| format_err!("set drive options failed - {}", err))?;
 | 
					            .map_err(|err| format_err!("set drive options failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
@ -784,10 +790,16 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    fn read_medium_configuration_page(
 | 
					    fn read_medium_configuration_page(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
    ) -> Result<(ModeParameterHeader, ModeBlockDescriptor, MediumConfigurationModePage), Error> {
 | 
					    ) -> Result<
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
        let (head, block_descriptor, page): (_,_, MediumConfigurationModePage)
 | 
					            ModeParameterHeader,
 | 
				
			||||||
            = scsi_mode_sense(&mut self.file, false, 0x1d, 0)?;
 | 
					            ModeBlockDescriptor,
 | 
				
			||||||
 | 
					            MediumConfigurationModePage,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        Error,
 | 
				
			||||||
 | 
					    > {
 | 
				
			||||||
 | 
					        let (head, block_descriptor, page): (_, _, MediumConfigurationModePage) =
 | 
				
			||||||
 | 
					            scsi_mode_sense(&mut self.file, false, 0x1d, 0)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        proxmox_lang::try_block!({
 | 
					        proxmox_lang::try_block!({
 | 
				
			||||||
            if (page.page_code & 0b0011_1111) != 0x1d {
 | 
					            if (page.page_code & 0b0011_1111) != 0x1d {
 | 
				
			||||||
@ -803,15 +815,22 @@ impl SgTape {
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok((head, block_descriptor, page))
 | 
					            Ok((head, block_descriptor, page))
 | 
				
			||||||
        }).map_err(|err| format_err!("read_medium_configuration failed - {}", err))
 | 
					        })
 | 
				
			||||||
 | 
					        .map_err(|err| format_err!("read_medium_configuration failed - {}", err))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn read_compression_page(
 | 
					    fn read_compression_page(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
    ) -> Result<(ModeParameterHeader, ModeBlockDescriptor, DataCompressionModePage), Error> {
 | 
					    ) -> Result<
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
        let (head, block_descriptor, page): (_,_, DataCompressionModePage)
 | 
					            ModeParameterHeader,
 | 
				
			||||||
            = scsi_mode_sense(&mut self.file, false, 0x0f, 0)?;
 | 
					            ModeBlockDescriptor,
 | 
				
			||||||
 | 
					            DataCompressionModePage,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        Error,
 | 
				
			||||||
 | 
					    > {
 | 
				
			||||||
 | 
					        let (head, block_descriptor, page): (_, _, DataCompressionModePage) =
 | 
				
			||||||
 | 
					            scsi_mode_sense(&mut self.file, false, 0x0f, 0)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        proxmox_lang::try_block!({
 | 
					        proxmox_lang::try_block!({
 | 
				
			||||||
            if (page.page_code & 0b0011_1111) != 0x0f {
 | 
					            if (page.page_code & 0b0011_1111) != 0x0f {
 | 
				
			||||||
@ -827,7 +846,8 @@ impl SgTape {
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok((head, block_descriptor, page))
 | 
					            Ok((head, block_descriptor, page))
 | 
				
			||||||
        }).map_err(|err| format_err!("read_compression_page failed: {}", err))
 | 
					        })
 | 
				
			||||||
 | 
					        .map_err(|err| format_err!("read_compression_page failed: {}", err))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Read drive options/status
 | 
					    /// Read drive options/status
 | 
				
			||||||
@ -835,7 +855,6 @@ impl SgTape {
 | 
				
			|||||||
    /// We read the drive compression page, including the
 | 
					    /// We read the drive compression page, including the
 | 
				
			||||||
    /// block_descriptor. This is all information we need for now.
 | 
					    /// block_descriptor. This is all information we need for now.
 | 
				
			||||||
    pub fn read_drive_status(&mut self) -> Result<LtoTapeStatus, Error> {
 | 
					    pub fn read_drive_status(&mut self) -> Result<LtoTapeStatus, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // We do a Request Sense, but ignore the result.
 | 
					        // We do a Request Sense, but ignore the result.
 | 
				
			||||||
        // This clears deferred error or media changed events.
 | 
					        // This clears deferred error or media changed events.
 | 
				
			||||||
        let _ = scsi_request_sense(&mut self.file);
 | 
					        let _ = scsi_request_sense(&mut self.file);
 | 
				
			||||||
@ -853,10 +872,10 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Get Tape and Media status
 | 
					    /// Get Tape and Media status
 | 
				
			||||||
    pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error> {
 | 
					    pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let drive_status = self.read_drive_status()?;
 | 
					        let drive_status = self.read_drive_status()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let alert_flags = self.tape_alert_flags()
 | 
					        let alert_flags = self
 | 
				
			||||||
 | 
					            .tape_alert_flags()
 | 
				
			||||||
            .map(|flags| format!("{:?}", flags))
 | 
					            .map(|flags| format!("{:?}", flags))
 | 
				
			||||||
            .ok();
 | 
					            .ok();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -881,7 +900,6 @@ impl SgTape {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.test_unit_ready().is_ok() {
 | 
					        if self.test_unit_ready().is_ok() {
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if drive_status.write_protect {
 | 
					            if drive_status.write_protect {
 | 
				
			||||||
                status.write_protect = Some(drive_status.write_protect);
 | 
					                status.write_protect = Some(drive_status.write_protect);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -892,7 +910,6 @@ impl SgTape {
 | 
				
			|||||||
            status.block_number = Some(position.logical_object_number);
 | 
					            status.block_number = Some(position.logical_object_number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if let Ok(mam) = self.cartridge_memory() {
 | 
					            if let Ok(mam) = self.cartridge_memory() {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                let usage = mam_extract_media_usage(&mam)?;
 | 
					                let usage = mam_extract_media_usage(&mam)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                status.manufactured = Some(usage.manufactured);
 | 
					                status.manufactured = Some(usage.manufactured);
 | 
				
			||||||
@ -900,7 +917,6 @@ impl SgTape {
 | 
				
			|||||||
                status.bytes_written = Some(usage.bytes_written);
 | 
					                status.bytes_written = Some(usage.bytes_written);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if let Ok(volume_stats) = self.volume_statistics() {
 | 
					                if let Ok(volume_stats) = self.volume_statistics() {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let passes = std::cmp::max(
 | 
					                    let passes = std::cmp::max(
 | 
				
			||||||
                        volume_stats.beginning_of_medium_passes,
 | 
					                        volume_stats.beginning_of_medium_passes,
 | 
				
			||||||
                        volume_stats.middle_of_tape_passes,
 | 
					                        volume_stats.middle_of_tape_passes,
 | 
				
			||||||
@ -908,7 +924,7 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    // assume max. 16000 medium passes
 | 
					                    // assume max. 16000 medium passes
 | 
				
			||||||
                    // see: https://en.wikipedia.org/wiki/Linear_Tape-Open
 | 
					                    // see: https://en.wikipedia.org/wiki/Linear_Tape-Open
 | 
				
			||||||
                    let wearout: f64 = (passes as f64)/16000.0_f64;
 | 
					                    let wearout: f64 = (passes as f64) / 16000.0_f64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    status.medium_passes = Some(passes);
 | 
					                    status.medium_passes = Some(passes);
 | 
				
			||||||
                    status.medium_wearout = Some(wearout);
 | 
					                    status.medium_wearout = Some(wearout);
 | 
				
			||||||
@ -920,7 +936,6 @@ impl SgTape {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        Ok(status)
 | 
					        Ok(status)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Drop for SgTape {
 | 
					impl Drop for SgTape {
 | 
				
			||||||
@ -932,31 +947,33 @@ impl Drop for SgTape {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct SgTapeReader<'a> {
 | 
					pub struct SgTapeReader<'a> {
 | 
				
			||||||
    sg_tape: &'a mut SgTape,
 | 
					    sg_tape: &'a mut SgTape,
 | 
				
			||||||
    end_of_file: bool,
 | 
					    end_of_file: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <'a> SgTapeReader<'a> {
 | 
					impl<'a> SgTapeReader<'a> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn new(sg_tape: &'a mut SgTape) -> Self {
 | 
					    pub fn new(sg_tape: &'a mut SgTape) -> Self {
 | 
				
			||||||
        Self { sg_tape, end_of_file: false, }
 | 
					        Self {
 | 
				
			||||||
 | 
					            sg_tape,
 | 
				
			||||||
 | 
					            end_of_file: false,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <'a> BlockRead for SgTapeReader<'a> {
 | 
					impl<'a> BlockRead for SgTapeReader<'a> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
 | 
					    fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
 | 
				
			||||||
        if self.end_of_file {
 | 
					        if self.end_of_file {
 | 
				
			||||||
            return Err(BlockReadError::Error(proxmox_lang::io_format_err!("detected read after EOF!")));
 | 
					            return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
 | 
				
			||||||
 | 
					                "detected read after EOF!"
 | 
				
			||||||
 | 
					            )));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        match self.sg_tape.read_block(buffer) {
 | 
					        match self.sg_tape.read_block(buffer) {
 | 
				
			||||||
            Ok(usize) => Ok(usize),
 | 
					            Ok(usize) => Ok(usize),
 | 
				
			||||||
            Err(BlockReadError::EndOfFile) => {
 | 
					            Err(BlockReadError::EndOfFile) => {
 | 
				
			||||||
                self.end_of_file = true;
 | 
					                self.end_of_file = true;
 | 
				
			||||||
                Err(BlockReadError::EndOfFile)
 | 
					                Err(BlockReadError::EndOfFile)
 | 
				
			||||||
            },
 | 
					            }
 | 
				
			||||||
            Err(err) => Err(err),
 | 
					            Err(err) => Err(err),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -967,15 +984,16 @@ pub struct SgTapeWriter<'a> {
 | 
				
			|||||||
    _leom_sent: bool,
 | 
					    _leom_sent: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <'a> SgTapeWriter<'a> {
 | 
					impl<'a> SgTapeWriter<'a> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn new(sg_tape: &'a mut SgTape) -> Self {
 | 
					    pub fn new(sg_tape: &'a mut SgTape) -> Self {
 | 
				
			||||||
        Self { sg_tape, _leom_sent: false }
 | 
					        Self {
 | 
				
			||||||
 | 
					            sg_tape,
 | 
				
			||||||
 | 
					            _leom_sent: false,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <'a> BlockWrite for SgTapeWriter<'a> {
 | 
					impl<'a> BlockWrite for SgTapeWriter<'a> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn write_block(&mut self, buffer: &[u8]) -> Result<bool, std::io::Error> {
 | 
					    fn write_block(&mut self, buffer: &[u8]) -> Result<bool, std::io::Error> {
 | 
				
			||||||
        self.sg_tape.write_block(buffer)
 | 
					        self.sg_tape.write_block(buffer)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,20 +1,17 @@
 | 
				
			|||||||
use std::os::unix::prelude::AsRawFd;
 | 
					 | 
				
			||||||
use std::io::Write;
 | 
					use std::io::Write;
 | 
				
			||||||
 | 
					use std::os::unix::prelude::AsRawFd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::{bail, format_err, Error};
 | 
					use anyhow::{bail, format_err, Error};
 | 
				
			||||||
use endian_trait::Endian;
 | 
					use endian_trait::Endian;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use proxmox_io::{ReadExt, WriteExt};
 | 
					use proxmox_io::{ReadExt, WriteExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::sgutils2::{SgRaw, alloc_page_aligned_buffer};
 | 
					use crate::sgutils2::{alloc_page_aligned_buffer, SgRaw};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Test if drive supports hardware encryption
 | 
					/// Test if drive supports hardware encryption
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// We search for AES_GCM algorithm with 256bits key.
 | 
					/// We search for AES_GCM algorithm with 256bits key.
 | 
				
			||||||
pub fn has_encryption<F: AsRawFd>(
 | 
					pub fn has_encryption<F: AsRawFd>(file: &mut F) -> bool {
 | 
				
			||||||
    file: &mut F,
 | 
					 | 
				
			||||||
) -> bool {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let data = match sg_spin_data_encryption_caps(file) {
 | 
					    let data = match sg_spin_data_encryption_caps(file) {
 | 
				
			||||||
        Ok(data) => data,
 | 
					        Ok(data) => data,
 | 
				
			||||||
        Err(_) => return false,
 | 
					        Err(_) => return false,
 | 
				
			||||||
@ -25,11 +22,7 @@ pub fn has_encryption<F: AsRawFd>(
 | 
				
			|||||||
/// Set or clear encryption key
 | 
					/// Set or clear encryption key
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// We always use mixed mode,
 | 
					/// We always use mixed mode,
 | 
				
			||||||
pub fn set_encryption<F: AsRawFd>(
 | 
					pub fn set_encryption<F: AsRawFd>(file: &mut F, key: Option<[u8; 32]>) -> Result<(), Error> {
 | 
				
			||||||
    file: &mut F,
 | 
					 | 
				
			||||||
    key: Option<[u8; 32]>,
 | 
					 | 
				
			||||||
) -> Result<(), Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let data = match sg_spin_data_encryption_caps(file) {
 | 
					    let data = match sg_spin_data_encryption_caps(file) {
 | 
				
			||||||
        Ok(data) => data,
 | 
					        Ok(data) => data,
 | 
				
			||||||
        Err(_) if key.is_none() => {
 | 
					        Err(_) if key.is_none() => {
 | 
				
			||||||
@ -85,7 +78,6 @@ fn sg_spout_set_encryption<F: AsRawFd>(
 | 
				
			|||||||
    algorythm_index: u8,
 | 
					    algorythm_index: u8,
 | 
				
			||||||
    key: Option<[u8; 32]>,
 | 
					    key: Option<[u8; 32]>,
 | 
				
			||||||
) -> Result<(), Error> {
 | 
					) -> Result<(), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, 0)?;
 | 
					    let mut sg_raw = SgRaw::new(file, 0)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut outbuf_len = std::mem::size_of::<SspSetDataEncryptionPage>();
 | 
					    let mut outbuf_len = std::mem::size_of::<SspSetDataEncryptionPage>();
 | 
				
			||||||
@ -106,7 +98,11 @@ fn sg_spout_set_encryption<F: AsRawFd>(
 | 
				
			|||||||
        algorythm_index,
 | 
					        algorythm_index,
 | 
				
			||||||
        key_format: 0,
 | 
					        key_format: 0,
 | 
				
			||||||
        reserved: [0u8; 8],
 | 
					        reserved: [0u8; 8],
 | 
				
			||||||
        key_len: if let Some(ref key) = key { key.len() as u16 } else { 0 },
 | 
					        key_len: if let Some(ref key) = key {
 | 
				
			||||||
 | 
					            key.len() as u16
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            0
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut writer = &mut outbuf[..];
 | 
					    let mut writer = &mut outbuf[..];
 | 
				
			||||||
@ -119,58 +115,72 @@ fn sg_spout_set_encryption<F: AsRawFd>(
 | 
				
			|||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT)
 | 
					    cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT)
 | 
				
			||||||
    cmd.push(0x20); // Tape Data Encryption Page
 | 
					    cmd.push(0x20); // Tape Data Encryption Page
 | 
				
			||||||
    cmd.push(0);cmd.push(0x10); // Set Data Encryption page
 | 
					    cmd.push(0);
 | 
				
			||||||
 | 
					    cmd.push(0x10); // Set Data Encryption page
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.extend(&(outbuf_len as u32).to_be_bytes()); // data out len
 | 
					    cmd.extend(&(outbuf_len as u32).to_be_bytes()); // data out len
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_out_command(&cmd, &outbuf)
 | 
					    sg_raw
 | 
				
			||||||
 | 
					        .do_out_command(&cmd, &outbuf)
 | 
				
			||||||
        .map_err(|err| format_err!("set data encryption SPOUT(20h[0010h]) failed - {}", err))
 | 
					        .map_err(|err| format_err!("set data encryption SPOUT(20h[0010h]) failed - {}", err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Warning: this blocks and fails if there is no media loaded
 | 
					// Warning: this blocks and fails if there is no media loaded
 | 
				
			||||||
fn sg_spin_data_encryption_status<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
					fn sg_spin_data_encryption_status<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
				
			||||||
 | 
					    let allocation_len: u32 = 8192 + 4;
 | 
				
			||||||
    let allocation_len: u32 = 8192+4;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
					    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
 | 
					    cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
 | 
				
			||||||
    cmd.push(0x20); // Tape Data Encryption Page
 | 
					    cmd.push(0x20); // Tape Data Encryption Page
 | 
				
			||||||
    cmd.push(0);cmd.push(0x20); // Data Encryption Status page
 | 
					    cmd.push(0);
 | 
				
			||||||
 | 
					    cmd.push(0x20); // Data Encryption Status page
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.extend(&allocation_len.to_be_bytes());
 | 
					    cmd.extend(&allocation_len.to_be_bytes());
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_command(&cmd)
 | 
					    sg_raw
 | 
				
			||||||
        .map_err(|err| format_err!("read data encryption status SPIN(20h[0020h]) failed - {}", err))
 | 
					        .do_command(&cmd)
 | 
				
			||||||
 | 
					        .map_err(|err| {
 | 
				
			||||||
 | 
					            format_err!(
 | 
				
			||||||
 | 
					                "read data encryption status SPIN(20h[0020h]) failed - {}",
 | 
				
			||||||
 | 
					                err
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
        .map(|v| v.to_vec())
 | 
					        .map(|v| v.to_vec())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Warning: this blocks and fails if there is no media loaded
 | 
					// Warning: this blocks and fails if there is no media loaded
 | 
				
			||||||
fn sg_spin_data_encryption_caps<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
					fn sg_spin_data_encryption_caps<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
				
			||||||
 | 
					    let allocation_len: u32 = 8192 + 4;
 | 
				
			||||||
    let allocation_len: u32 = 8192+4;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
					    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
 | 
					    cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
 | 
				
			||||||
    cmd.push(0x20); // Tape Data Encryption Page
 | 
					    cmd.push(0x20); // Tape Data Encryption Page
 | 
				
			||||||
    cmd.push(0);cmd.push(0x10); // Data Encryption Capabilities page
 | 
					    cmd.push(0);
 | 
				
			||||||
 | 
					    cmd.push(0x10); // Data Encryption Capabilities page
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.extend(&allocation_len.to_be_bytes());
 | 
					    cmd.extend(&allocation_len.to_be_bytes());
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_command(&cmd)
 | 
					    sg_raw
 | 
				
			||||||
        .map_err(|err| format_err!("read data encryption caps SPIN(20h[0010h]) failed - {}", err))
 | 
					        .do_command(&cmd)
 | 
				
			||||||
 | 
					        .map_err(|err| {
 | 
				
			||||||
 | 
					            format_err!(
 | 
				
			||||||
 | 
					                "read data encryption caps SPIN(20h[0010h]) failed - {}",
 | 
				
			||||||
 | 
					                err
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
        .map(|v| v.to_vec())
 | 
					        .map(|v| v.to_vec())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -215,7 +225,6 @@ struct SspDataEncryptionAlgorithmDescriptor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Returns the algorythm_index for AES-GCM
 | 
					// Returns the algorythm_index for AES-GCM
 | 
				
			||||||
fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
 | 
					fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    proxmox_lang::try_block!({
 | 
					    proxmox_lang::try_block!({
 | 
				
			||||||
        let mut reader = data;
 | 
					        let mut reader = data;
 | 
				
			||||||
        let _page: SspDataEncryptionCapabilityPage = unsafe { reader.read_be_value()? };
 | 
					        let _page: SspDataEncryptionCapabilityPage = unsafe { reader.read_be_value()? };
 | 
				
			||||||
@ -223,9 +232,10 @@ fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
 | 
				
			|||||||
        let mut aes_gcm_index = None;
 | 
					        let mut aes_gcm_index = None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        loop {
 | 
					        loop {
 | 
				
			||||||
            if reader.is_empty() { break; };
 | 
					            if reader.is_empty() {
 | 
				
			||||||
            let desc: SspDataEncryptionAlgorithmDescriptor =
 | 
					                break;
 | 
				
			||||||
                unsafe { reader.read_be_value()? };
 | 
					            };
 | 
				
			||||||
 | 
					            let desc: SspDataEncryptionAlgorithmDescriptor = unsafe { reader.read_be_value()? };
 | 
				
			||||||
            if desc.descriptor_len != 0x14 {
 | 
					            if desc.descriptor_len != 0x14 {
 | 
				
			||||||
                bail!("got wrong key descriptor len");
 | 
					                bail!("got wrong key descriptor len");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -245,8 +255,8 @@ fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
 | 
				
			|||||||
            Some(index) => Ok(index),
 | 
					            Some(index) => Ok(index),
 | 
				
			||||||
            None => bail!("drive does not support AES-GCM encryption"),
 | 
					            None => bail!("drive does not support AES-GCM encryption"),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }).map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err))
 | 
					    })
 | 
				
			||||||
 | 
					    .map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Endian)]
 | 
					#[derive(Endian)]
 | 
				
			||||||
@ -266,7 +276,6 @@ struct SspDataEncryptionStatusPage {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatus, Error> {
 | 
					fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatus, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    proxmox_lang::try_block!({
 | 
					    proxmox_lang::try_block!({
 | 
				
			||||||
        let mut reader = data;
 | 
					        let mut reader = data;
 | 
				
			||||||
        let page: SspDataEncryptionStatusPage = unsafe { reader.read_be_value()? };
 | 
					        let page: SspDataEncryptionStatusPage = unsafe { reader.read_be_value()? };
 | 
				
			||||||
@ -283,11 +292,9 @@ fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatu
 | 
				
			|||||||
            _ => bail!("unknown encryption mode"),
 | 
					            _ => bail!("unknown encryption mode"),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let status = DataEncryptionStatus {
 | 
					        let status = DataEncryptionStatus { mode };
 | 
				
			||||||
            mode,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(status)
 | 
					        Ok(status)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
    }).map_err(|err| format_err!("decode data encryption status page failed - {}", err))
 | 
					    .map_err(|err| format_err!("decode data encryption status page failed - {}", err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ use super::TapeAlertFlags;
 | 
				
			|||||||
// see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1
 | 
					// see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Endian)]
 | 
					#[derive(Endian)]
 | 
				
			||||||
#[repr(C,packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
struct MamAttributeHeader {
 | 
					struct MamAttributeHeader {
 | 
				
			||||||
    id: u16,
 | 
					    id: u16,
 | 
				
			||||||
    flags: u8,
 | 
					    flags: u8,
 | 
				
			||||||
@ -30,8 +30,13 @@ enum MamFormat {
 | 
				
			|||||||
    DEC,
 | 
					    DEC,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
 | 
					static MAM_ATTRIBUTES: &[(u16, u16, MamFormat, &str)] = &[
 | 
				
			||||||
    (0x00_00, 8, MamFormat::DEC, "Remaining Capacity In Partition"),
 | 
					    (
 | 
				
			||||||
 | 
					        0x00_00,
 | 
				
			||||||
 | 
					        8,
 | 
				
			||||||
 | 
					        MamFormat::DEC,
 | 
				
			||||||
 | 
					        "Remaining Capacity In Partition",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    (0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"),
 | 
					    (0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"),
 | 
				
			||||||
    (0x00_02, 8, MamFormat::DEC, "Tapealert Flags"),
 | 
					    (0x00_02, 8, MamFormat::DEC, "Tapealert Flags"),
 | 
				
			||||||
    (0x00_03, 8, MamFormat::DEC, "Load Count"),
 | 
					    (0x00_03, 8, MamFormat::DEC, "Load Count"),
 | 
				
			||||||
@ -40,19 +45,66 @@ static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
 | 
				
			|||||||
    (0x00_06, 1, MamFormat::BINARY, "Formatted Density Code"),
 | 
					    (0x00_06, 1, MamFormat::BINARY, "Formatted Density Code"),
 | 
				
			||||||
    (0x00_07, 2, MamFormat::DEC, "Initialization Count"),
 | 
					    (0x00_07, 2, MamFormat::DEC, "Initialization Count"),
 | 
				
			||||||
    (0x00_09, 4, MamFormat::BINARY, "Volume Change Reference"),
 | 
					    (0x00_09, 4, MamFormat::BINARY, "Volume Change Reference"),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
    (0x02_0A, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Last Load"),
 | 
					        0x02_0A,
 | 
				
			||||||
    (0x02_0B, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-1"),
 | 
					        40,
 | 
				
			||||||
    (0x02_0C, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-2"),
 | 
					        MamFormat::ASCII,
 | 
				
			||||||
    (0x02_0D, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-3"),
 | 
					        "Device Vendor/Serial Number at Last Load",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    (0x02_20, 8, MamFormat::DEC, "Total MBytes Written in Medium Life"),
 | 
					    (
 | 
				
			||||||
    (0x02_21, 8, MamFormat::DEC, "Total MBytes Read In Medium Life"),
 | 
					        0x02_0B,
 | 
				
			||||||
    (0x02_22, 8, MamFormat::DEC, "Total MBytes Written in Current Load"),
 | 
					        40,
 | 
				
			||||||
    (0x02_23, 8, MamFormat::DEC, "Total MBytes Read in Current/Last Load"),
 | 
					        MamFormat::ASCII,
 | 
				
			||||||
    (0x02_24, 8, MamFormat::BINARY, "Logical Position of First Encrypted Block"),
 | 
					        "Device Vendor/Serial Number at Load-1",
 | 
				
			||||||
    (0x02_25, 8, MamFormat::BINARY, "Logical Position of First Unencrypted Block After the First Encrypted Block"),
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x02_0C,
 | 
				
			||||||
 | 
					        40,
 | 
				
			||||||
 | 
					        MamFormat::ASCII,
 | 
				
			||||||
 | 
					        "Device Vendor/Serial Number at Load-2",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x02_0D,
 | 
				
			||||||
 | 
					        40,
 | 
				
			||||||
 | 
					        MamFormat::ASCII,
 | 
				
			||||||
 | 
					        "Device Vendor/Serial Number at Load-3",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x02_20,
 | 
				
			||||||
 | 
					        8,
 | 
				
			||||||
 | 
					        MamFormat::DEC,
 | 
				
			||||||
 | 
					        "Total MBytes Written in Medium Life",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x02_21,
 | 
				
			||||||
 | 
					        8,
 | 
				
			||||||
 | 
					        MamFormat::DEC,
 | 
				
			||||||
 | 
					        "Total MBytes Read In Medium Life",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x02_22,
 | 
				
			||||||
 | 
					        8,
 | 
				
			||||||
 | 
					        MamFormat::DEC,
 | 
				
			||||||
 | 
					        "Total MBytes Written in Current Load",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x02_23,
 | 
				
			||||||
 | 
					        8,
 | 
				
			||||||
 | 
					        MamFormat::DEC,
 | 
				
			||||||
 | 
					        "Total MBytes Read in Current/Last Load",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x02_24,
 | 
				
			||||||
 | 
					        8,
 | 
				
			||||||
 | 
					        MamFormat::BINARY,
 | 
				
			||||||
 | 
					        "Logical Position of First Encrypted Block",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x02_25,
 | 
				
			||||||
 | 
					        8,
 | 
				
			||||||
 | 
					        MamFormat::BINARY,
 | 
				
			||||||
 | 
					        "Logical Position of First Unencrypted Block After the First Encrypted Block",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    (0x04_00, 8, MamFormat::ASCII, "Medium Manufacturer"),
 | 
					    (0x04_00, 8, MamFormat::ASCII, "Medium Manufacturer"),
 | 
				
			||||||
    (0x04_01, 32, MamFormat::ASCII, "Medium Serial Number"),
 | 
					    (0x04_01, 32, MamFormat::ASCII, "Medium Serial Number"),
 | 
				
			||||||
    (0x04_02, 4, MamFormat::DEC, "Medium Length"),
 | 
					    (0x04_02, 4, MamFormat::DEC, "Medium Length"),
 | 
				
			||||||
@ -64,27 +116,54 @@ static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
 | 
				
			|||||||
    (0x04_08, 1, MamFormat::BINARY, "Medium Type"),
 | 
					    (0x04_08, 1, MamFormat::BINARY, "Medium Type"),
 | 
				
			||||||
    (0x04_09, 2, MamFormat::BINARY, "Medium Type Information"),
 | 
					    (0x04_09, 2, MamFormat::BINARY, "Medium Type Information"),
 | 
				
			||||||
    (0x04_0B, 10, MamFormat::BINARY, "Supported Density Codes"),
 | 
					    (0x04_0B, 10, MamFormat::BINARY, "Supported Density Codes"),
 | 
				
			||||||
 | 
					 | 
				
			||||||
    (0x08_00, 8, MamFormat::ASCII, "Application Vendor"),
 | 
					    (0x08_00, 8, MamFormat::ASCII, "Application Vendor"),
 | 
				
			||||||
    (0x08_01, 32, MamFormat::ASCII, "Application Name"),
 | 
					    (0x08_01, 32, MamFormat::ASCII, "Application Name"),
 | 
				
			||||||
    (0x08_02, 8, MamFormat::ASCII, "Application Version"),
 | 
					    (0x08_02, 8, MamFormat::ASCII, "Application Version"),
 | 
				
			||||||
    (0x08_03, 160, MamFormat::ASCII, "User Medium Text Label"),
 | 
					    (0x08_03, 160, MamFormat::ASCII, "User Medium Text Label"),
 | 
				
			||||||
    (0x08_04, 12, MamFormat::ASCII, "Date And Time Last Written"),
 | 
					    (0x08_04, 12, MamFormat::ASCII, "Date And Time Last Written"),
 | 
				
			||||||
    (0x08_05, 1, MamFormat::BINARY, "Text Localization Identifier"),
 | 
					    (
 | 
				
			||||||
 | 
					        0x08_05,
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        MamFormat::BINARY,
 | 
				
			||||||
 | 
					        "Text Localization Identifier",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    (0x08_06, 32, MamFormat::ASCII, "Barcode"),
 | 
					    (0x08_06, 32, MamFormat::ASCII, "Barcode"),
 | 
				
			||||||
    (0x08_07, 80, MamFormat::ASCII, "Owning Host Textual Name"),
 | 
					    (0x08_07, 80, MamFormat::ASCII, "Owning Host Textual Name"),
 | 
				
			||||||
    (0x08_08, 160, MamFormat::ASCII, "Media Pool"),
 | 
					    (0x08_08, 160, MamFormat::ASCII, "Media Pool"),
 | 
				
			||||||
    (0x08_0B, 16, MamFormat::ASCII, "Application Format Version"),
 | 
					    (0x08_0B, 16, MamFormat::ASCII, "Application Format Version"),
 | 
				
			||||||
    (0x08_0C, 50, MamFormat::ASCII, "Volume Coherency Information"),
 | 
					    (
 | 
				
			||||||
    (0x08_20, 36, MamFormat::ASCII, "Medium Globally Unique Identifier"),
 | 
					        0x08_0C,
 | 
				
			||||||
    (0x08_21, 36, MamFormat::ASCII, "Media Pool Globally Unique Identifier"),
 | 
					        50,
 | 
				
			||||||
 | 
					        MamFormat::ASCII,
 | 
				
			||||||
    (0x10_00, 28,  MamFormat::BINARY, "Unique Cartridge Identify (UCI)"),
 | 
					        "Volume Coherency Information",
 | 
				
			||||||
    (0x10_01, 24,  MamFormat::BINARY, "Alternate Unique Cartridge Identify (Alt-UCI)"),
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x08_20,
 | 
				
			||||||
 | 
					        36,
 | 
				
			||||||
 | 
					        MamFormat::ASCII,
 | 
				
			||||||
 | 
					        "Medium Globally Unique Identifier",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x08_21,
 | 
				
			||||||
 | 
					        36,
 | 
				
			||||||
 | 
					        MamFormat::ASCII,
 | 
				
			||||||
 | 
					        "Media Pool Globally Unique Identifier",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x10_00,
 | 
				
			||||||
 | 
					        28,
 | 
				
			||||||
 | 
					        MamFormat::BINARY,
 | 
				
			||||||
 | 
					        "Unique Cartridge Identify (UCI)",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        0x10_01,
 | 
				
			||||||
 | 
					        24,
 | 
				
			||||||
 | 
					        MamFormat::BINARY,
 | 
				
			||||||
 | 
					        "Alternate Unique Cartridge Identify (Alt-UCI)",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lazy_static::lazy_static!{
 | 
					lazy_static::lazy_static! {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static ref MAM_ATTRIBUTE_NAMES: HashMap<u16, &'static (u16, u16, MamFormat, &'static str)> = {
 | 
					    static ref MAM_ATTRIBUTE_NAMES: HashMap<u16, &'static (u16, u16, MamFormat, &'static str)> = {
 | 
				
			||||||
        let mut map = HashMap::new();
 | 
					        let mut map = HashMap::new();
 | 
				
			||||||
@ -98,8 +177,7 @@ lazy_static::lazy_static!{
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
					fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
				
			||||||
 | 
					    let alloc_len: u32 = 32 * 1024;
 | 
				
			||||||
    let alloc_len: u32 = 32*1024;
 | 
					 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
 | 
					    let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
@ -108,33 +186,35 @@ fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
				
			|||||||
    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
 | 
					    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
 | 
				
			||||||
    cmd.extend(&[0u8, 0u8]);
 | 
					    cmd.extend(&[0u8, 0u8]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_command(&cmd)
 | 
					    sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("read cartidge memory failed - {}", err))
 | 
					        .map_err(|err| format_err!("read cartidge memory failed - {}", err))
 | 
				
			||||||
        .map(|v| v.to_vec())
 | 
					        .map(|v| v.to_vec())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Read Medium auxiliary memory attributes (cartridge memory) using raw SCSI command.
 | 
					/// Read Medium auxiliary memory attributes (cartridge memory) using raw SCSI command.
 | 
				
			||||||
pub fn read_mam_attributes<F: AsRawFd>(file: &mut F) -> Result<Vec<MamAttribute>, Error> {
 | 
					pub fn read_mam_attributes<F: AsRawFd>(file: &mut F) -> Result<Vec<MamAttribute>, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let data = read_tape_mam(file)?;
 | 
					    let data = read_tape_mam(file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    decode_mam_attributes(&data)
 | 
					    decode_mam_attributes(&data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
 | 
					fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut reader = data;
 | 
					    let mut reader = data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let data_len: u32 = unsafe { reader.read_be_value()? };
 | 
					    let data_len: u32 = unsafe { reader.read_be_value()? };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let expected_len = data_len as usize;
 | 
					    let expected_len = data_len as usize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if reader.len() < expected_len {
 | 
					    if reader.len() < expected_len {
 | 
				
			||||||
        bail!("read_mam_attributes: got unexpected data len ({} != {})", reader.len(), expected_len);
 | 
					        bail!(
 | 
				
			||||||
 | 
					            "read_mam_attributes: got unexpected data len ({} != {})",
 | 
				
			||||||
 | 
					            reader.len(),
 | 
				
			||||||
 | 
					            expected_len
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    } else if reader.len() > expected_len {
 | 
					    } else if reader.len() > expected_len {
 | 
				
			||||||
        // Note: Quantum hh7 returns the allocation_length instead of real data_len
 | 
					        // Note: Quantum hh7 returns the allocation_length instead of real data_len
 | 
				
			||||||
        reader = &data[4..expected_len+4];
 | 
					        reader = &data[4..expected_len + 4];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut list = Vec::new();
 | 
					    let mut list = Vec::new();
 | 
				
			||||||
@ -164,7 +244,8 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
 | 
				
			|||||||
                        } else if info.1 == 4 {
 | 
					                        } else if info.1 == 4 {
 | 
				
			||||||
                            format!("{}", u32::from_be_bytes(data[0..4].try_into()?))
 | 
					                            format!("{}", u32::from_be_bytes(data[0..4].try_into()?))
 | 
				
			||||||
                        } else if info.1 == 8 {
 | 
					                        } else if info.1 == 8 {
 | 
				
			||||||
                            if head_id == 2 { // Tape Alert Flags
 | 
					                            if head_id == 2 {
 | 
				
			||||||
 | 
					                                // Tape Alert Flags
 | 
				
			||||||
                                let value = u64::from_be_bytes(data[0..8].try_into()?);
 | 
					                                let value = u64::from_be_bytes(data[0..8].try_into()?);
 | 
				
			||||||
                                let flags = TapeAlertFlags::from_bits_truncate(value);
 | 
					                                let flags = TapeAlertFlags::from_bits_truncate(value);
 | 
				
			||||||
                                format!("{:?}", flags)
 | 
					                                format!("{:?}", flags)
 | 
				
			||||||
@ -174,7 +255,7 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
 | 
				
			|||||||
                        } else {
 | 
					                        } else {
 | 
				
			||||||
                            unreachable!();
 | 
					                            unreachable!();
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    },
 | 
					                    }
 | 
				
			||||||
                    MamFormat::BINARY => hex::encode(&data),
 | 
					                    MamFormat::BINARY => hex::encode(&data),
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                list.push(MamAttribute {
 | 
					                list.push(MamAttribute {
 | 
				
			||||||
@ -183,7 +264,10 @@ fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
 | 
				
			|||||||
                    value,
 | 
					                    value,
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                eprintln!("read_mam_attributes: got starnge data len for id {:04X}", head_id);
 | 
					                eprintln!(
 | 
				
			||||||
 | 
					                    "read_mam_attributes: got starnge data len for id {:04X}",
 | 
				
			||||||
 | 
					                    head_id
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // skip unknown IDs
 | 
					            // skip unknown IDs
 | 
				
			||||||
@ -201,8 +285,11 @@ pub struct MediaUsageInfo {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Extract Media Usage Information from Cartridge Memory
 | 
					/// Extract Media Usage Information from Cartridge Memory
 | 
				
			||||||
pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, Error> {
 | 
					pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, Error> {
 | 
				
			||||||
 | 
					    let manufactured: i64 = match mam
 | 
				
			||||||
   let manufactured: i64 = match mam.iter().find(|v| v.id == 0x04_06).map(|v| v.value.clone()) {
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .find(|v| v.id == 0x04_06)
 | 
				
			||||||
 | 
					        .map(|v| v.value.clone())
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        Some(date_str) => {
 | 
					        Some(date_str) => {
 | 
				
			||||||
            if date_str.len() != 8 {
 | 
					            if date_str.len() != 8 {
 | 
				
			||||||
                bail!("unable to parse 'Medium Manufacture Date' - wrong length");
 | 
					                bail!("unable to parse 'Medium Manufacture Date' - wrong length");
 | 
				
			||||||
@ -222,15 +309,27 @@ pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, E
 | 
				
			|||||||
        None => bail!("unable to read MAM 'Medium Manufacture Date'"),
 | 
					        None => bail!("unable to read MAM 'Medium Manufacture Date'"),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let bytes_written: u64 = match mam.iter().find(|v| v.id == 0x02_20).map(|v| v.value.clone()) {
 | 
					    let bytes_written: u64 = match mam
 | 
				
			||||||
        Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .find(|v| v.id == 0x02_20)
 | 
				
			||||||
 | 
					        .map(|v| v.value.clone())
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Some(read_str) => read_str.parse::<u64>()? * 1024 * 1024,
 | 
				
			||||||
        None => bail!("unable to read MAM 'Total MBytes Written In Medium Life'"),
 | 
					        None => bail!("unable to read MAM 'Total MBytes Written In Medium Life'"),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let bytes_read: u64 = match mam.iter().find(|v| v.id == 0x02_21).map(|v| v.value.clone()) {
 | 
					    let bytes_read: u64 = match mam
 | 
				
			||||||
        Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .find(|v| v.id == 0x02_21)
 | 
				
			||||||
 | 
					        .map(|v| v.value.clone())
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Some(read_str) => read_str.parse::<u64>()? * 1024 * 1024,
 | 
				
			||||||
        None => bail!("unable to read MAM 'Total MBytes Read In Medium Life'"),
 | 
					        None => bail!("unable to read MAM 'Total MBytes Read In Medium Life'"),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(MediaUsageInfo { manufactured, bytes_written, bytes_read })
 | 
					    Ok(MediaUsageInfo {
 | 
				
			||||||
 | 
					        manufactured,
 | 
				
			||||||
 | 
					        bytes_written,
 | 
				
			||||||
 | 
					        bytes_read,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
use anyhow::{bail, format_err, Error};
 | 
					use anyhow::{bail, format_err, Error};
 | 
				
			||||||
use std::io::Read;
 | 
					 | 
				
			||||||
use endian_trait::Endian;
 | 
					use endian_trait::Endian;
 | 
				
			||||||
 | 
					use std::io::Read;
 | 
				
			||||||
use std::os::unix::io::AsRawFd;
 | 
					use std::os::unix::io::AsRawFd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use proxmox_io::ReadExt;
 | 
					use proxmox_io::ReadExt;
 | 
				
			||||||
@ -33,7 +33,8 @@ pub fn report_density<F: AsRawFd>(file: &mut F) -> Result<u8, Error> {
 | 
				
			|||||||
    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
 | 
					    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
 | 
				
			||||||
    cmd.push(0u8); // control byte
 | 
					    cmd.push(0u8); // control byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let data = sg_raw.do_command(&cmd)
 | 
					    let data = sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("report density failed - {}", err))?;
 | 
					        .map_err(|err| format_err!("report density failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut max_density = 0u8;
 | 
					    let mut max_density = 0u8;
 | 
				
			||||||
@ -48,13 +49,15 @@ pub fn report_density<F: AsRawFd>(file: &mut F) -> Result<u8, Error> {
 | 
				
			|||||||
            bail!("invalid page length {} {}", page_len + 2, data.len());
 | 
					            bail!("invalid page length {} {}", page_len + 2, data.len());
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // Note: Quantum hh7 returns the allocation_length instead of real data_len
 | 
					            // Note: Quantum hh7 returns the allocation_length instead of real data_len
 | 
				
			||||||
            reader = &data[2..page_len+2];
 | 
					            reader = &data[2..page_len + 2];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let mut reserved = [0u8; 2];
 | 
					        let mut reserved = [0u8; 2];
 | 
				
			||||||
        reader.read_exact(&mut reserved)?;
 | 
					        reader.read_exact(&mut reserved)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        loop {
 | 
					        loop {
 | 
				
			||||||
            if reader.is_empty() { break; }
 | 
					            if reader.is_empty() {
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            let block: DesnityDescriptorBlock = unsafe { reader.read_be_value()? };
 | 
					            let block: DesnityDescriptorBlock = unsafe { reader.read_be_value()? };
 | 
				
			||||||
            if block.primary_density_code > max_density {
 | 
					            if block.primary_density_code > max_density {
 | 
				
			||||||
                max_density = block.primary_density_code;
 | 
					                max_density = block.primary_density_code;
 | 
				
			||||||
@ -62,8 +65,8 @@ pub fn report_density<F: AsRawFd>(file: &mut F) -> Result<u8, Error> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
    }).map_err(|err| format_err!("decode report density failed - {}", err))?;
 | 
					    .map_err(|err| format_err!("decode report density failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(max_density)
 | 
					    Ok(max_density)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ use proxmox_io::ReadExt;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use crate::sgutils2::SgRaw;
 | 
					use crate::sgutils2::SgRaw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bitflags::bitflags!{
 | 
					bitflags::bitflags! {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Tape Alert Flags
 | 
					    /// Tape Alert Flags
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -74,15 +74,12 @@ bitflags::bitflags!{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Read Tape Alert Flags using raw SCSI command.
 | 
					/// Read Tape Alert Flags using raw SCSI command.
 | 
				
			||||||
pub fn read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<TapeAlertFlags, Error> {
 | 
					pub fn read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<TapeAlertFlags, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let data = sg_read_tape_alert_flags(file)?;
 | 
					    let data = sg_read_tape_alert_flags(file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    decode_tape_alert_flags(&data)
 | 
					    decode_tape_alert_flags(&data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
					fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, 512)?;
 | 
					    let mut sg_raw = SgRaw::new(file, 512)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Note: We cannjot use LP 2Eh TapeAlerts, because that clears flags on read.
 | 
					    // Note: We cannjot use LP 2Eh TapeAlerts, because that clears flags on read.
 | 
				
			||||||
@ -91,7 +88,7 @@ fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error>
 | 
				
			|||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.push(0x4D); // LOG SENSE
 | 
					    cmd.push(0x4D); // LOG SENSE
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push((1<<6) | 0x12); // Tape Alert Response log page
 | 
					    cmd.push((1 << 6) | 0x12); // Tape Alert Response log page
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
@ -99,13 +96,13 @@ fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error>
 | 
				
			|||||||
    cmd.extend(&[2u8, 0u8]); // alloc len
 | 
					    cmd.extend(&[2u8, 0u8]); // alloc len
 | 
				
			||||||
    cmd.push(0u8); // control byte
 | 
					    cmd.push(0u8); // control byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_command(&cmd)
 | 
					    sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("read tape alert flags failed - {}", err))
 | 
					        .map_err(|err| format_err!("read tape alert flags failed - {}", err))
 | 
				
			||||||
        .map(|v| v.to_vec())
 | 
					        .map(|v| v.to_vec())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> {
 | 
					fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    proxmox_lang::try_block!({
 | 
					    proxmox_lang::try_block!({
 | 
				
			||||||
        if !((data[0] & 0x7f) == 0x12 && data[1] == 0) {
 | 
					        if !((data[0] & 0x7f) == 0x12 && data[1] == 0) {
 | 
				
			||||||
            bail!("invalid response");
 | 
					            bail!("invalid response");
 | 
				
			||||||
@ -136,30 +133,30 @@ fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> {
 | 
				
			|||||||
        value = value.reverse_bits();
 | 
					        value = value.reverse_bits();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(TapeAlertFlags::from_bits_truncate(value))
 | 
					        Ok(TapeAlertFlags::from_bits_truncate(value))
 | 
				
			||||||
    }).map_err(|err| format_err!("decode tape alert flags failed - {}", err))
 | 
					    })
 | 
				
			||||||
 | 
					    .map_err(|err| format_err!("decode tape alert flags failed - {}", err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CRITICAL_FLAG_MASK: u64 =
 | 
					const CRITICAL_FLAG_MASK: u64 = TapeAlertFlags::MEDIA.bits()
 | 
				
			||||||
TapeAlertFlags::MEDIA.bits() |
 | 
					    | TapeAlertFlags::WRITE_FAILURE.bits()
 | 
				
			||||||
TapeAlertFlags::WRITE_FAILURE.bits() |
 | 
					    | TapeAlertFlags::READ_FAILURE.bits()
 | 
				
			||||||
TapeAlertFlags::READ_FAILURE.bits() |
 | 
					    | TapeAlertFlags::WRITE_PROTECT.bits()
 | 
				
			||||||
TapeAlertFlags::WRITE_PROTECT.bits() |
 | 
					    | TapeAlertFlags::UNRECOVERABLE_SNAPPED_TAPE.bits()
 | 
				
			||||||
TapeAlertFlags::UNRECOVERABLE_SNAPPED_TAPE.bits() |
 | 
					    | TapeAlertFlags::FORCED_EJECT.bits()
 | 
				
			||||||
TapeAlertFlags::FORCED_EJECT.bits() |
 | 
					    | TapeAlertFlags::EXPIRED_CLEANING_MEDIA.bits()
 | 
				
			||||||
TapeAlertFlags::EXPIRED_CLEANING_MEDIA.bits() |
 | 
					    | TapeAlertFlags::INVALID_CLEANING_TAPE.bits()
 | 
				
			||||||
TapeAlertFlags::INVALID_CLEANING_TAPE.bits() |
 | 
					    | TapeAlertFlags::HARDWARE_A.bits()
 | 
				
			||||||
TapeAlertFlags::HARDWARE_A.bits() |
 | 
					    | TapeAlertFlags::HARDWARE_B.bits()
 | 
				
			||||||
TapeAlertFlags::HARDWARE_B.bits() |
 | 
					    | TapeAlertFlags::EJECT_MEDIA.bits()
 | 
				
			||||||
TapeAlertFlags::EJECT_MEDIA.bits() |
 | 
					    | TapeAlertFlags::PREDICTIVE_FAILURE.bits()
 | 
				
			||||||
TapeAlertFlags::PREDICTIVE_FAILURE.bits() |
 | 
					    | TapeAlertFlags::LOADER_STRAY_TAPE.bits()
 | 
				
			||||||
TapeAlertFlags::LOADER_STRAY_TAPE.bits() |
 | 
					    | TapeAlertFlags::LOADER_MAGAZINE.bits()
 | 
				
			||||||
TapeAlertFlags::LOADER_MAGAZINE.bits() |
 | 
					    | TapeAlertFlags::TAPE_SYSTEM_AREA_WRITE_FAILURE.bits()
 | 
				
			||||||
TapeAlertFlags::TAPE_SYSTEM_AREA_WRITE_FAILURE.bits() |
 | 
					    | TapeAlertFlags::TAPE_SYSTEM_AREA_READ_FAILURE.bits()
 | 
				
			||||||
TapeAlertFlags::TAPE_SYSTEM_AREA_READ_FAILURE.bits() |
 | 
					    | TapeAlertFlags::NO_START_OF_DATA.bits()
 | 
				
			||||||
TapeAlertFlags::NO_START_OF_DATA.bits() |
 | 
					    | TapeAlertFlags::LOADING_FAILURE.bits()
 | 
				
			||||||
TapeAlertFlags::LOADING_FAILURE.bits() |
 | 
					    | TapeAlertFlags::UNRECOVERABLE_UNLOAD_FAILURE.bits()
 | 
				
			||||||
TapeAlertFlags::UNRECOVERABLE_UNLOAD_FAILURE.bits() |
 | 
					    | TapeAlertFlags::AUTOMATION_INTERFACE_FAILURE.bits();
 | 
				
			||||||
TapeAlertFlags::AUTOMATION_INTERFACE_FAILURE.bits();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Check if tape-alert-flags contains critial errors.
 | 
					/// Check if tape-alert-flags contains critial errors.
 | 
				
			||||||
pub fn tape_alert_flags_critical(flags: TapeAlertFlags) -> bool {
 | 
					pub fn tape_alert_flags_critical(flags: TapeAlertFlags) -> bool {
 | 
				
			||||||
@ -167,8 +164,7 @@ pub fn tape_alert_flags_critical(flags: TapeAlertFlags) -> bool {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MEDIA_LIFE_MASK: u64 =
 | 
					const MEDIA_LIFE_MASK: u64 =
 | 
				
			||||||
TapeAlertFlags::MEDIA_LIFE.bits() |
 | 
					    TapeAlertFlags::MEDIA_LIFE.bits() | TapeAlertFlags::NEARING_MEDIA_LIFE.bits();
 | 
				
			||||||
TapeAlertFlags::NEARING_MEDIA_LIFE.bits();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Check if tape-alert-flags indicates media-life end
 | 
					/// Check if tape-alert-flags indicates media-life end
 | 
				
			||||||
pub fn tape_alert_flags_media_life(flags: TapeAlertFlags) -> bool {
 | 
					pub fn tape_alert_flags_media_life(flags: TapeAlertFlags) -> bool {
 | 
				
			||||||
@ -176,8 +172,7 @@ pub fn tape_alert_flags_media_life(flags: TapeAlertFlags) -> bool {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MEDIA_CLEAN_MASK: u64 =
 | 
					const MEDIA_CLEAN_MASK: u64 =
 | 
				
			||||||
TapeAlertFlags::CLEAN_NOW.bits() |
 | 
					    TapeAlertFlags::CLEAN_NOW.bits() | TapeAlertFlags::CLEAN_PERIODIC.bits();
 | 
				
			||||||
TapeAlertFlags::CLEAN_PERIODIC.bits();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Check if tape-alert-flags indicates media cleaning request
 | 
					/// Check if tape-alert-flags indicates media cleaning request
 | 
				
			||||||
pub fn tape_alert_flags_cleaning_request(flags: TapeAlertFlags) -> bool {
 | 
					pub fn tape_alert_flags_cleaning_request(flags: TapeAlertFlags) -> bool {
 | 
				
			||||||
 | 
				
			|||||||
@ -17,21 +17,19 @@ use crate::sgutils2::SgRaw;
 | 
				
			|||||||
/// The Volume Statistics log page is included in Ultrium 5 and later
 | 
					/// The Volume Statistics log page is included in Ultrium 5 and later
 | 
				
			||||||
/// drives.
 | 
					/// drives.
 | 
				
			||||||
pub fn read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Lp17VolumeStatistics, Error> {
 | 
					pub fn read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Lp17VolumeStatistics, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let data = sg_read_volume_statistics(file)?;
 | 
					    let data = sg_read_volume_statistics(file)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    decode_volume_statistics(&data)
 | 
					    decode_volume_statistics(&data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
					fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let alloc_len: u16 = 8192;
 | 
					    let alloc_len: u16 = 8192;
 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
 | 
					    let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.push(0x4D); // LOG SENSE
 | 
					    cmd.push(0x4D); // LOG SENSE
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push((1<<6) | 0x17); // Volume Statistics log page
 | 
					    cmd.push((1 << 6) | 0x17); // Volume Statistics log page
 | 
				
			||||||
    cmd.push(0); // Subpage 0
 | 
					    cmd.push(0); // Subpage 0
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
    cmd.push(0);
 | 
					    cmd.push(0);
 | 
				
			||||||
@ -39,7 +37,8 @@ fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error>
 | 
				
			|||||||
    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
 | 
					    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
 | 
				
			||||||
    cmd.push(0u8); // control byte
 | 
					    cmd.push(0u8); // control byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sg_raw.do_command(&cmd)
 | 
					    sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("read tape volume statistics failed - {}", err))
 | 
					        .map_err(|err| format_err!("read tape volume statistics failed - {}", err))
 | 
				
			||||||
        .map(|v| v.to_vec())
 | 
					        .map(|v| v.to_vec())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -53,8 +52,6 @@ struct LpParameterHeader {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> {
 | 
					fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let read_be_counter = |reader: &mut &[u8], len: u8| {
 | 
					    let read_be_counter = |reader: &mut &[u8], len: u8| {
 | 
				
			||||||
        let len = len as usize;
 | 
					        let len = len as usize;
 | 
				
			||||||
        if len == 0 || len > 8 {
 | 
					        if len == 0 || len > 8 {
 | 
				
			||||||
@ -86,7 +83,7 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
 | 
				
			|||||||
            bail!("invalid page length");
 | 
					            bail!("invalid page length");
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // Note: Quantum hh7 returns the allocation_length instead of real data_len
 | 
					            // Note: Quantum hh7 returns the allocation_length instead of real data_len
 | 
				
			||||||
            reader = &data[4..page_len+4];
 | 
					            reader = &data[4..page_len + 4];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut stat = Lp17VolumeStatistics::default();
 | 
					        let mut stat = Lp17VolumeStatistics::default();
 | 
				
			||||||
@ -107,8 +104,7 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
 | 
				
			|||||||
                    page_valid = true;
 | 
					                    page_valid = true;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                0x0001 => {
 | 
					                0x0001 => {
 | 
				
			||||||
                    stat.volume_mounts =
 | 
					                    stat.volume_mounts = read_be_counter(&mut reader, head.parameter_len)?;
 | 
				
			||||||
                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                0x0002 => {
 | 
					                0x0002 => {
 | 
				
			||||||
                    stat.volume_datasets_written =
 | 
					                    stat.volume_datasets_written =
 | 
				
			||||||
@ -131,8 +127,7 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
 | 
				
			|||||||
                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
					                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                0x0007 => {
 | 
					                0x0007 => {
 | 
				
			||||||
                    stat.volume_datasets_read =
 | 
					                    stat.volume_datasets_read = read_be_counter(&mut reader, head.parameter_len)?;
 | 
				
			||||||
                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                0x0008 => {
 | 
					                0x0008 => {
 | 
				
			||||||
                    stat.volume_recovered_read_errors =
 | 
					                    stat.volume_recovered_read_errors =
 | 
				
			||||||
@ -175,12 +170,10 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
 | 
				
			|||||||
                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
					                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                0x0014 => {
 | 
					                0x0014 => {
 | 
				
			||||||
                    stat.medium_mount_time =
 | 
					                    stat.medium_mount_time = read_be_counter(&mut reader, head.parameter_len)?;
 | 
				
			||||||
                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                0x0015 => {
 | 
					                0x0015 => {
 | 
				
			||||||
                    stat.medium_ready_time =
 | 
					                    stat.medium_ready_time = read_be_counter(&mut reader, head.parameter_len)?;
 | 
				
			||||||
                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                0x0016 => {
 | 
					                0x0016 => {
 | 
				
			||||||
                    stat.total_native_capacity =
 | 
					                    stat.total_native_capacity =
 | 
				
			||||||
@ -211,8 +204,7 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
 | 
				
			|||||||
                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
					                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                0x0102 => {
 | 
					                0x0102 => {
 | 
				
			||||||
                   stat.middle_of_tape_passes =
 | 
					                    stat.middle_of_tape_passes = read_be_counter(&mut reader, head.parameter_len)?;
 | 
				
			||||||
                        read_be_counter(&mut reader, head.parameter_len)?;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                _ => {
 | 
					                _ => {
 | 
				
			||||||
                    reader.read_exact_allocated(head.parameter_len as usize)?;
 | 
					                    reader.read_exact_allocated(head.parameter_len as usize)?;
 | 
				
			||||||
@ -225,6 +217,6 @@ fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error>
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(stat)
 | 
					        Ok(stat)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
    }).map_err(|err| format_err!("decode volume statistics failed - {}", err))
 | 
					    .map_err(|err| format_err!("decode volume statistics failed - {}", err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -25,9 +25,7 @@ pub struct SenseInfo {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl std::fmt::Display for SenseInfo {
 | 
					impl std::fmt::Display for SenseInfo {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let sense_text = SENSE_KEY_DESCRIPTIONS
 | 
					        let sense_text = SENSE_KEY_DESCRIPTIONS
 | 
				
			||||||
            .get(self.sense_key as usize)
 | 
					            .get(self.sense_key as usize)
 | 
				
			||||||
            .map(|s| String::from(*s))
 | 
					            .map(|s| String::from(*s))
 | 
				
			||||||
@ -58,7 +56,9 @@ impl From<std::io::Error> for ScsiError {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Opaque wrapper for sg_pt_base
 | 
					// Opaque wrapper for sg_pt_base
 | 
				
			||||||
#[repr(C)]
 | 
					#[repr(C)]
 | 
				
			||||||
struct SgPtBase { _private: [u8; 0] }
 | 
					struct SgPtBase {
 | 
				
			||||||
 | 
					    _private: [u8; 0],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[repr(transparent)]
 | 
					#[repr(transparent)]
 | 
				
			||||||
struct SgPt {
 | 
					struct SgPt {
 | 
				
			||||||
@ -184,9 +184,9 @@ pub struct RequestSenseFixed {
 | 
				
			|||||||
    pub response_code: u8,
 | 
					    pub response_code: u8,
 | 
				
			||||||
    obsolete: u8,
 | 
					    obsolete: u8,
 | 
				
			||||||
    pub flags2: u8,
 | 
					    pub flags2: u8,
 | 
				
			||||||
    pub information: [u8;4],
 | 
					    pub information: [u8; 4],
 | 
				
			||||||
    pub additional_sense_len: u8,
 | 
					    pub additional_sense_len: u8,
 | 
				
			||||||
    pub command_specific_information: [u8;4],
 | 
					    pub command_specific_information: [u8; 4],
 | 
				
			||||||
    pub additional_sense_code: u8,
 | 
					    pub additional_sense_code: u8,
 | 
				
			||||||
    pub additional_sense_code_qualifier: u8,
 | 
					    pub additional_sense_code_qualifier: u8,
 | 
				
			||||||
    pub field_replacable_unit_code: u8,
 | 
					    pub field_replacable_unit_code: u8,
 | 
				
			||||||
@ -195,12 +195,12 @@ pub struct RequestSenseFixed {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[repr(C, packed)]
 | 
					#[repr(C, packed)]
 | 
				
			||||||
#[derive(Endian, Debug)]
 | 
					#[derive(Endian, Debug)]
 | 
				
			||||||
struct RequestSenseDescriptor{
 | 
					struct RequestSenseDescriptor {
 | 
				
			||||||
    response_code: u8,
 | 
					    response_code: u8,
 | 
				
			||||||
    sense_key: u8,
 | 
					    sense_key: u8,
 | 
				
			||||||
    additional_sense_code: u8,
 | 
					    additional_sense_code: u8,
 | 
				
			||||||
    additional_sense_code_qualifier: u8,
 | 
					    additional_sense_code_qualifier: u8,
 | 
				
			||||||
    reserved: [u8;4],
 | 
					    reserved: [u8; 4],
 | 
				
			||||||
    additional_sense_len: u8,
 | 
					    additional_sense_len: u8,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -228,12 +228,11 @@ pub struct ModeParameterHeader {
 | 
				
			|||||||
    // not compatible with IBM.
 | 
					    // not compatible with IBM.
 | 
				
			||||||
    pub medium_type: u8,
 | 
					    pub medium_type: u8,
 | 
				
			||||||
    pub flags3: u8,
 | 
					    pub flags3: u8,
 | 
				
			||||||
    reserved4: [u8;2],
 | 
					    reserved4: [u8; 2],
 | 
				
			||||||
    pub block_descriptior_len: u16,
 | 
					    pub block_descriptior_len: u16,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl ModeParameterHeader {
 | 
					impl ModeParameterHeader {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn buffer_mode(&self) -> u8 {
 | 
					    pub fn buffer_mode(&self) -> u8 {
 | 
				
			||||||
        (self.flags3 & 0b0111_0000) >> 4
 | 
					        (self.flags3 & 0b0111_0000) >> 4
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -256,18 +255,16 @@ impl ModeParameterHeader {
 | 
				
			|||||||
/// SCSI ModeBlockDescriptor for Tape devices
 | 
					/// SCSI ModeBlockDescriptor for Tape devices
 | 
				
			||||||
pub struct ModeBlockDescriptor {
 | 
					pub struct ModeBlockDescriptor {
 | 
				
			||||||
    pub density_code: u8,
 | 
					    pub density_code: u8,
 | 
				
			||||||
    pub number_of_blocks: [u8;3],
 | 
					    pub number_of_blocks: [u8; 3],
 | 
				
			||||||
    reserverd: u8,
 | 
					    reserverd: u8,
 | 
				
			||||||
    pub block_length: [u8; 3],
 | 
					    pub block_length: [u8; 3],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl ModeBlockDescriptor {
 | 
					impl ModeBlockDescriptor {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn block_length(&self) -> u32 {
 | 
					    pub fn block_length(&self) -> u32 {
 | 
				
			||||||
        ((self.block_length[0] as u32) << 16) +
 | 
					        ((self.block_length[0] as u32) << 16)
 | 
				
			||||||
            ((self.block_length[1] as u32) << 8) +
 | 
					            + ((self.block_length[1] as u32) << 8)
 | 
				
			||||||
            (self.block_length[2] as u32)
 | 
					            + (self.block_length[2] as u32)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn set_block_length(&mut self, length: u32) -> Result<(), Error> {
 | 
					    pub fn set_block_length(&mut self, length: u32) -> Result<(), Error> {
 | 
				
			||||||
@ -281,64 +278,36 @@ impl ModeBlockDescriptor {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const SCSI_PT_DO_START_OK:c_int = 0;
 | 
					pub const SCSI_PT_DO_START_OK: c_int = 0;
 | 
				
			||||||
pub const SCSI_PT_DO_BAD_PARAMS:c_int = 1;
 | 
					pub const SCSI_PT_DO_BAD_PARAMS: c_int = 1;
 | 
				
			||||||
pub const SCSI_PT_DO_TIMEOUT:c_int = 2;
 | 
					pub const SCSI_PT_DO_TIMEOUT: c_int = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const SCSI_PT_RESULT_GOOD:c_int = 0;
 | 
					pub const SCSI_PT_RESULT_GOOD: c_int = 0;
 | 
				
			||||||
pub const SCSI_PT_RESULT_STATUS:c_int = 1;
 | 
					pub const SCSI_PT_RESULT_STATUS: c_int = 1;
 | 
				
			||||||
pub const SCSI_PT_RESULT_SENSE:c_int = 2;
 | 
					pub const SCSI_PT_RESULT_SENSE: c_int = 2;
 | 
				
			||||||
pub const SCSI_PT_RESULT_TRANSPORT_ERR:c_int = 3;
 | 
					pub const SCSI_PT_RESULT_TRANSPORT_ERR: c_int = 3;
 | 
				
			||||||
pub const SCSI_PT_RESULT_OS_ERR:c_int = 4;
 | 
					pub const SCSI_PT_RESULT_OS_ERR: c_int = 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[link(name = "sgutils2")]
 | 
					#[link(name = "sgutils2")]
 | 
				
			||||||
extern "C" {
 | 
					extern "C" {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[allow(dead_code)]
 | 
					    #[allow(dead_code)]
 | 
				
			||||||
    fn scsi_pt_open_device(
 | 
					    fn scsi_pt_open_device(device_name: *const c_char, read_only: bool, verbose: c_int) -> c_int;
 | 
				
			||||||
        device_name: * const c_char,
 | 
					 | 
				
			||||||
        read_only: bool,
 | 
					 | 
				
			||||||
        verbose: c_int,
 | 
					 | 
				
			||||||
    ) -> c_int;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn sg_is_scsi_cdb(
 | 
					    fn sg_is_scsi_cdb(cdbp: *const u8, clen: c_int) -> bool;
 | 
				
			||||||
        cdbp: *const u8,
 | 
					 | 
				
			||||||
        clen: c_int,
 | 
					 | 
				
			||||||
    ) -> bool;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn construct_scsi_pt_obj() -> *mut SgPtBase;
 | 
					    fn construct_scsi_pt_obj() -> *mut SgPtBase;
 | 
				
			||||||
    fn destruct_scsi_pt_obj(objp: *mut SgPtBase);
 | 
					    fn destruct_scsi_pt_obj(objp: *mut SgPtBase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn set_scsi_pt_data_in(
 | 
					    fn set_scsi_pt_data_in(objp: *mut SgPtBase, dxferp: *mut u8, dxfer_ilen: c_int);
 | 
				
			||||||
        objp: *mut SgPtBase,
 | 
					 | 
				
			||||||
        dxferp: *mut u8,
 | 
					 | 
				
			||||||
        dxfer_ilen: c_int,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn set_scsi_pt_data_out(
 | 
					    fn set_scsi_pt_data_out(objp: *mut SgPtBase, dxferp: *const u8, dxfer_olen: c_int);
 | 
				
			||||||
        objp: *mut SgPtBase,
 | 
					 | 
				
			||||||
        dxferp: *const u8,
 | 
					 | 
				
			||||||
        dxfer_olen: c_int,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn set_scsi_pt_cdb(
 | 
					    fn set_scsi_pt_cdb(objp: *mut SgPtBase, cdb: *const u8, cdb_len: c_int);
 | 
				
			||||||
        objp: *mut SgPtBase,
 | 
					 | 
				
			||||||
        cdb: *const u8,
 | 
					 | 
				
			||||||
        cdb_len: c_int,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn set_scsi_pt_sense(
 | 
					    fn set_scsi_pt_sense(objp: *mut SgPtBase, sense: *mut u8, max_sense_len: c_int);
 | 
				
			||||||
        objp: *mut SgPtBase,
 | 
					 | 
				
			||||||
        sense: *mut u8,
 | 
					 | 
				
			||||||
        max_sense_len: c_int,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn do_scsi_pt(
 | 
					    fn do_scsi_pt(objp: *mut SgPtBase, fd: c_int, timeout_secs: c_int, verbose: c_int) -> c_int;
 | 
				
			||||||
        objp: *mut SgPtBase,
 | 
					 | 
				
			||||||
        fd: c_int,
 | 
					 | 
				
			||||||
        timeout_secs: c_int,
 | 
					 | 
				
			||||||
        verbose: c_int,
 | 
					 | 
				
			||||||
    ) -> c_int;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn get_scsi_pt_resid(objp: *const SgPtBase) -> c_int;
 | 
					    fn get_scsi_pt_resid(objp: *const SgPtBase) -> c_int;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -352,10 +321,10 @@ extern "C" {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    fn sg_get_asc_ascq_str(
 | 
					    fn sg_get_asc_ascq_str(
 | 
				
			||||||
        asc: c_int,
 | 
					        asc: c_int,
 | 
				
			||||||
        ascq:c_int,
 | 
					        ascq: c_int,
 | 
				
			||||||
        buff_len: c_int,
 | 
					        buff_len: c_int,
 | 
				
			||||||
        buffer: *mut c_char,
 | 
					        buffer: *mut c_char,
 | 
				
			||||||
    ) -> * const c_char;
 | 
					    ) -> *const c_char;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Safe interface to run RAW SCSI commands
 | 
					/// Safe interface to run RAW SCSI commands
 | 
				
			||||||
@ -368,31 +337,30 @@ pub struct SgRaw<'a, F> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Get the string associated with ASC/ASCQ values
 | 
					/// Get the string associated with ASC/ASCQ values
 | 
				
			||||||
pub fn get_asc_ascq_string(asc: u8, ascq: u8) -> String {
 | 
					pub fn get_asc_ascq_string(asc: u8, ascq: u8) -> String {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut buffer = [0u8; 1024];
 | 
					    let mut buffer = [0u8; 1024];
 | 
				
			||||||
    let res = unsafe {
 | 
					    let res = unsafe {
 | 
				
			||||||
        sg_get_asc_ascq_str(
 | 
					        sg_get_asc_ascq_str(
 | 
				
			||||||
            asc as c_int,
 | 
					            asc as c_int,
 | 
				
			||||||
            ascq as c_int,
 | 
					            ascq as c_int,
 | 
				
			||||||
            buffer.len() as c_int,
 | 
					            buffer.len() as c_int,
 | 
				
			||||||
            buffer.as_mut_ptr() as * mut c_char,
 | 
					            buffer.as_mut_ptr() as *mut c_char,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    proxmox_lang::try_block!({
 | 
					    proxmox_lang::try_block!({
 | 
				
			||||||
        if res.is_null() { // just to be safe
 | 
					        if res.is_null() {
 | 
				
			||||||
 | 
					            // just to be safe
 | 
				
			||||||
            bail!("unexpected NULL ptr");
 | 
					            bail!("unexpected NULL ptr");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Ok(unsafe { CStr::from_ptr(res) }.to_str()?.to_owned())
 | 
					        Ok(unsafe { CStr::from_ptr(res) }.to_str()?.to_owned())
 | 
				
			||||||
    }).unwrap_or_else(|_err: Error| {
 | 
					 | 
				
			||||||
        format!("ASC={:02x}x, ASCQ={:02x}x", asc, ascq)
 | 
					 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					    .unwrap_or_else(|_err: Error| format!("ASC={:02x}x, ASCQ={:02x}x", asc, ascq))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Allocate a page aligned buffer
 | 
					/// Allocate a page aligned buffer
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// SG RAWIO commands needs page aligned transfer buffers.
 | 
					/// SG RAWIO commands needs page aligned transfer buffers.
 | 
				
			||||||
pub fn alloc_page_aligned_buffer(buffer_size: usize) -> Result<Box<[u8]> , Error> {
 | 
					pub fn alloc_page_aligned_buffer(buffer_size: usize) -> Result<Box<[u8]>, Error> {
 | 
				
			||||||
    let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
 | 
					    let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
 | 
				
			||||||
    let layout = std::alloc::Layout::from_size_align(buffer_size, page_size)?;
 | 
					    let layout = std::alloc::Layout::from_size_align(buffer_size, page_size)?;
 | 
				
			||||||
    let dinp = unsafe { std::alloc::alloc_zeroed(layout) };
 | 
					    let dinp = unsafe { std::alloc::alloc_zeroed(layout) };
 | 
				
			||||||
@ -400,17 +368,15 @@ pub fn alloc_page_aligned_buffer(buffer_size: usize) -> Result<Box<[u8]> , Error
 | 
				
			|||||||
        bail!("alloc SCSI output buffer failed");
 | 
					        bail!("alloc SCSI output buffer failed");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let buffer_slice = unsafe { std::slice::from_raw_parts_mut(dinp, buffer_size)};
 | 
					    let buffer_slice = unsafe { std::slice::from_raw_parts_mut(dinp, buffer_size) };
 | 
				
			||||||
    Ok(unsafe { Box::from_raw(buffer_slice) })
 | 
					    Ok(unsafe { Box::from_raw(buffer_slice) })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
					impl<'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Create a new instance to run commands
 | 
					    /// Create a new instance to run commands
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// The file must be a handle to a SCSI device.
 | 
					    /// The file must be a handle to a SCSI device.
 | 
				
			||||||
    pub fn new(file: &'a mut F, buffer_size: usize) -> Result<Self, Error> {
 | 
					    pub fn new(file: &'a mut F, buffer_size: usize) -> Result<Self, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let buffer;
 | 
					        let buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if buffer_size > 0 {
 | 
					        if buffer_size > 0 {
 | 
				
			||||||
@ -421,7 +387,12 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let sense_buffer = [0u8; 32];
 | 
					        let sense_buffer = [0u8; 32];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(Self { file, buffer, sense_buffer, timeout: 0 })
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            file,
 | 
				
			||||||
 | 
					            buffer,
 | 
				
			||||||
 | 
					            sense_buffer,
 | 
				
			||||||
 | 
					            timeout: 0,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Set the command timeout in seconds (0 means default (60 seconds))
 | 
					    /// Set the command timeout in seconds (0 means default (60 seconds))
 | 
				
			||||||
@ -435,7 +406,6 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // create new object with initialized data_in and sense buffer
 | 
					    // create new object with initialized data_in and sense buffer
 | 
				
			||||||
    fn create_scsi_pt_obj(&mut self) -> Result<SgPt, Error> {
 | 
					    fn create_scsi_pt_obj(&mut self) -> Result<SgPt, Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut ptvp = SgPt::new()?;
 | 
					        let mut ptvp = SgPt::new()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if !self.buffer.is_empty() {
 | 
					        if !self.buffer.is_empty() {
 | 
				
			||||||
@ -460,18 +430,21 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn do_scsi_pt_checked(&mut self, ptvp: &mut SgPt) -> Result<(), ScsiError> {
 | 
					    fn do_scsi_pt_checked(&mut self, ptvp: &mut SgPt) -> Result<(), ScsiError> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let res = unsafe { do_scsi_pt(ptvp.as_mut_ptr(), self.file.as_raw_fd(), self.timeout, 0) };
 | 
					        let res = unsafe { do_scsi_pt(ptvp.as_mut_ptr(), self.file.as_raw_fd(), self.timeout, 0) };
 | 
				
			||||||
        match res {
 | 
					        match res {
 | 
				
			||||||
            SCSI_PT_DO_START_OK => { /* Ok */ },
 | 
					            SCSI_PT_DO_START_OK => { /* Ok */ }
 | 
				
			||||||
            SCSI_PT_DO_BAD_PARAMS => return Err(format_err!("do_scsi_pt failed - bad pass through setup").into()),
 | 
					            SCSI_PT_DO_BAD_PARAMS => {
 | 
				
			||||||
 | 
					                return Err(format_err!("do_scsi_pt failed - bad pass through setup").into())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            SCSI_PT_DO_TIMEOUT => return Err(format_err!("do_scsi_pt failed - timeout").into()),
 | 
					            SCSI_PT_DO_TIMEOUT => return Err(format_err!("do_scsi_pt failed - timeout").into()),
 | 
				
			||||||
            code if code < 0 => {
 | 
					            code if code < 0 => {
 | 
				
			||||||
                let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) };
 | 
					                let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) };
 | 
				
			||||||
                let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno));
 | 
					                let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno));
 | 
				
			||||||
                return Err(format_err!("do_scsi_pt failed with err {}", err).into());
 | 
					                return Err(format_err!("do_scsi_pt failed with err {}", err).into());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            unknown => return Err(format_err!("do_scsi_pt failed: unknown error {}", unknown).into()),
 | 
					            unknown => {
 | 
				
			||||||
 | 
					                return Err(format_err!("do_scsi_pt failed: unknown error {}", unknown).into())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if res < 0 {
 | 
					        if res < 0 {
 | 
				
			||||||
@ -490,7 +463,9 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
            SCSI_PT_RESULT_STATUS => {
 | 
					            SCSI_PT_RESULT_STATUS => {
 | 
				
			||||||
                let status = unsafe { get_scsi_pt_status_response(ptvp.as_ptr()) };
 | 
					                let status = unsafe { get_scsi_pt_status_response(ptvp.as_ptr()) };
 | 
				
			||||||
                if status != 0 {
 | 
					                if status != 0 {
 | 
				
			||||||
                    return Err(format_err!("unknown scsi error - status response {}", status).into());
 | 
					                    return Err(
 | 
				
			||||||
 | 
					                        format_err!("unknown scsi error - status response {}", status).into(),
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                Ok(())
 | 
					                Ok(())
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -521,28 +496,39 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    0x71 | 0x73 => {
 | 
					                    0x71 | 0x73 => {
 | 
				
			||||||
                        return Err(format_err!("scsi command failed: received deferred Sense").into());
 | 
					                        return Err(
 | 
				
			||||||
 | 
					                            format_err!("scsi command failed: received deferred Sense").into()
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    unknown => {
 | 
					                    unknown => {
 | 
				
			||||||
                        return Err(format_err!("scsi command failed: invalid Sense response code {:x}", unknown).into());
 | 
					                        return Err(format_err!(
 | 
				
			||||||
 | 
					                            "scsi command failed: invalid Sense response code {:x}",
 | 
				
			||||||
 | 
					                            unknown
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .into());
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Err(ScsiError::Sense(sense))
 | 
					                Err(ScsiError::Sense(sense))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            SCSI_PT_RESULT_TRANSPORT_ERR => return Err(format_err!("scsi command failed: transport error").into()),
 | 
					            SCSI_PT_RESULT_TRANSPORT_ERR => {
 | 
				
			||||||
 | 
					                return Err(format_err!("scsi command failed: transport error").into())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            SCSI_PT_RESULT_OS_ERR => {
 | 
					            SCSI_PT_RESULT_OS_ERR => {
 | 
				
			||||||
                let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) };
 | 
					                let errno = unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) };
 | 
				
			||||||
                let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno));
 | 
					                let err = nix::Error::from_errno(nix::errno::Errno::from_i32(errno));
 | 
				
			||||||
                return Err(format_err!("scsi command failed with err {}", err).into());
 | 
					                return Err(format_err!("scsi command failed with err {}", err).into());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            unknown => return Err(format_err!("scsi command failed: unknown result category {}", unknown).into()),
 | 
					            unknown => {
 | 
				
			||||||
 | 
					                return Err(
 | 
				
			||||||
 | 
					                    format_err!("scsi command failed: unknown result category {}", unknown).into(),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Run the specified RAW SCSI command
 | 
					    /// Run the specified RAW SCSI command
 | 
				
			||||||
    pub fn do_command(&mut self, cmd: &[u8]) -> Result<&[u8], ScsiError> {
 | 
					    pub fn do_command(&mut self, cmd: &[u8]) -> Result<&[u8], ScsiError> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
 | 
					        if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
 | 
				
			||||||
            return Err(format_err!("no valid SCSI command").into());
 | 
					            return Err(format_err!("no valid SCSI command").into());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -553,19 +539,15 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let mut ptvp = self.create_scsi_pt_obj()?;
 | 
					        let mut ptvp = self.create_scsi_pt_obj()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        unsafe {
 | 
					        unsafe { set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int) };
 | 
				
			||||||
            set_scsi_pt_cdb(
 | 
					 | 
				
			||||||
                ptvp.as_mut_ptr(),
 | 
					 | 
				
			||||||
                cmd.as_ptr(),
 | 
					 | 
				
			||||||
                cmd.len() as c_int,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.do_scsi_pt_checked(&mut ptvp)?;
 | 
					        self.do_scsi_pt_checked(&mut ptvp)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize;
 | 
					        let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize;
 | 
				
			||||||
        if resid > self.buffer.len() {
 | 
					        if resid > self.buffer.len() {
 | 
				
			||||||
            return Err(format_err!("do_scsi_pt failed - got strange resid (value too big)").into());
 | 
					            return Err(
 | 
				
			||||||
 | 
					                format_err!("do_scsi_pt failed - got strange resid (value too big)").into(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let data_len = self.buffer.len() - resid;
 | 
					        let data_len = self.buffer.len() - resid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -573,8 +555,11 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Run the specified RAW SCSI command, use data as input buffer
 | 
					    /// Run the specified RAW SCSI command, use data as input buffer
 | 
				
			||||||
    pub fn do_in_command<'b>(&mut self, cmd: &[u8], data: &'b mut [u8]) -> Result<&'b [u8], ScsiError> {
 | 
					    pub fn do_in_command<'b>(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        cmd: &[u8],
 | 
				
			||||||
 | 
					        data: &'b mut [u8],
 | 
				
			||||||
 | 
					    ) -> Result<&'b [u8], ScsiError> {
 | 
				
			||||||
        if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
 | 
					        if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
 | 
				
			||||||
            return Err(format_err!("no valid SCSI command").into());
 | 
					            return Err(format_err!("no valid SCSI command").into());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -586,17 +571,9 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
        let mut ptvp = self.create_scsi_pt_obj()?;
 | 
					        let mut ptvp = self.create_scsi_pt_obj()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        unsafe {
 | 
					        unsafe {
 | 
				
			||||||
            set_scsi_pt_data_in(
 | 
					            set_scsi_pt_data_in(ptvp.as_mut_ptr(), data.as_mut_ptr(), data.len() as c_int);
 | 
				
			||||||
                ptvp.as_mut_ptr(),
 | 
					 | 
				
			||||||
                data.as_mut_ptr(),
 | 
					 | 
				
			||||||
                data.len() as c_int,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            set_scsi_pt_cdb(
 | 
					            set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int);
 | 
				
			||||||
                ptvp.as_mut_ptr(),
 | 
					 | 
				
			||||||
                cmd.as_ptr(),
 | 
					 | 
				
			||||||
                cmd.len() as c_int,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.do_scsi_pt_checked(&mut ptvp)?;
 | 
					        self.do_scsi_pt_checked(&mut ptvp)?;
 | 
				
			||||||
@ -604,7 +581,9 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
        let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize;
 | 
					        let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if resid > data.len() {
 | 
					        if resid > data.len() {
 | 
				
			||||||
            return Err(format_err!("do_scsi_pt failed - got strange resid (value too big)").into());
 | 
					            return Err(
 | 
				
			||||||
 | 
					                format_err!("do_scsi_pt failed - got strange resid (value too big)").into(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let data_len = data.len() - resid;
 | 
					        let data_len = data.len() - resid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -615,30 +594,21 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
 | 
				
			|||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Note: use alloc_page_aligned_buffer to alloc data transfer buffer
 | 
					    /// Note: use alloc_page_aligned_buffer to alloc data transfer buffer
 | 
				
			||||||
    pub fn do_out_command(&mut self, cmd: &[u8], data: &[u8]) -> Result<(), ScsiError> {
 | 
					    pub fn do_out_command(&mut self, cmd: &[u8], data: &[u8]) -> Result<(), ScsiError> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
 | 
					        if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
 | 
				
			||||||
            return Err(format_err!("no valid SCSI command").into());
 | 
					            return Err(format_err!("no valid SCSI command").into());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
 | 
					        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
 | 
				
			||||||
        if ((data.as_ptr() as usize) & (page_size -1)) != 0 {
 | 
					        if ((data.as_ptr() as usize) & (page_size - 1)) != 0 {
 | 
				
			||||||
            return Err(format_err!("wrong transfer buffer alignment").into());
 | 
					            return Err(format_err!("wrong transfer buffer alignment").into());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut ptvp = self.create_scsi_pt_obj()?;
 | 
					        let mut ptvp = self.create_scsi_pt_obj()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        unsafe {
 | 
					        unsafe {
 | 
				
			||||||
            set_scsi_pt_data_out(
 | 
					            set_scsi_pt_data_out(ptvp.as_mut_ptr(), data.as_ptr(), data.len() as c_int);
 | 
				
			||||||
                ptvp.as_mut_ptr(),
 | 
					 | 
				
			||||||
                data.as_ptr(),
 | 
					 | 
				
			||||||
                data.len() as c_int,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            set_scsi_pt_cdb(
 | 
					            set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int);
 | 
				
			||||||
                ptvp.as_mut_ptr(),
 | 
					 | 
				
			||||||
                cmd.as_ptr(),
 | 
					 | 
				
			||||||
                cmd.len() as c_int,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.do_scsi_pt_checked(&mut ptvp)?;
 | 
					        self.do_scsi_pt_checked(&mut ptvp)?;
 | 
				
			||||||
@ -660,10 +630,7 @@ pub fn scsi_ascii_to_string(data: &[u8]) -> String {
 | 
				
			|||||||
/// Read SCSI Inquiry page
 | 
					/// Read SCSI Inquiry page
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// Returns Product/Vendor/Revision and device type.
 | 
					/// Returns Product/Vendor/Revision and device type.
 | 
				
			||||||
pub fn scsi_inquiry<F: AsRawFd>(
 | 
					pub fn scsi_inquiry<F: AsRawFd>(file: &mut F) -> Result<InquiryInfo, Error> {
 | 
				
			||||||
    file: &mut F,
 | 
					 | 
				
			||||||
) -> Result<InquiryInfo, Error> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let allocation_len: u8 = std::mem::size_of::<InquiryPage>() as u8;
 | 
					    let allocation_len: u8 = std::mem::size_of::<InquiryPage>() as u8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
					    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
				
			||||||
@ -672,7 +639,8 @@ pub fn scsi_inquiry<F: AsRawFd>(
 | 
				
			|||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.extend(&[0x12, 0, 0, 0, allocation_len, 0]); // INQUIRY
 | 
					    cmd.extend(&[0x12, 0, 0, 0, allocation_len, 0]); // INQUIRY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let data = sg_raw.do_command(&cmd)
 | 
					    let data = sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("SCSI inquiry failed - {}", err))?;
 | 
					        .map_err(|err| format_err!("SCSI inquiry failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    proxmox_lang::try_block!({
 | 
					    proxmox_lang::try_block!({
 | 
				
			||||||
@ -691,7 +659,8 @@ pub fn scsi_inquiry<F: AsRawFd>(
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(info)
 | 
					        Ok(info)
 | 
				
			||||||
    }).map_err(|err: Error| format_err!("decode inquiry page failed - {}", err))
 | 
					    })
 | 
				
			||||||
 | 
					    .map_err(|err: Error| format_err!("decode inquiry page failed - {}", err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Run SCSI Mode Sense
 | 
					/// Run SCSI Mode Sense
 | 
				
			||||||
@ -703,7 +672,6 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
 | 
				
			|||||||
    page_code: u8,
 | 
					    page_code: u8,
 | 
				
			||||||
    sub_page_code: u8,
 | 
					    sub_page_code: u8,
 | 
				
			||||||
) -> Result<(ModeParameterHeader, Option<ModeBlockDescriptor>, P), Error> {
 | 
					) -> Result<(ModeParameterHeader, Option<ModeBlockDescriptor>, P), Error> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let allocation_len: u16 = 4096;
 | 
					    let allocation_len: u16 = 4096;
 | 
				
			||||||
    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
					    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -721,7 +689,8 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
 | 
				
			|||||||
    cmd.extend(&allocation_len.to_be_bytes()); // allocation len
 | 
					    cmd.extend(&allocation_len.to_be_bytes()); // allocation len
 | 
				
			||||||
    cmd.push(0); //control
 | 
					    cmd.push(0); //control
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let data = sg_raw.do_command(&cmd)
 | 
					    let data = sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("mode sense failed - {}", err))?;
 | 
					        .map_err(|err| format_err!("mode sense failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    proxmox_lang::try_block!({
 | 
					    proxmox_lang::try_block!({
 | 
				
			||||||
@ -731,7 +700,11 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
 | 
				
			|||||||
        let expected_len = head.mode_data_len as usize + 2;
 | 
					        let expected_len = head.mode_data_len as usize + 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if data.len() < expected_len {
 | 
					        if data.len() < expected_len {
 | 
				
			||||||
            bail!("wrong mode_data_len: got {}, expected {}", data.len(), expected_len);
 | 
					            bail!(
 | 
				
			||||||
 | 
					                "wrong mode_data_len: got {}, expected {}",
 | 
				
			||||||
 | 
					                data.len(),
 | 
				
			||||||
 | 
					                expected_len
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        } else if data.len() > expected_len {
 | 
					        } else if data.len() > expected_len {
 | 
				
			||||||
            // Note: Some hh7 drives returns the allocation_length
 | 
					            // Note: Some hh7 drives returns the allocation_length
 | 
				
			||||||
            // instead of real data_len
 | 
					            // instead of real data_len
 | 
				
			||||||
@ -758,14 +731,12 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
 | 
				
			|||||||
        let page: P = unsafe { reader.read_be_value()? };
 | 
					        let page: P = unsafe { reader.read_be_value()? };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok((head, block_descriptor, page))
 | 
					        Ok((head, block_descriptor, page))
 | 
				
			||||||
    }).map_err(|err: Error| format_err!("decode mode sense failed - {}", err))
 | 
					    })
 | 
				
			||||||
 | 
					    .map_err(|err: Error| format_err!("decode mode sense failed - {}", err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Resuqest Sense
 | 
					/// Resuqest Sense
 | 
				
			||||||
pub fn scsi_request_sense<F: AsRawFd>(
 | 
					pub fn scsi_request_sense<F: AsRawFd>(file: &mut F) -> Result<RequestSenseFixed, ScsiError> {
 | 
				
			||||||
    file: &mut F,
 | 
					 | 
				
			||||||
) -> Result<RequestSenseFixed, ScsiError> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // request 252 bytes, as mentioned in the Seagate SCSI reference
 | 
					    // request 252 bytes, as mentioned in the Seagate SCSI reference
 | 
				
			||||||
    let allocation_len: u8 = 252;
 | 
					    let allocation_len: u8 = 252;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -774,7 +745,8 @@ pub fn scsi_request_sense<F: AsRawFd>(
 | 
				
			|||||||
    let mut cmd = Vec::new();
 | 
					    let mut cmd = Vec::new();
 | 
				
			||||||
    cmd.extend(&[0x03, 0, 0, 0, allocation_len, 0]); // REQUEST SENSE FIXED FORMAT
 | 
					    cmd.extend(&[0x03, 0, 0, 0, allocation_len, 0]); // REQUEST SENSE FIXED FORMAT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let data = sg_raw.do_command(&cmd)
 | 
					    let data = sg_raw
 | 
				
			||||||
 | 
					        .do_command(&cmd)
 | 
				
			||||||
        .map_err(|err| format_err!("request sense failed - {}", err))?;
 | 
					        .map_err(|err| format_err!("request sense failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let sense = proxmox_lang::try_block!({
 | 
					    let sense = proxmox_lang::try_block!({
 | 
				
			||||||
@ -793,7 +765,8 @@ pub fn scsi_request_sense<F: AsRawFd>(
 | 
				
			|||||||
        let sense: RequestSenseFixed = unsafe { reader.read_be_value()? };
 | 
					        let sense: RequestSenseFixed = unsafe { reader.read_be_value()? };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(sense)
 | 
					        Ok(sense)
 | 
				
			||||||
    }).map_err(|err: Error| format_err!("decode request sense failed - {}", err))?;
 | 
					    })
 | 
				
			||||||
 | 
					    .map_err(|err: Error| format_err!("decode request sense failed - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(sense)
 | 
					    Ok(sense)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -38,12 +38,16 @@ pub trait TapeWrite {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        let header = header.to_le();
 | 
					        let header = header.to_le();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let res = self.write_all(unsafe { std::slice::from_raw_parts(
 | 
					        let res = self.write_all(unsafe {
 | 
				
			||||||
 | 
					            std::slice::from_raw_parts(
 | 
				
			||||||
                &header as *const MediaContentHeader as *const u8,
 | 
					                &header as *const MediaContentHeader as *const u8,
 | 
				
			||||||
                std::mem::size_of::<MediaContentHeader>(),
 | 
					                std::mem::size_of::<MediaContentHeader>(),
 | 
				
			||||||
        )})?;
 | 
					            )
 | 
				
			||||||
 | 
					        })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if data.is_empty() { return Ok(res); }
 | 
					        if data.is_empty() {
 | 
				
			||||||
 | 
					            return Ok(res);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.write_all(data)
 | 
					        self.write_all(data)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user