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}