libfreemkv 0.1.0

Open source raw disc access library for optical drives
Documentation
//! SCSI/MMC command interface via Linux SG_IO.

use crate::error::{Error, Result};
use std::path::Path;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DataDirection {
    None,
    FromDevice,
    ToDevice,
}

#[derive(Debug)]
pub struct ScsiResult {
    pub status: u8,
    pub bytes_transferred: usize,
    pub sense: [u8; 32],
}

/// Low-level SCSI transport.
pub trait ScsiTransport {
    fn execute(
        &mut self,
        cdb: &[u8],
        direction: DataDirection,
        data: &mut [u8],
        timeout_ms: u32,
    ) -> Result<ScsiResult>;
}

/// Linux SG_IO transport.
pub struct SgIoTransport {
    fd: i32,
}

// SG_IO constants
const SG_IO: libc::c_ulong = 0x2285;
const SG_DXFER_NONE: i32 = -1;
const SG_DXFER_TO_DEV: i32 = -2;
const SG_DXFER_FROM_DEV: i32 = -3;

#[repr(C)]
#[allow(non_camel_case_types)]
struct sg_io_hdr {
    interface_id: i32,
    dxfer_direction: i32,
    cmd_len: u8,
    mx_sb_len: u8,
    iovec_count: u16,
    dxfer_len: u32,
    dxferp: *mut u8,
    cmdp: *const u8,
    sbp: *mut u8,
    timeout: u32,
    flags: u32,
    pack_id: i32,
    usr_ptr: *mut libc::c_void,
    status: u8,
    masked_status: u8,
    msg_status: u8,
    sb_len_wr: u8,
    host_status: u16,
    driver_status: u16,
    resid: i32,
    duration: u32,
    info: u32,
}

impl SgIoTransport {
    pub fn open(device: &Path) -> Result<Self> {
        use std::os::unix::ffi::OsStrExt;
        let path_bytes = device.as_os_str().as_bytes();
        let mut c_path = Vec::with_capacity(path_bytes.len() + 1);
        c_path.extend_from_slice(path_bytes);
        c_path.push(0);

        let fd = unsafe { libc::open(c_path.as_ptr() as *const libc::c_char, libc::O_RDWR | libc::O_NONBLOCK) };
        if fd < 0 {
            return Err(Error::DeviceNotFound(device.display().to_string()));
        }
        Ok(SgIoTransport { fd })
    }
}

impl Drop for SgIoTransport {
    fn drop(&mut self) {
        unsafe { libc::close(self.fd); }
    }
}

impl ScsiTransport for SgIoTransport {
    fn execute(
        &mut self,
        cdb: &[u8],
        direction: DataDirection,
        data: &mut [u8],
        timeout_ms: u32,
    ) -> Result<ScsiResult> {
        let mut sense = [0u8; 32];

        let dxfer_direction = match direction {
            DataDirection::None => SG_DXFER_NONE,
            DataDirection::FromDevice => SG_DXFER_FROM_DEV,
            DataDirection::ToDevice => SG_DXFER_TO_DEV,
        };

        let mut hdr: sg_io_hdr = unsafe { std::mem::zeroed() };
        hdr.interface_id = b'S' as i32;
        hdr.dxfer_direction = dxfer_direction;
        hdr.cmd_len = cdb.len() as u8;
        hdr.mx_sb_len = sense.len() as u8;
        hdr.dxfer_len = data.len() as u32;
        hdr.dxferp = data.as_mut_ptr();
        hdr.cmdp = cdb.as_ptr();
        hdr.sbp = sense.as_mut_ptr();
        hdr.timeout = timeout_ms;

        let ret = unsafe {
            libc::ioctl(self.fd, SG_IO, &mut hdr as *mut sg_io_hdr)
        };

        if ret < 0 {
            return Err(Error::Io(std::io::Error::last_os_error()));
        }

        let bytes_transferred = (data.len() as i32 - hdr.resid) as usize;

        if hdr.status != 0 {
            return Err(Error::ScsiError {
                cdb: cdb.to_vec(),
                status: hdr.status,
                sense: sense[..hdr.sb_len_wr as usize].to_vec(),
            });
        }

        Ok(ScsiResult {
            status: hdr.status,
            bytes_transferred,
            sense,
        })
    }
}

/// SCSI INQUIRY response.
#[derive(Debug, Clone)]
pub struct InquiryResult {
    pub vendor_id: String,
    pub model: String,
    pub firmware: String,
    pub raw: Vec<u8>,
}

/// Send INQUIRY command and parse the standard response fields.
pub fn inquiry(scsi: &mut dyn ScsiTransport) -> Result<InquiryResult> {
    let cdb = [0x12, 0x00, 0x00, 0x00, 0x60, 0x00];
    let mut buf = [0u8; 96];
    scsi.execute(&cdb, DataDirection::FromDevice, &mut buf, 5_000)?;

    let vendor = String::from_utf8_lossy(&buf[8..16]).trim().to_string();
    let model = String::from_utf8_lossy(&buf[16..32]).trim().to_string();
    let firmware = String::from_utf8_lossy(&buf[32..36]).trim().to_string();

    Ok(InquiryResult {
        vendor_id: vendor,
        model,
        firmware,
        raw: buf.to_vec(),
    })
}

/// Send GET CONFIGURATION for feature 0x010C (drive serial number).
pub fn get_config_010c(scsi: &mut dyn ScsiTransport) -> Result<Vec<u8>> {
    let cdb = [0x46, 0x02, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00];
    let mut buf = [0u8; 16];
    scsi.execute(&cdb, DataDirection::FromDevice, &mut buf, 5_000)?;
    Ok(buf.to_vec())
}

/// Build a READ BUFFER (0x3C) CDB with the given mode, buffer ID, offset, and length.
pub fn build_read_buffer(mode: u8, buffer_id: u8, offset: u32, length: u32) -> [u8; 10] {
    [
        0x3C,                              // READ BUFFER
        mode,
        buffer_id,
        (offset >> 16) as u8,
        (offset >> 8) as u8,
        offset as u8,
        (length >> 16) as u8,
        (length >> 8) as u8,
        length as u8,
        0x00,
    ]
}

/// Build a SET CD SPEED (0xBB) CDB with the given read speed.
pub fn build_set_cd_speed(read_speed: u16) -> [u8; 12] {
    [
        0xBB, 0x00,
        (read_speed >> 8) as u8, read_speed as u8,
        0xFF, 0xFF, // write speed = max
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ]
}

/// Build a READ(10) CDB with the raw read flag (0x08) set.
pub fn build_read10_raw(lba: u32, count: u16) -> [u8; 10] {
    [
        0x28, 0x08,                                  // READ(10), flag=0x08 (raw)
        (lba >> 24) as u8, (lba >> 16) as u8,
        (lba >> 8) as u8, lba as u8,
        0x00,
        (count >> 8) as u8, count as u8,
        0x00,
    ]
}