use crate::nvme::io::*;
use crate::nvme::ocp::{OcpSmartData, read_ocp_smart_log_fd};
use crate::nvme::types::{self, *};
use std::fs::{self, OpenOptions};
use std::os::fd::{AsFd, OwnedFd};
use std::path::Path;
#[derive(Debug)]
pub struct Device {
fd: OwnedFd,
nvme_name: String,
}
impl Device {
pub fn open(path: impl AsRef<Path>) -> Result<Device> {
let path = path.as_ref();
let file = OpenOptions::new()
.read(true)
.write(true) .open(path)?;
let nvme_name = path
.to_string_lossy()
.trim_start_matches("/dev/")
.to_string();
Ok(Device {
fd: file.into(),
nvme_name,
})
}
pub fn smart_log(&self) -> Result<NvmeSmartLog> {
let id_ctrl = read_nvme_id_ctrl_fd(self.fd.as_fd())?;
let serial_number = types::parse_ascii_field(&id_ctrl.sn);
let raw_smart = read_nvme_smart_log_fd(self.fd.as_fd())?;
Ok(NvmeSmartLog::new(
self.nvme_name.clone(),
serial_number,
&raw_smart,
))
}
pub fn identity(&self) -> Result<CtrlIdentity> {
let raw = read_nvme_id_ctrl_fd(self.fd.as_fd())?;
Ok(CtrlIdentity::new(self.nvme_name.clone(), &raw))
}
pub fn error_log(&self) -> Result<NvmeErrorLog> {
let id_ctrl = read_nvme_id_ctrl_fd(self.fd.as_fd())?;
let diag = CtrlDiagnostics::new(self.nvme_name.clone(), &id_ctrl);
let serial_number = types::parse_ascii_field(&id_ctrl.sn);
let max_entries = u16::from(diag.elpe) + 1;
let raw_entries = read_error_log_raw_fd(self.fd.as_fd(), max_entries)?;
Ok(NvmeErrorLog::new(
self.nvme_name.clone(),
serial_number,
raw_entries,
))
}
pub fn ocp_smart_log(&self) -> Result<OcpSmartData> {
let id_ctrl = read_nvme_id_ctrl_fd(self.fd.as_fd())?;
let serial_number = types::parse_ascii_field(&id_ctrl.sn);
let raw_smart_add_log = read_ocp_smart_log_fd(self.fd.as_fd())?;
Ok(OcpSmartData::new(
self.nvme_name.clone(),
serial_number,
&raw_smart_add_log,
))
}
}
#[deprecated(since = "0.3.2", note = "use Device::open(path)?.smart_log() instead")]
pub fn get_smart_log(dev_path: &str) -> Result<NvmeSmartLog> {
Device::open(dev_path)?.smart_log()
}
#[deprecated(since = "0.3.2", note = "use Device::open(path)?.error_log() instead")]
pub fn get_error_log(dev_path: &str) -> Result<NvmeErrorLog> {
Device::open(dev_path)?.error_log()
}
#[deprecated(since = "0.3.2", note = "use Device::open(path)?.identity() instead")]
pub fn get_controller_identity(dev_path: &str) -> Result<CtrlIdentity> {
Device::open(dev_path)?.identity()
}
pub fn list_nvme_controllers() -> Vec<String> {
let mut names = Vec::new();
if let Ok(entries) = fs::read_dir("/sys/class/nvme") {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().into_owned();
names.push(name);
}
}
names
}
#[deprecated(
since = "0.3.2",
note = "use Device::open(path)?.ocp_smart_log() instead"
)]
pub fn get_smart_add_log(dev_path: &str) -> Result<OcpSmartData> {
Device::open(dev_path)?.ocp_smart_log()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_nonexistent_path_returns_error() {
let err = Device::open("/dev/nvme-telem-does-not-exist-xyz").unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
}
#[test]
#[allow(deprecated)]
fn deprecated_wrappers_fail_the_same_way_as_device_open() {
let path = "/dev/nvme-telem-does-not-exist-xyz";
let device_err = Device::open(path).unwrap_err();
let smart_err = get_smart_log(path).unwrap_err();
let identity_err = get_controller_identity(path).unwrap_err();
let error_log_err = get_error_log(path).unwrap_err();
let ocp_err = get_smart_add_log(path).unwrap_err();
assert_eq!(device_err.kind(), smart_err.kind());
assert_eq!(device_err.kind(), identity_err.kind());
assert_eq!(device_err.kind(), error_log_err.kind());
assert_eq!(device_err.kind(), ocp_err.kind());
}
#[test]
fn device_name_strips_dev_prefix() {
let device = Device::open("/dev/null").expect("opening /dev/null should succeed");
assert_eq!(device.nvme_name, "null");
}
#[test]
fn drop_closes_fd_without_panicking() {
let device = Device::open("/dev/null").expect("opening /dev/null should succeed");
drop(device);
}
}