use std::ffi::CString;
use std::marker::PhantomData;
use std::ptr::NonNull;
#[cfg(has_unique_discovery_ctrl)]
use libnvme_sys::nvme_ctrl_set_unique_discovery_ctrl;
use libnvme_sys::{
nvme_create_ctrl, nvme_ctrl_set_discovery_ctrl, nvme_ctrl_set_persistent, nvme_fabrics_config,
nvmf_add_ctrl, nvmf_default_config, nvmf_disc_log_entry, nvmf_discovery_log,
nvmf_get_discovery_log,
};
use crate::error::check_ret;
use crate::host::Host;
use crate::util::cstr_to_str;
use crate::{Controller, Error, Result};
unsafe extern "C" {
fn free(ptr: *mut std::ffi::c_void);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Transport<'a> {
Tcp,
Rdma,
Fc,
Loop,
Other(&'a str),
}
impl Transport<'_> {
fn as_str(&self) -> &str {
match self {
Transport::Tcp => "tcp",
Transport::Rdma => "rdma",
Transport::Fc => "fc",
Transport::Loop => "loop",
Transport::Other(s) => s,
}
}
}
pub struct Connect<'a, 'r> {
host: &'a Host<'r>,
transport: String,
subsysnqn: String,
traddr: Option<String>,
trsvcid: Option<String>,
host_traddr: Option<String>,
host_iface: Option<String>,
queue_size: Option<i32>,
nr_io_queues: Option<i32>,
keep_alive_tmo: Option<i32>,
reconnect_delay: Option<i32>,
ctrl_loss_tmo: Option<i32>,
hdr_digest: bool,
data_digest: bool,
tls: bool,
duplicate_connect: bool,
disable_sqflow: bool,
persistent: bool,
discovery: bool,
unique_discovery: bool,
}
impl<'a, 'r> Connect<'a, 'r> {
pub(crate) fn new(host: &'a Host<'r>, transport: Transport<'_>, subsysnqn: &str) -> Self {
Connect {
host,
transport: transport.as_str().to_owned(),
subsysnqn: subsysnqn.to_owned(),
traddr: None,
trsvcid: None,
host_traddr: None,
host_iface: None,
queue_size: None,
nr_io_queues: None,
keep_alive_tmo: None,
reconnect_delay: None,
ctrl_loss_tmo: None,
hdr_digest: false,
data_digest: false,
tls: false,
duplicate_connect: false,
disable_sqflow: false,
persistent: false,
discovery: false,
unique_discovery: false,
}
}
pub fn traddr(mut self, addr: &str) -> Self {
self.traddr = Some(addr.into());
self
}
pub fn trsvcid(mut self, svcid: &str) -> Self {
self.trsvcid = Some(svcid.into());
self
}
pub fn host_traddr(mut self, addr: &str) -> Self {
self.host_traddr = Some(addr.into());
self
}
pub fn host_iface(mut self, iface: &str) -> Self {
self.host_iface = Some(iface.into());
self
}
pub fn queue_size(mut self, n: i32) -> Self {
self.queue_size = Some(n);
self
}
pub fn nr_io_queues(mut self, n: i32) -> Self {
self.nr_io_queues = Some(n);
self
}
pub fn keep_alive_tmo(mut self, seconds: i32) -> Self {
self.keep_alive_tmo = Some(seconds);
self
}
pub fn reconnect_delay(mut self, seconds: i32) -> Self {
self.reconnect_delay = Some(seconds);
self
}
pub fn ctrl_loss_tmo(mut self, seconds: i32) -> Self {
self.ctrl_loss_tmo = Some(seconds);
self
}
pub fn hdr_digest(mut self, enabled: bool) -> Self {
self.hdr_digest = enabled;
self
}
pub fn data_digest(mut self, enabled: bool) -> Self {
self.data_digest = enabled;
self
}
pub fn tls(mut self, enabled: bool) -> Self {
self.tls = enabled;
self
}
pub fn duplicate_connect(mut self, enabled: bool) -> Self {
self.duplicate_connect = enabled;
self
}
pub fn disable_sqflow(mut self, enabled: bool) -> Self {
self.disable_sqflow = enabled;
self
}
pub fn persistent(mut self, enabled: bool) -> Self {
self.persistent = enabled;
self
}
pub fn discovery(mut self) -> Self {
self.discovery = true;
self
}
#[cfg(has_unique_discovery_ctrl)]
pub fn unique_discovery(mut self) -> Self {
self.discovery = true;
self.unique_discovery = true;
self
}
pub fn execute(self) -> Result<Controller<'r>> {
let subsysnqn = CString::new(self.subsysnqn).map_err(invalid_input)?;
let transport = CString::new(self.transport).map_err(invalid_input)?;
let traddr = string_to_cstring_opt(self.traddr)?;
let trsvcid = string_to_cstring_opt(self.trsvcid)?;
let host_traddr = string_to_cstring_opt(self.host_traddr)?;
let host_iface = string_to_cstring_opt(self.host_iface)?;
let ctrl = unsafe {
nvme_create_ctrl(
self.host.root_ptr(),
subsysnqn.as_ptr(),
transport.as_ptr(),
cstr_ptr_or_null(&traddr),
cstr_ptr_or_null(&host_traddr),
cstr_ptr_or_null(&host_iface),
cstr_ptr_or_null(&trsvcid),
)
};
if ctrl.is_null() {
return Err(Error::Os(std::io::Error::last_os_error()));
}
if self.discovery {
unsafe { nvme_ctrl_set_discovery_ctrl(ctrl, true) };
}
#[cfg(has_unique_discovery_ctrl)]
if self.unique_discovery {
unsafe { nvme_ctrl_set_unique_discovery_ctrl(ctrl, true) };
}
if self.persistent {
unsafe { nvme_ctrl_set_persistent(ctrl, true) };
}
let mut cfg = nvme_fabrics_config::default();
unsafe { nvmf_default_config(&mut cfg) };
if let Some(n) = self.queue_size {
cfg.queue_size = n;
}
if let Some(n) = self.nr_io_queues {
cfg.nr_io_queues = n;
}
if let Some(n) = self.keep_alive_tmo {
cfg.keep_alive_tmo = n;
}
if let Some(n) = self.reconnect_delay {
cfg.reconnect_delay = n;
}
if let Some(n) = self.ctrl_loss_tmo {
cfg.ctrl_loss_tmo = n;
}
cfg.hdr_digest = self.hdr_digest;
cfg.data_digest = self.data_digest;
cfg.tls = self.tls;
cfg.duplicate_connect = self.duplicate_connect;
cfg.disable_sqflow = self.disable_sqflow;
if let Some(ref s) = host_traddr {
cfg.host_traddr = s.as_ptr() as *mut _;
}
if let Some(ref s) = host_iface {
cfg.host_iface = s.as_ptr() as *mut _;
}
let ret = unsafe { nvmf_add_ctrl(self.host.as_ptr(), ctrl, &cfg) };
check_ret(ret)?;
Ok(Controller::from_raw(ctrl))
}
}
pub struct DiscoveryLog {
inner: NonNull<nvmf_discovery_log>,
_not_send_sync: PhantomData<*const ()>,
}
impl DiscoveryLog {
pub(crate) fn from_raw(ptr: *mut nvmf_discovery_log) -> Result<Self> {
let inner = NonNull::new(ptr).ok_or_else(|| Error::Os(std::io::Error::last_os_error()))?;
Ok(DiscoveryLog {
inner,
_not_send_sync: PhantomData,
})
}
pub fn generation_counter(&self) -> u64 {
unsafe { (*self.inner.as_ptr()).genctr }
}
pub fn num_records(&self) -> u64 {
unsafe { (*self.inner.as_ptr()).numrec }
}
pub fn entries(&self) -> impl Iterator<Item = DiscoveryLogEntry<'_>> {
let count = self.num_records() as usize;
let ptr = unsafe { (*self.inner.as_ptr()).entries.as_ptr() };
(0..count).map(move |i| DiscoveryLogEntry {
raw: unsafe { &*ptr.add(i) },
})
}
}
impl Drop for DiscoveryLog {
fn drop(&mut self) {
unsafe { free(self.inner.as_ptr() as *mut _) };
}
}
impl std::fmt::Debug for DiscoveryLog {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DiscoveryLog")
.field("genctr", &self.generation_counter())
.field("numrec", &self.num_records())
.finish()
}
}
pub struct DiscoveryLogEntry<'log> {
raw: &'log nvmf_disc_log_entry,
}
impl<'log> DiscoveryLogEntry<'log> {
pub fn transport_type(&self) -> u8 {
self.raw.trtype
}
pub fn address_family(&self) -> u8 {
self.raw.adrfam
}
pub fn subsystem_type(&self) -> u8 {
self.raw.subtype
}
pub fn transport_requirements(&self) -> u8 {
self.raw.treq
}
pub fn port_id(&self) -> u16 {
self.raw.portid
}
pub fn controller_id(&self) -> u16 {
self.raw.cntlid
}
pub fn asq_size(&self) -> u16 {
self.raw.asqsz
}
pub fn entry_flags(&self) -> u16 {
self.raw.eflags
}
pub fn transport_service_id(&self) -> Result<&'log str> {
unsafe { cstr_to_str(self.raw.trsvcid.as_ptr()) }
}
pub fn subnqn(&self) -> Result<&'log str> {
unsafe { cstr_to_str(self.raw.subnqn.as_ptr()) }
}
pub fn traddr(&self) -> Result<&'log str> {
unsafe { cstr_to_str(self.raw.traddr.as_ptr()) }
}
}
impl std::fmt::Debug for DiscoveryLogEntry<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DiscoveryLogEntry")
.field("trtype", &self.transport_type())
.field("adrfam", &self.address_family())
.field("subnqn", &self.subnqn().ok())
.field("traddr", &self.traddr().ok())
.field("trsvcid", &self.transport_service_id().ok())
.finish()
}
}
pub(crate) fn fetch_discovery_log(
ctrl: libnvme_sys::nvme_ctrl_t,
max_retries: i32,
) -> Result<DiscoveryLog> {
let mut logp: *mut nvmf_discovery_log = std::ptr::null_mut();
let ret = unsafe { nvmf_get_discovery_log(ctrl, &mut logp, max_retries) };
check_ret(ret)?;
DiscoveryLog::from_raw(logp)
}
fn string_to_cstring_opt(opt: Option<String>) -> Result<Option<CString>> {
match opt {
None => Ok(None),
Some(s) => CString::new(s).map(Some).map_err(invalid_input),
}
}
fn cstr_ptr_or_null(opt: &Option<CString>) -> *const std::os::raw::c_char {
match opt {
Some(s) => s.as_ptr(),
None => std::ptr::null(),
}
}
fn invalid_input(_e: std::ffi::NulError) -> Error {
Error::Os(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"interior NUL byte in fabrics parameter",
))
}