use cam::{CAMDevice, CCB, error};
use cam::bindings::*;
use std::io;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct Device {
pub(crate) dev: CAMDevice,
}
#[derive(Debug)]
pub enum Type { ATA, SCSI }
impl Device {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
Ok(Device {
dev: CAMDevice::open(path.as_ref().as_os_str())?,
})
}
pub fn get_type(&self) -> Result<Type, io::Error> {
unsafe {
let ccb: CCB = CCB::new(&self.dev);
ccb.ccb_h().func_code = xpt_opcode_XPT_PATH_INQ;
self.dev.send_ccb(&ccb)?;
if ccb.get_status() != cam_status_CAM_REQ_CMP as u32 {
Err(error::from_status(&self.dev, &ccb))?
}
Ok(match ccb.cpi().protocol {
cam_proto_PROTO_ATA => Type::ATA,
_ => Type::SCSI,
})
}
}
}
pub fn list_devices() -> Result<Vec<PathBuf>, io::Error> {
use libc::ioctl;
use std::mem;
use std::ffi::CStr;
use std::fs::OpenOptions;
use std::os::unix::io::AsRawFd;
info!("listing devices through xpt(4)");
let xpt = String::from_utf8(
XPT_DEVICE[..XPT_DEVICE.len()-1].to_vec()
).unwrap();
let xpt = OpenOptions::new()
.read(true)
.write(true)
.open(xpt)?;
const MAX_NUM_DEV: usize = 64;
let mut matches = Vec::with_capacity(MAX_NUM_DEV);
let mut ccb = unsafe {
let mut ccb = mem::zeroed::<ccb>();
ccb.ccb_h.path_id = CAM_XPT_PATH_ID;
ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
ccb.ccb_h.target_lun = CAM_LUN_WILDCARD.into();
ccb.ccb_h.func_code = xpt_opcode_XPT_DEV_MATCH;
ccb.cdm.match_buf_len = (matches.capacity() * mem::size_of::<dev_match_result>()) as u32;
ccb.cdm.matches = matches.as_mut_ptr();
ccb.cdm.num_matches = 0;
ccb.cdm.num_patterns = 0;
ccb.cdm.pattern_buf_len = 0;
ccb
};
let mut devices: Vec<Vec<(String, u32)>> = vec![];
loop {
debug!("(CAMIOCOMMAND)");
if unsafe { ioctl(xpt.as_raw_fd(), CAMIOCOMMAND, &mut ccb) == -1 } {
return Err(io::Error::last_os_error());
}
let cam_status = unsafe { ccb.ccb_h.status };
let cdm_status = unsafe { ccb.cdm.status };
let err = || Err(io::Error::new(io::ErrorKind::Other,
format!("CAM error 0x{:x}, CDM error {}\n",
cam_status,
cdm_status as u32,
)
));
if (cam_status & cam_status_CAM_STATUS_MASK as u32) != (cam_status_CAM_REQ_CMP as u32) {
return err();
}
match cdm_status {
ccb_dev_match_status_CAM_DEV_MATCH_LAST => (),
ccb_dev_match_status_CAM_DEV_MATCH_MORE => (),
_ => return err(),
}
unsafe { matches.set_len(ccb.cdm.num_matches as usize) }
let mut skip_bus = false;
let mut skip_dev = false;
for m in matches.iter() { match m.type_ {
dev_match_type_DEV_MATCH_BUS => {
let bus = unsafe { m.result.bus_result };
let bus_name = unsafe { CStr::from_ptr(bus.dev_name.as_ptr()) };
debug!("bus {:?}", bus_name);
skip_bus = match bus_name.to_str() {
Ok("xpt") => true,
Err(e) => { debug!(" failed to parse bus name: {}", e);
true
},
_ => false,
};
if skip_bus { debug!(" skip"); }
},
dev_match_type_DEV_MATCH_DEVICE => {
let dev = unsafe { m.result.device_result };
debug!(" dev flags=0x{:x}", dev.flags as usize);
devices.push(vec![]);
skip_dev = skip_bus || (dev.flags as usize & dev_result_flags_DEV_RESULT_UNCONFIGURED as usize != 0);
if skip_dev { debug!(" skip"); }
},
dev_match_type_DEV_MATCH_PERIPH => {
let pdev = unsafe { m.result.periph_result };
let pname = unsafe { CStr::from_ptr(pdev.periph_name.as_ref().as_ptr()) };
debug!(" periph {:?} {}", pname, pdev.unit_number);
if ! skip_dev {
match pname.to_str() {
Ok(pname) => match devices.last_mut() { Some(last_dev) =>
last_dev.push((pname.to_string(), pdev.unit_number)),
None => {
return Err(io::Error::new(io::ErrorKind::Other, "list_devices: peripheral device appeared before any actual device"))
},
},
Err(e) => debug!(" failed to parse pname: {}", e),
}
} else {
debug!(" skipped");
}
},
_ => panic!("Unknown match type {}", m.type_)
}
}
if cdm_status == ccb_dev_match_status_CAM_DEV_MATCH_LAST { break }
}
let devices = devices.into_iter()
.filter_map(|names| {
let mut names = names.into_iter();
names.next().map(|init|
names.fold(init, |prev, current|
if current.0 != "pass" { current }
else { prev }
)
)
})
.map(|(name, unit)| PathBuf::from(format!("/dev/{}{}", name, unit)))
.collect();
Ok(devices)
}