duende_ublk/
lib.rs

1//! duende-ublk - ublk device lifecycle management
2//!
3//! This crate provides tools for managing ublk (userspace block device) lifecycle,
4//! particularly for swap-critical daemons that need to clean up orphaned devices.
5//!
6//! # Problem: Orphaned ublk Devices
7//!
8//! When a ublk daemon crashes or is killed, the kernel may retain device state
9//! even after the `/dev/ublkbN` block device disappears. This causes:
10//!
11//! - "File exists" errors when creating new devices
12//! - Device ID conflicts
13//! - System requires reboot to clear stale state
14//!
15//! # Solution
16//!
17//! ```rust,no_run
18//! use duende_ublk::{UblkControl, cleanup_orphaned_devices};
19//!
20//! fn main() -> Result<(), duende_ublk::Error> {
21//!     // Clean up any orphaned devices from previous crashes
22//!     let cleaned = cleanup_orphaned_devices()?;
23//!     println!("Cleaned {} orphaned devices", cleaned);
24//!
25//!     // Now safe to create new devices
26//!     Ok(())
27//! }
28//! ```
29//!
30//! # Kernel Interface
31//!
32//! This crate uses io_uring URING_CMD to communicate with the ublk kernel driver.
33//! Linux 6.0+ is required.
34
35#![forbid(unsafe_op_in_unsafe_fn)]
36
37mod control;
38mod error;
39mod sys;
40
41pub use control::{
42    UblkControl, block_device_exists, block_device_path, build_device_command,
43    build_get_info_command, char_device_path, cleanup_orphaned_devices,
44    control_device_available, detect_orphaned_devices, detect_orphans_in_dir,
45    interpret_command_result, parse_char_device_id,
46};
47pub use error::Error;
48pub use sys::{UBLK_CTRL_DEV, UblkCtrlCmd, UblkCtrlCmdExt, UblkCtrlDevInfo};
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    // ============================================================================
55    // A. Struct Layout Tests (Protocol Correctness)
56    // ============================================================================
57
58    #[test]
59    fn test_ctrl_cmd_size() {
60        // Kernel ublksrv_ctrl_cmd is exactly 32 bytes
61        assert_eq!(std::mem::size_of::<sys::UblkCtrlCmd>(), 32);
62    }
63
64    #[test]
65    fn test_ctrl_cmd_ext_size() {
66        // Extended command for io_uring SQE cmd field is 80 bytes
67        assert_eq!(std::mem::size_of::<sys::UblkCtrlCmdExt>(), 80);
68    }
69
70    #[test]
71    fn test_ctrl_dev_info_size() {
72        // Kernel ublksrv_ctrl_dev_info is exactly 64 bytes
73        assert_eq!(std::mem::size_of::<sys::UblkCtrlDevInfo>(), 64);
74    }
75
76    // ============================================================================
77    // B. ioctl Encoding Tests
78    // ============================================================================
79
80    #[test]
81    fn test_del_dev_ioctl_value() {
82        // UBLK_U_CMD_DEL_DEV = _IOWR('u', 0x05, struct ublksrv_ctrl_cmd)
83        // = (3 << 30) | (32 << 16) | (0x75 << 8) | 0x05
84        let expected = (3u32 << 30) | (32u32 << 16) | (0x75u32 << 8) | 0x05;
85        assert_eq!(sys::UBLK_U_CMD_DEL_DEV, expected);
86    }
87
88    #[test]
89    fn test_stop_dev_ioctl_value() {
90        // UBLK_U_CMD_STOP_DEV = _IOWR('u', 0x07, struct ublksrv_ctrl_cmd)
91        let expected = (3u32 << 30) | (32u32 << 16) | (0x75u32 << 8) | 0x07;
92        assert_eq!(sys::UBLK_U_CMD_STOP_DEV, expected);
93    }
94
95    #[test]
96    fn test_get_dev_info_ioctl_value() {
97        // UBLK_U_CMD_GET_DEV_INFO = _IOR('u', 0x02, struct ublksrv_ctrl_cmd)
98        let expected = (2u32 << 30) | (32u32 << 16) | (0x75u32 << 8) | 0x02;
99        assert_eq!(sys::UBLK_U_CMD_GET_DEV_INFO, expected);
100    }
101
102    // ============================================================================
103    // C. Error Type Tests
104    // ============================================================================
105
106    #[test]
107    fn test_error_display() {
108        let err = Error::ControlDeviceNotFound;
109        assert!(err.to_string().contains("control"));
110
111        let err = Error::DeviceNotFound { dev_id: 5 };
112        assert!(err.to_string().contains("5"));
113    }
114
115    // ============================================================================
116    // D. Orphan Detection Tests (requires /dev access)
117    // ============================================================================
118
119    #[test]
120    fn test_detect_orphaned_devices_no_panic() {
121        // Should not panic on systems without ublk
122        let result = detect_orphaned_devices();
123        // Either Ok with empty vec or error is acceptable
124        if let Ok(orphans) = result {
125            // On a clean system, should be empty
126            // (can't assert this as system state varies)
127            let _ = orphans;
128        }
129        // Error is also acceptable if /dev/ublk-control doesn't exist
130    }
131
132    #[test]
133    fn test_cleanup_orphaned_devices_no_panic() {
134        // Should not panic on systems without ublk
135        let result = cleanup_orphaned_devices();
136        // Either Ok or error is acceptable
137        assert!(result.is_ok() || result.is_err());
138    }
139}