From dbe7e556b0382bf8e9fefa13283ea2d0af8f9fca Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Wed, 23 Dec 2020 09:44:53 +0100 Subject: [PATCH] tape: implement binding for libsgutils2 So that we can read cartridge memory without calling "sg_raw". In future, we may need further low level command to control the tape.. --- debian/control | 9 +- debian/control.in | 1 + src/api2/tape/drive.rs | 5 +- src/tape/drive/linux_tape.rs | 6 ++ src/tape/drive/mam.rs | 39 ++++---- src/tape/mod.rs | 2 + src/tape/sgutils2.rs | 179 +++++++++++++++++++++++++++++++++++ 7 files changed, 218 insertions(+), 23 deletions(-) create mode 100644 src/tape/sgutils2.rs diff --git a/debian/control b/debian/control index ed0339d5..ab0b3ac9 100644 --- a/debian/control +++ b/debian/control @@ -35,10 +35,10 @@ Build-Depends: debhelper (>= 11), librust-percent-encoding-2+default-dev (>= 2.1-~~), librust-pin-project-0.4+default-dev, librust-pin-utils-0.1+default-dev, - librust-proxmox-0.9+api-macro-dev, - librust-proxmox-0.9+default-dev, - librust-proxmox-0.9+sortable-macro-dev, - librust-proxmox-0.9+websocket-dev, + librust-proxmox-0.9+api-macro-dev (>= 0.9.1-~~), + librust-proxmox-0.9+default-dev (>= 0.9.1-~~), + librust-proxmox-0.9+sortable-macro-dev (>= 0.9.1-~~), + librust-proxmox-0.9+websocket-dev (>= 0.9.1-~~), librust-proxmox-fuse-0.1+default-dev, librust-pxar-0.6+default-dev (>= 0.6.2-~~), librust-pxar-0.6+futures-io-dev (>= 0.6.2-~~), @@ -115,6 +115,7 @@ Depends: fonts-font-awesome, smartmontools, mtx, mt-st, + libsgutils2-2, sg3-utils, ${misc:Depends}, ${shlibs:Depends}, diff --git a/debian/control.in b/debian/control.in index cd9c2bdd..0ddd1d56 100644 --- a/debian/control.in +++ b/debian/control.in @@ -14,6 +14,7 @@ Depends: fonts-font-awesome, smartmontools, mtx, mt-st, + libsgutils2-2, sg3-utils, ${misc:Depends}, ${shlibs:Depends}, diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index 2ce64ef6..275e1de6 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -48,7 +48,6 @@ use crate::{ mtx_load, mtx_unload, linux_tape_device_list, - read_mam_attributes, open_drive, media_changer, update_changer_online_status, @@ -793,8 +792,10 @@ pub fn cartridge_memory(drive: String) -> Result, Error> { let (config, _digest) = config::drive::config()?; let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; + let mut handle = drive_config.open() + .map_err(|err| format_err!("open drive '{}' ({}) failed - {}", drive, drive_config.path, err))?; - read_mam_attributes(&drive_config.path) + handle.cartridge_memory() } #[api( diff --git a/src/tape/drive/linux_tape.rs b/src/tape/drive/linux_tape.rs index 630fd9e5..7cec8840 100644 --- a/src/tape/drive/linux_tape.rs +++ b/src/tape/drive/linux_tape.rs @@ -12,10 +12,12 @@ use crate::{ api2::types::{ TapeDensity, LinuxDriveStatusFlat, + MamAttribute, }, tape::{ TapeRead, TapeWrite, + read_mam_attributes, drive::{ LinuxTapeDrive, TapeDriver, @@ -260,6 +262,10 @@ impl LinuxTapeHandle { }) } + /// Read Cartridge Memory (MAM Attributes) + pub fn cartridge_memory(&mut self) -> Result, Error> { + read_mam_attributes(&mut self.file) + } } diff --git a/src/tape/drive/mam.rs b/src/tape/drive/mam.rs index e22da38b..73142b74 100644 --- a/src/tape/drive/mam.rs +++ b/src/tape/drive/mam.rs @@ -1,12 +1,16 @@ use std::collections::HashMap; use std::convert::TryInto; +use std::os::unix::io::AsRawFd; use anyhow::{bail, format_err, Error}; use endian_trait::Endian; use proxmox::tools::io::ReadExt; -use crate::api2::types::MamAttribute; +use crate::{ + api2::types::MamAttribute, + tape::sgutils2::SgRaw, +}; // Read Medium auxiliary memory attributes (MAM) // see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1 @@ -92,28 +96,29 @@ lazy_static::lazy_static!{ }; } -fn read_tape_mam(path: &str) -> Result, Error> { +fn read_tape_mam(file: &mut F) -> Result, Error> { - let mut command = std::process::Command::new("sg_raw"); - command.args(&[ - "-r", "32k", - "-o", "-", - path, - "8c", "00", "00", "00", "00", "00", "00", "00", - "00", "00", // first attribute - "00", "00", "8f", "ff", // alloc len - "00", "00", - ]); + let mut sg_raw = SgRaw::new(file, 32*1024)?; - let output = command.output() - .map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?; + let mut cmd = Vec::new(); + cmd.extend(&[0x8c, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]); + cmd.extend(&[0u8, 0u8]); // first attribute + cmd.extend(&[0u8, 0u8, 0x8f, 0xff]); // alloc len + cmd.extend(&[0u8, 0u8]); - Ok(output.stdout) + sg_raw.do_command(&cmd) + .map_err(|err| format_err!("read cartidge memory failed - {}", err)) + .map(|v| v.to_vec()) } -pub fn read_mam_attributes(path: &str) -> Result, Error> { +pub fn read_mam_attributes(file: &mut F) -> Result, Error> { - let data = read_tape_mam(path)?; + let data = read_tape_mam(file)?; + + decode_mam_attributes(&data) +} + +fn decode_mam_attributes(data: &[u8]) -> Result, Error> { let mut reader = &data[..]; diff --git a/src/tape/mod.rs b/src/tape/mod.rs index f9309ec0..296513af 100644 --- a/src/tape/mod.rs +++ b/src/tape/mod.rs @@ -7,6 +7,8 @@ use proxmox::tools::fs::{ CreateOptions, }; +pub mod sgutils2; + pub mod file_formats; mod tape_write; diff --git a/src/tape/sgutils2.rs b/src/tape/sgutils2.rs new file mode 100644 index 00000000..30826074 --- /dev/null +++ b/src/tape/sgutils2.rs @@ -0,0 +1,179 @@ +/// Bindings for libsgutils2 +/// +/// Incomplete, but we currently do not need more. + +use std::os::unix::io::AsRawFd; + +use anyhow::{bail, Error}; +use libc::{c_char, c_int}; + +#[repr(C)] +pub struct SgPtBase { _private: [u8; 0] } + +impl Drop for SgPtBase { + fn drop(&mut self) { + unsafe { destruct_scsi_pt_obj(self as *mut SgPtBase) }; + } +} + +#[link(name = "sgutils2")] +extern { + + pub fn scsi_pt_open_device( + device_name: * const c_char, + read_only: bool, + verbose: c_int, + ) -> c_int; + + pub fn sg_is_scsi_cdb( + cdbp: *const u8, + clen: c_int, + ) -> bool; + + pub fn construct_scsi_pt_obj() -> *mut SgPtBase; + pub fn destruct_scsi_pt_obj(objp: *mut SgPtBase); + + pub fn set_scsi_pt_data_in( + objp: *mut SgPtBase, + dxferp: *const u8, + dxfer_ilen: c_int, + ); + + pub fn set_scsi_pt_cdb( + objp: *mut SgPtBase, + cdb: *const u8, + cdb_len: c_int, + ); + + pub fn set_scsi_pt_sense( + objp: *mut SgPtBase, + sense: *const u8, + max_sense_len: c_int, + ); + + pub fn do_scsi_pt( + objp: *mut SgPtBase, + fd: c_int, + timeout_secs: c_int, + verbose: c_int, + ) -> c_int; + + pub fn get_scsi_pt_resid(objp: *const SgPtBase) -> c_int; + + pub fn get_scsi_pt_sense_len(objp: *const SgPtBase) -> c_int; + + pub fn get_scsi_pt_status_response(objp: *const SgPtBase) -> c_int; +} + +/// Creates a Box +/// +/// Which get automatically dropped, so you do not need to call +/// destruct_scsi_pt_obj yourself. +pub fn boxed_scsi_pt_obj() -> Result, Error> { + let objp = unsafe { + construct_scsi_pt_obj() + }; + if objp.is_null() { + bail!("construct_scsi_pt_ob failed"); + } + + Ok(unsafe { std::mem::transmute(objp)}) +} + +/// Safe interface to run RAW SCSI commands +pub struct SgRaw<'a, F> { + file: &'a mut F, + buffer: Box<[u8]>, + sense_buffer: [u8; 32], +} + +impl <'a, F: AsRawFd> SgRaw<'a, F> { + + /// Create a new instance to run commands + /// + /// The file must be a handle to a SCSI device. + pub fn new(file: &'a mut F, buffer_size: usize) -> Result { + + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; + let layout = std::alloc::Layout::from_size_align(buffer_size, page_size)?; + let dinp = unsafe { std::alloc::alloc(layout) }; + if dinp.is_null() { + bail!("alloc SCSI output buffer failed"); + } + + let buffer = unsafe { std::slice::from_raw_parts_mut(dinp, buffer_size)}; + let buffer = unsafe { Box::from_raw(buffer) }; + + let sense_buffer = [0u8; 32]; + + Ok(Self { file, buffer, sense_buffer }) + } + + // create new object with initialized data_in and sense buffer + fn create_boxed_scsi_pt_obj(&mut self) -> Result, Error> { + + let mut ptvp = boxed_scsi_pt_obj()?; + + unsafe { + set_scsi_pt_data_in( + &mut *ptvp, + self.buffer.as_ptr(), + self.buffer.len() as c_int, + ) + }; + + unsafe { + set_scsi_pt_sense( + &mut *ptvp, + self.sense_buffer.as_ptr(), + self.sense_buffer.len() as c_int, + ) + }; + + Ok(ptvp) + } + + /// Run the specified RAW SCSI command + pub fn do_command(&mut self, cmd: &[u8]) -> Result<&[u8], Error> { + + if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } { + bail!("no valid SCSI command"); + } + + let mut ptvp = self.create_boxed_scsi_pt_obj()?; + + unsafe { + set_scsi_pt_cdb( + &mut *ptvp, + cmd.as_ptr(), + cmd.len() as c_int, + ) + }; + + let res = unsafe { do_scsi_pt(&mut *ptvp, self.file.as_raw_fd(), 0, 0) }; + if res < 0 { + let err = nix::Error::last(); + bail!("do_scsi_pt failed - {}", err); + } + if res != 0 { + bail!("do_scsi_pt failed {}", res); + } + + // todo: what about sense data? + let _sense_len = unsafe { get_scsi_pt_sense_len(&mut *ptvp) }; + + let status = unsafe { get_scsi_pt_status_response(&mut *ptvp) }; + if status != 0 { + // toto: improve error reporting + bail!("unknown scsi error - status response {}", status); + } + + let data_len = self.buffer.len() - + (unsafe { get_scsi_pt_resid(&mut *ptvp) } as usize); + if data_len <= 0 { + bail!("do_scsi_pt failed - no data received"); + } + + Ok(&self.buffer[..data_len]) + } +}