Skip to main content

libnvme/
reservations.rs

1//! NVMe Reservation commands (Acquire / Register / Release / Report).
2//!
3//! Reservations let multiple host controllers coordinate access to a shared
4//! namespace — the NVMe analogue of SCSI Persistent Reservations. Typical
5//! use is multi-host NVMe-oF clustering where two or more hosts back the
6//! same namespace and need to negotiate which one has write access.
7//!
8//! All four commands are namespace-scoped. Build them via the methods on
9//! [`Namespace`] (`reservation_acquire`, `reservation_register`,
10//! `reservation_release`, `reservation_report`) and call `.execute()`.
11//!
12//! See NVMe spec §8.19 for the full semantics of each action.
13
14use std::ffi::c_void;
15
16use libnvme_sys::{
17    nvme_resv_acquire, nvme_resv_acquire_args, nvme_resv_register, nvme_resv_register_args,
18    nvme_resv_release, nvme_resv_release_args, nvme_resv_report, nvme_resv_report_args,
19};
20
21use crate::error::{check_ret, Error, Result};
22use crate::namespace::Namespace;
23
24/// The raw Reservation Status data structure (`nvme_resv_status`),
25/// re-exported from `libnvme-sys` so callers of
26/// [`ReservationReport::execute`] / [`ReservationReport::execute_to_vec`]
27/// can name and cast to it without taking a direct `libnvme-sys`
28/// dependency.
29///
30/// This is a `#[repr(C)]` struct with a variable-length trailing array of
31/// per-controller entries; a higher-level decoded view may be added in a
32/// future (additive) release. The `pub use` also brings it into scope for
33/// this module's internal use (the buffer-size checks in `execute`).
34pub use libnvme_sys::nvme_resv_status;
35
36// ---------------------------------------------------------------------------
37// Enums
38// ---------------------------------------------------------------------------
39
40/// Reservation type (rtype) — sets the access semantics for the reservation
41/// holder vs. registered hosts vs. non-registered hosts.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[non_exhaustive]
44#[repr(u8)]
45pub enum ReservationType {
46    /// Write Exclusive — only the holder can write; all hosts can read.
47    WriteExclusive = 1,
48    /// Exclusive Access — only the holder can read or write.
49    ExclusiveAccess = 2,
50    /// Write Exclusive, Registrants Only.
51    WriteExclusiveRegistrantsOnly = 3,
52    /// Exclusive Access, Registrants Only.
53    ExclusiveAccessRegistrantsOnly = 4,
54    /// Write Exclusive, All Registrants.
55    WriteExclusiveAllRegistrants = 5,
56    /// Exclusive Access, All Registrants.
57    ExclusiveAccessAllRegistrants = 6,
58}
59
60impl ReservationType {
61    fn as_raw(self) -> u32 {
62        self as u8 as u32
63    }
64}
65
66/// Reservation Acquire action (racqa).
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
68#[non_exhaustive]
69#[repr(u8)]
70pub enum ReservationAcquireAction {
71    /// Acquire the reservation.
72    #[default]
73    Acquire = 0,
74    /// Preempt the current holder.
75    Preempt = 1,
76    /// Preempt and abort outstanding commands from the current holder.
77    PreemptAndAbort = 2,
78}
79
80impl ReservationAcquireAction {
81    fn as_raw(self) -> u32 {
82        self as u8 as u32
83    }
84}
85
86/// Reservation Register action (rrega).
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
88#[non_exhaustive]
89#[repr(u8)]
90pub enum ReservationRegisterAction {
91    /// Register a new reservation key.
92    #[default]
93    Register = 0,
94    /// Unregister this host's reservation key.
95    Unregister = 1,
96    /// Replace the existing reservation key.
97    Replace = 2,
98}
99
100impl ReservationRegisterAction {
101    fn as_raw(self) -> u32 {
102        self as u8 as u32
103    }
104}
105
106/// Reservation Release action (rrela).
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
108#[non_exhaustive]
109#[repr(u8)]
110pub enum ReservationReleaseAction {
111    /// Release the reservation.
112    #[default]
113    Release = 0,
114    /// Clear all reservations and registrations.
115    Clear = 1,
116}
117
118impl ReservationReleaseAction {
119    fn as_raw(self) -> u32 {
120        self as u8 as u32
121    }
122}
123
124/// Change-Persist-Through-Power-Loss (cptpl) for Reservation Register.
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
126#[non_exhaustive]
127#[repr(u8)]
128pub enum PtplChange {
129    /// No change to PTPL state.
130    #[default]
131    NoChange = 0,
132    /// Reservations and registrations are released on power-on.
133    Clear = 2,
134    /// Reservations and registrations persist across power loss.
135    Persist = 3,
136}
137
138impl PtplChange {
139    fn as_raw(self) -> u32 {
140        self as u8 as u32
141    }
142}
143
144// ---------------------------------------------------------------------------
145// Acquire
146// ---------------------------------------------------------------------------
147
148/// Builder returned by [`Namespace::reservation_acquire`].
149#[must_use = "this builder does nothing until `.execute()` is called"]
150pub struct ReservationAcquire<'a, 'r> {
151    ns: &'a Namespace<'r>,
152    crkey: u64,
153    nrkey: u64,
154    rtype: ReservationType,
155    action: ReservationAcquireAction,
156    iekey: bool,
157    timeout_ms: u32,
158}
159
160impl<'a, 'r> ReservationAcquire<'a, 'r> {
161    pub(crate) fn new(ns: &'a Namespace<'r>) -> Self {
162        ReservationAcquire {
163            ns,
164            crkey: 0,
165            nrkey: 0,
166            rtype: ReservationType::WriteExclusive,
167            action: ReservationAcquireAction::default(),
168            iekey: false,
169            timeout_ms: 0,
170        }
171    }
172
173    /// Current Reservation Key associated with this host.
174    pub fn key(mut self, crkey: u64) -> Self {
175        self.crkey = crkey;
176        self
177    }
178
179    /// New Reservation Key — used only when `action` is
180    /// [`ReservationAcquireAction::Preempt`] or `PreemptAndAbort`.
181    pub fn new_key(mut self, nrkey: u64) -> Self {
182        self.nrkey = nrkey;
183        self
184    }
185
186    /// Reservation type to acquire.
187    pub fn rtype(mut self, rtype: ReservationType) -> Self {
188        self.rtype = rtype;
189        self
190    }
191
192    /// Which acquire action to perform.
193    pub fn action(mut self, action: ReservationAcquireAction) -> Self {
194        self.action = action;
195        self
196    }
197
198    /// Ignore Existing Key — bypass the host-registered key check.
199    pub fn ignore_existing_key(mut self) -> Self {
200        self.iekey = true;
201        self
202    }
203
204    /// Per-command timeout in milliseconds.
205    pub fn timeout_ms(mut self, ms: u32) -> Self {
206        self.timeout_ms = ms;
207        self
208    }
209
210    /// Issue the Reservation Acquire command.
211    pub fn execute(self) -> Result<u32> {
212        let fd = ns_fd(self.ns)?;
213        let mut result: u32 = 0;
214        let mut args = nvme_resv_acquire_args {
215            crkey: self.crkey,
216            nrkey: self.nrkey,
217            result: &mut result,
218            args_size: std::mem::size_of::<nvme_resv_acquire_args>() as i32,
219            fd,
220            timeout: self.timeout_ms,
221            nsid: self.ns.nsid(),
222            rtype: self.rtype.as_raw(),
223            racqa: self.action.as_raw(),
224            iekey: self.iekey,
225        };
226        // SAFETY: args is fully-initialized on the stack; fd is a valid
227        // file descriptor for this namespace.
228        let ret = unsafe { nvme_resv_acquire(&mut args) };
229        check_ret(ret)?;
230        Ok(result)
231    }
232}
233
234// ---------------------------------------------------------------------------
235// Register
236// ---------------------------------------------------------------------------
237
238/// Builder returned by [`Namespace::reservation_register`].
239#[must_use = "this builder does nothing until `.execute()` is called"]
240pub struct ReservationRegister<'a, 'r> {
241    ns: &'a Namespace<'r>,
242    crkey: u64,
243    nrkey: u64,
244    action: ReservationRegisterAction,
245    cptpl: PtplChange,
246    iekey: bool,
247    timeout_ms: u32,
248}
249
250impl<'a, 'r> ReservationRegister<'a, 'r> {
251    pub(crate) fn new(ns: &'a Namespace<'r>) -> Self {
252        ReservationRegister {
253            ns,
254            crkey: 0,
255            nrkey: 0,
256            action: ReservationRegisterAction::default(),
257            cptpl: PtplChange::default(),
258            iekey: false,
259            timeout_ms: 0,
260        }
261    }
262
263    /// Current Reservation Key — only required when replacing or
264    /// unregistering an existing key.
265    pub fn key(mut self, crkey: u64) -> Self {
266        self.crkey = crkey;
267        self
268    }
269
270    /// New Reservation Key to register (or replace with).
271    pub fn new_key(mut self, nrkey: u64) -> Self {
272        self.nrkey = nrkey;
273        self
274    }
275
276    /// Which register action to perform.
277    pub fn action(mut self, action: ReservationRegisterAction) -> Self {
278        self.action = action;
279        self
280    }
281
282    /// Change Persist-Through-Power-Loss state.
283    pub fn ptpl(mut self, cptpl: PtplChange) -> Self {
284        self.cptpl = cptpl;
285        self
286    }
287
288    /// Ignore Existing Key — bypass the host-registered key check.
289    pub fn ignore_existing_key(mut self) -> Self {
290        self.iekey = true;
291        self
292    }
293
294    /// Per-command timeout in milliseconds. `0` uses libnvme's default.
295    pub fn timeout_ms(mut self, ms: u32) -> Self {
296        self.timeout_ms = ms;
297        self
298    }
299
300    /// Issue the Reservation Register command.
301    pub fn execute(self) -> Result<u32> {
302        let fd = ns_fd(self.ns)?;
303        let mut result: u32 = 0;
304        let mut args = nvme_resv_register_args {
305            crkey: self.crkey,
306            nrkey: self.nrkey,
307            result: &mut result,
308            args_size: std::mem::size_of::<nvme_resv_register_args>() as i32,
309            fd,
310            timeout: self.timeout_ms,
311            nsid: self.ns.nsid(),
312            rrega: self.action.as_raw(),
313            cptpl: self.cptpl.as_raw(),
314            iekey: self.iekey,
315        };
316        // SAFETY: args is fully-initialized on the stack; fd is valid.
317        let ret = unsafe { nvme_resv_register(&mut args) };
318        check_ret(ret)?;
319        Ok(result)
320    }
321}
322
323// ---------------------------------------------------------------------------
324// Release
325// ---------------------------------------------------------------------------
326
327/// Builder returned by [`Namespace::reservation_release`].
328#[must_use = "this builder does nothing until `.execute()` is called"]
329pub struct ReservationRelease<'a, 'r> {
330    ns: &'a Namespace<'r>,
331    crkey: u64,
332    rtype: ReservationType,
333    action: ReservationReleaseAction,
334    iekey: bool,
335    timeout_ms: u32,
336}
337
338impl<'a, 'r> ReservationRelease<'a, 'r> {
339    pub(crate) fn new(ns: &'a Namespace<'r>) -> Self {
340        ReservationRelease {
341            ns,
342            crkey: 0,
343            rtype: ReservationType::WriteExclusive,
344            action: ReservationReleaseAction::default(),
345            iekey: false,
346            timeout_ms: 0,
347        }
348    }
349
350    /// Current Reservation Key being released.
351    pub fn key(mut self, crkey: u64) -> Self {
352        self.crkey = crkey;
353        self
354    }
355
356    /// Reservation type being released (must match the held type).
357    pub fn rtype(mut self, rtype: ReservationType) -> Self {
358        self.rtype = rtype;
359        self
360    }
361
362    /// Release action.
363    pub fn action(mut self, action: ReservationReleaseAction) -> Self {
364        self.action = action;
365        self
366    }
367
368    /// Ignore Existing Key.
369    pub fn ignore_existing_key(mut self) -> Self {
370        self.iekey = true;
371        self
372    }
373
374    /// Per-command timeout in milliseconds. `0` uses libnvme's default.
375    pub fn timeout_ms(mut self, ms: u32) -> Self {
376        self.timeout_ms = ms;
377        self
378    }
379
380    /// Issue the Reservation Release command.
381    pub fn execute(self) -> Result<u32> {
382        let fd = ns_fd(self.ns)?;
383        let mut result: u32 = 0;
384        let mut args = nvme_resv_release_args {
385            crkey: self.crkey,
386            result: &mut result,
387            args_size: std::mem::size_of::<nvme_resv_release_args>() as i32,
388            fd,
389            timeout: self.timeout_ms,
390            nsid: self.ns.nsid(),
391            rtype: self.rtype.as_raw(),
392            rrela: self.action.as_raw(),
393            iekey: self.iekey,
394        };
395        // SAFETY: args is fully-initialized on the stack; fd is valid.
396        let ret = unsafe { nvme_resv_release(&mut args) };
397        check_ret(ret)?;
398        Ok(result)
399    }
400}
401
402// ---------------------------------------------------------------------------
403// Report
404// ---------------------------------------------------------------------------
405
406/// Builder returned by [`Namespace::reservation_report`].
407///
408/// Reads into a caller-supplied buffer. The buffer holds an
409/// `nvme_resv_status` header followed by a variable number of registered
410/// controller entries; callers cast to the bindgen type or use the
411/// convenience [`ReservationReport::execute_to_vec`] for a default-sized
412/// allocation.
413#[must_use = "this builder does nothing until `.execute()` is called"]
414pub struct ReservationReport<'a, 'r> {
415    ns: &'a Namespace<'r>,
416    buf: Option<&'a mut [u8]>,
417    eds: bool,
418    timeout_ms: u32,
419}
420
421impl<'a, 'r> ReservationReport<'a, 'r> {
422    pub(crate) fn new(ns: &'a Namespace<'r>) -> Self {
423        ReservationReport {
424            ns,
425            buf: None,
426            eds: false,
427            timeout_ms: 0,
428        }
429    }
430
431    /// Read the report into this buffer. Must be at least
432    /// `size_of::<nvme_resv_status>()` bytes; bigger buffers will read
433    /// the variable controller-entry tail too. Required before
434    /// [`Self::execute`].
435    pub fn buffer(mut self, buf: &'a mut [u8]) -> Self {
436        self.buf = Some(buf);
437        self
438    }
439
440    /// Request the Extended Data Structure (64-bit reservation keys
441    /// instead of the legacy 16-bit registrant IDs).
442    pub fn extended(mut self) -> Self {
443        self.eds = true;
444        self
445    }
446
447    /// Per-command timeout in milliseconds. `0` uses libnvme's default.
448    pub fn timeout_ms(mut self, ms: u32) -> Self {
449        self.timeout_ms = ms;
450        self
451    }
452
453    /// Execute against the user-provided buffer.
454    pub fn execute(self) -> Result<u32> {
455        let buf = self.buf.ok_or(Error::invalid_argument(
456            "ReservationReport requires a buffer via .buffer()",
457        ))?;
458        if buf.len() < std::mem::size_of::<nvme_resv_status>() {
459            return Err(Error::invalid_argument(
460                "ReservationReport buffer smaller than nvme_resv_status header",
461            ));
462        }
463        let fd = ns_fd(self.ns)?;
464        let mut result: u32 = 0;
465        let mut args = nvme_resv_report_args {
466            result: &mut result,
467            report: buf.as_mut_ptr() as *mut nvme_resv_status,
468            args_size: std::mem::size_of::<nvme_resv_report_args>() as i32,
469            fd,
470            timeout: self.timeout_ms,
471            nsid: self.ns.nsid(),
472            len: buf.len() as u32,
473            eds: self.eds,
474        };
475        // SAFETY: args is fully-initialized on the stack; report points
476        // to a caller-owned buffer of `len` bytes (verified above).
477        let ret = unsafe { nvme_resv_report(&mut args) };
478        check_ret(ret)?;
479        Ok(result)
480    }
481
482    /// Convenience: allocate a 4 KiB buffer and execute. Returns the raw
483    /// bytes; reinterpret the leading bytes as [`nvme_resv_status`]
484    /// (re-exported from this crate) to decode the report.
485    pub fn execute_to_vec(self) -> Result<Vec<u8>> {
486        let eds = self.eds;
487        let timeout_ms = self.timeout_ms;
488        let ns = self.ns;
489        let mut buf = vec![0u8; 4096];
490        ReservationReport {
491            ns,
492            buf: Some(&mut buf),
493            eds,
494            timeout_ms,
495        }
496        .execute()?;
497        Ok(buf)
498    }
499}
500
501// ---------------------------------------------------------------------------
502// Helpers
503// ---------------------------------------------------------------------------
504
505fn ns_fd(ns: &Namespace<'_>) -> Result<std::os::raw::c_int> {
506    // SAFETY: ns.raw_handle() is a non-null nvme_ns_t tied to the Root tree
507    // via 'r; libnvme opens the device lazily and returns -1 on failure.
508    let fd = unsafe { libnvme_sys::nvme_ns_get_fd(ns.raw_handle()) };
509    if fd < 0 {
510        return Err(Error::Os(std::io::Error::last_os_error()));
511    }
512    Ok(fd)
513}
514
515// Silence unused-import lints when `c_void` is only referenced through
516// type-system equivalences (kept for future buffer-bearing report variants).
517#[allow(dead_code)]
518const _C_VOID_HINT: Option<*const c_void> = None;