test_bd/
lib.rs

1//! A library for creating procedurally generated test block devices using ublk.
2//!
3//! `test-bd` provides a simple API for creating userspace block devices with
4//! deterministic, procedurally generated data patterns. This is useful for testing
5//! storage systems, compression algorithms, deduplication systems, and other
6//! block-level operations.
7//!
8//! # Features
9//!
10//! - **Procedural Data Generation**: Create block devices with deterministic data patterns
11//!   using a seed-based random number generator
12//! - **Multiple Pattern Types**: Support for fill (zeros), duplicate (repeating blocks),
13//!   and random data patterns
14//! - **Configurable Segmentation**: Divide devices into multiple segments with different
15//!   data patterns
16//! - **Device Management**: High-level API for managing multiple devices with automatic
17//!   cleanup
18//!
19//! # Quick Start
20//!
21//! The easiest way to create and manage test block devices is using [`DeviceManager`]:
22//!
23//! ```no_run
24//! use test_bd::{DeviceManager, TestBlockDeviceConfig};
25//!
26//! // Create a device manager
27//! let mut manager = DeviceManager::new();
28//!
29//! // Configure a 1 GiB device with mixed data patterns
30//! let config = TestBlockDeviceConfig {
31//!     dev_id: -1,              // Auto-assign device ID
32//!     size: 1024 * 1024 * 1024, // 1 GiB
33//!     seed: 42,                // Seed for reproducibility
34//!     fill_percent: 40,        // 40% zeros (compressible)
35//!     duplicate_percent: 30,   // 30% duplicate blocks (deduplicatable)
36//!     random_percent: 30,      // 30% random data
37//!     segments: 100,           // 100 segments
38//!     unprivileged: false,
39//! };
40//!
41//! // Create the device (appears as /dev/ublkbN)
42//! let device = manager.create(config).expect("Failed to create device");
43//! println!("Created device: /dev/ublkb{}", device.dev_id);
44//!
45//! // Use the device...
46//! // The device will be automatically cleaned up when manager is dropped
47//! ```
48//!
49//! # Data Patterns
50//!
51//! Three types of data patterns are supported via the [`Bucket`] enum:
52//!
53//! - **Fill**: All zeros (highly compressible)
54//! - **Duplicate**: Repeating 512-byte blocks (deduplicatable)
55//! - **Random**: Deterministic pseudo-random data (incompressible)
56//!
57//! The device is divided into segments, and each segment is randomly assigned
58//! one of these patterns based on the configured percentages.
59//!
60//! # Low-Level API
61//!
62//! For more control, use the [`TestBlockDevice`] API directly:
63//!
64//! ```no_run
65//! use test_bd::{TestBlockDevice, TestBlockDeviceConfig};
66//!
67//! let config = TestBlockDeviceConfig {
68//!     dev_id: -1,
69//!     size: 100 * 1024 * 1024,  // 100 MiB
70//!     seed: 42,
71//!     fill_percent: 50,
72//!     duplicate_percent: 25,
73//!     random_percent: 25,
74//!     segments: 50,
75//!     unprivileged: false,
76//! };
77//!
78//! // This blocks until the device is stopped
79//! TestBlockDevice::run(config).expect("Failed to run device");
80//! ```
81//!
82//! # Segment Information
83//!
84//! You can inspect the segment layout before or after creating a device:
85//!
86//! ```
87//! use test_bd::{TestBlockDeviceConfig, Bucket};
88//!
89//! let config = TestBlockDeviceConfig {
90//!     dev_id: -1,
91//!     size: 10240,
92//!     seed: 42,
93//!     fill_percent: 50,
94//!     duplicate_percent: 25,
95//!     random_percent: 25,
96//!     segments: 10,
97//!     unprivileged: false,
98//! };
99//!
100//! let segments = config.generate_segments();
101//! for (i, segment) in segments.iter().enumerate() {
102//!     println!("Segment {}: {:?} pattern, {} bytes",
103//!              i, segment.pattern, segment.size_bytes());
104//! }
105//! ```
106//!
107//! # Requirements
108//!
109//! - Linux kernel with ublk support (kernel 6.0+)
110//! - Root privileges (unless using unprivileged mode, which requires kernel 6.5+)
111//!
112//! # See Also
113//!
114//! - [`TestBlockDeviceConfig`] - Configuration structure
115//! - [`DeviceManager`] - High-level device management
116//! - [`TestBlockDevice`] - Low-level device API
117//! - [`SegmentInfo`] - Segment layout information
118//! - [`Bucket`] - Data pattern types
119
120use io_uring::IoUring;
121use libublk::ctrl::UblkCtrl;
122use libublk::ctrl_async::UblkCtrlAsync;
123use libublk::helpers::IoBuf;
124use libublk::io::{UblkDev, UblkQueue};
125use libublk::uring_async::{run_uring_tasks, ublk_reap_events_with_handler, ublk_wake_task};
126use libublk::{BufDesc, UblkError, UblkFlags};
127use std::fs::File;
128use std::os::fd::{AsRawFd, FromRawFd};
129use std::rc::Rc;
130use std::sync::{Arc, Mutex};
131
132mod data_pattern;
133pub use data_pattern::Bucket;
134use data_pattern::PercentPattern;
135mod position;
136
137pub use position::IndexPos;
138use serde::{Deserialize, Serialize};
139use std::collections::HashMap;
140use std::sync::mpsc::{self, Receiver, Sender};
141use std::thread::{self, JoinHandle};
142
143/// Information about a contiguous segment of the block device with a specific data pattern.
144///
145/// A test block device is divided into multiple segments, where each segment uses a
146/// different data pattern type (Fill, Duplicate, or Random). This struct describes
147/// a single segment's location and pattern type.
148///
149/// # Examples
150///
151/// ```
152/// use test_bd::{SegmentInfo, IndexPos, Bucket};
153///
154/// let segment = SegmentInfo {
155///     start: IndexPos::new(0),
156///     end: IndexPos::new(1024),
157///     pattern: Bucket::Fill,
158/// };
159///
160/// assert_eq!(segment.count(), 1024);
161/// assert_eq!(segment.size_bytes(), 8192);  // 1024 * 8 bytes
162/// ```
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct SegmentInfo {
165    /// Starting position of this segment (inclusive).
166    pub start: IndexPos,
167
168    /// Ending position of this segment (exclusive).
169    pub end: IndexPos,
170
171    /// The data pattern type used in this segment.
172    pub pattern: Bucket,
173}
174
175impl SegmentInfo {
176    /// Returns the number of 8-byte elements in this segment.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// use test_bd::{SegmentInfo, IndexPos, Bucket};
182    ///
183    /// let segment = SegmentInfo {
184    ///     start: IndexPos::new(0),
185    ///     end: IndexPos::new(100),
186    ///     pattern: Bucket::Random,
187    /// };
188    ///
189    /// assert_eq!(segment.count(), 100);
190    /// ```
191    pub fn count(&self) -> u64 {
192        self.end.as_u64() - self.start.as_u64()
193    }
194
195    /// Returns the size of this segment in bytes.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// use test_bd::{SegmentInfo, IndexPos, Bucket};
201    ///
202    /// let segment = SegmentInfo {
203    ///     start: IndexPos::new(0),
204    ///     end: IndexPos::new(100),
205    ///     pattern: Bucket::Random,
206    /// };
207    ///
208    /// assert_eq!(segment.size_bytes(), 800);  // 100 * 8 bytes
209    /// ```
210    pub fn size_bytes(&self) -> u64 {
211        (self.end.as_u64() - self.start.as_u64()) * 8
212    }
213}
214
215/// Configuration for creating a test block device.
216///
217/// This struct contains all the parameters needed to create a procedurally generated
218/// test block device with specific characteristics for testing storage systems, compression,
219/// deduplication, and other block-level operations.
220///
221/// # Examples
222///
223/// ```no_run
224/// use test_bd::TestBlockDeviceConfig;
225///
226/// let config = TestBlockDeviceConfig {
227///     dev_id: -1,  // Auto-assign device ID
228///     size: 1024 * 1024 * 1024,  // 1 GiB
229///     seed: 42,
230///     fill_percent: 40,
231///     duplicate_percent: 30,
232///     random_percent: 30,
233///     segments: 100,
234///     unprivileged: false,
235/// };
236///
237/// // Validate the configuration
238/// config.validate().expect("Invalid configuration");
239/// ```
240#[derive(Debug, Clone)]
241pub struct TestBlockDeviceConfig {
242    /// Device ID for the block device.
243    ///
244    /// Use `-1` to automatically assign the next available device ID,
245    /// or specify a non-negative value to request a specific device ID.
246    pub dev_id: i32,
247
248    /// Total size of the block device in bytes.
249    ///
250    /// Must be a multiple of 8 bytes and not exceed `i64::MAX`.
251    pub size: u64,
252
253    /// Seed for the pseudo-random number generator.
254    ///
255    /// Using the same seed with the same configuration will produce
256    /// identical data patterns, enabling reproducible tests.
257    pub seed: u64,
258
259    /// Percentage of the device to fill with zeros (0-100).
260    ///
261    /// Fill segments are useful for testing compression.
262    pub fill_percent: u32,
263
264    /// Percentage of the device to fill with duplicate patterns (0-100).
265    ///
266    /// Duplicate segments contain repeating 512-byte blocks, useful for
267    /// testing deduplication.
268    pub duplicate_percent: u32,
269
270    /// Percentage of the device to fill with pseudo-random data (0-100).
271    ///
272    /// Random segments simulate encrypted or already-compressed data.
273    pub random_percent: u32,
274
275    /// Number of segments to divide the device into.
276    ///
277    /// Must be less than `size / 512`. More segments create more varied
278    /// data distributions but may impact performance.
279    pub segments: usize,
280
281    /// Whether to create an unprivileged device (non-root access).
282    ///
283    /// When `true`, the device can be accessed by non-root users
284    /// (requires kernel support).
285    pub unprivileged: bool,
286}
287
288impl TestBlockDeviceConfig {
289    /// Generates the segment layout for this configuration without creating a device.
290    ///
291    /// This is useful for inspecting what the device layout would be before
292    /// actually creating the device, or for verification purposes.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// use test_bd::TestBlockDeviceConfig;
298    ///
299    /// let config = TestBlockDeviceConfig {
300    ///     dev_id: -1,
301    ///     size: 10240,
302    ///     seed: 42,
303    ///     fill_percent: 50,
304    ///     duplicate_percent: 25,
305    ///     random_percent: 25,
306    ///     segments: 10,
307    ///     unprivileged: false,
308    /// };
309    ///
310    /// let segments = config.generate_segments();
311    /// assert_eq!(segments.len(), 10);
312    /// ```
313    pub fn generate_segments(&self) -> Vec<SegmentInfo> {
314        let percents = self.percent_pattern();
315        let (_, mapping) =
316            data_pattern::DataMix::create(self.size, self.seed, self.segments, &percents);
317
318        mapping
319            .into_iter()
320            .map(|(range, bucket)| SegmentInfo {
321                start: range.start,
322                end: range.end,
323                pattern: bucket,
324            })
325            .collect()
326    }
327
328    /// Validates the configuration parameters.
329    ///
330    /// Ensures that:
331    /// - Percentages sum to exactly 100
332    /// - Size doesn't exceed `i64::MAX`
333    /// - Number of segments is reasonable for the device size
334    ///
335    /// # Returns
336    ///
337    /// - `Ok(())` if the configuration is valid
338    /// - `Err(String)` with a descriptive error message if validation fails
339    ///
340    /// # Examples
341    ///
342    /// ```
343    /// use test_bd::TestBlockDeviceConfig;
344    ///
345    /// let config = TestBlockDeviceConfig {
346    ///     dev_id: -1,
347    ///     size: 10240,
348    ///     seed: 42,
349    ///     fill_percent: 50,
350    ///     duplicate_percent: 30,
351    ///     random_percent: 20,
352    ///     segments: 10,
353    ///     unprivileged: false,
354    /// };
355    ///
356    /// assert!(config.validate().is_ok());
357    /// ```
358    pub fn validate(&self) -> Result<(), String> {
359        if self.fill_percent + self.duplicate_percent + self.random_percent != 100 {
360            return Err(format!(
361                "Percentages must total 100, got: fill={}, dup={}, random={}",
362                self.fill_percent, self.duplicate_percent, self.random_percent
363            ));
364        }
365
366        if self.size > i64::MAX as u64 {
367            return Err(format!("Size exceeds maximum: {}", i64::MAX));
368        }
369
370        if self.segments as u64 >= self.size / 512 {
371            return Err(format!(
372                "Number of segments ({}) must be less than device size / 512 ({})",
373                self.segments,
374                self.size / 512
375            ));
376        }
377
378        Ok(())
379    }
380
381    pub(crate) fn percent_pattern(&self) -> PercentPattern {
382        PercentPattern {
383            fill: self.fill_percent,
384            duplicates: self.duplicate_percent,
385            random: self.random_percent,
386        }
387    }
388}
389
390fn handle_io(
391    q: &UblkQueue,
392    tag: u16,
393    buf_addr: *mut u8,
394    state: &mut data_pattern::TestBdState,
395) -> i32 {
396    let iod = q.get_iod(tag);
397    let off = iod.start_sector << 9;
398    let bytes = (iod.nr_sectors << 9) as i32;
399    let op = iod.op_flags & 0xff;
400
401    assert!(bytes % 8 == 0);
402
403    match op {
404        libublk::sys::UBLK_IO_OP_READ => unsafe {
405            let offset_index = IndexPos::new(off / 8);
406            let mut p = buf_addr as *mut libc::c_ulonglong;
407            let writes: u64 = (bytes / 8) as u64;
408
409            let mut io_gen = state.s.lock().unwrap();
410            io_gen.setup(offset_index);
411
412            for _ in 0..writes {
413                let v = io_gen.next_u64().to_be();
414                *p = v;
415                p = p.wrapping_add(1);
416            }
417        },
418        libublk::sys::UBLK_IO_OP_WRITE => {
419            return -libc::EINVAL;
420        }
421        libublk::sys::UBLK_IO_OP_FLUSH => {}
422        _ => {
423            return -libc::EINVAL;
424        }
425    }
426
427    bytes
428}
429
430async fn io_task(
431    q: &UblkQueue<'_>,
432    tag: u16,
433    state: &mut data_pattern::TestBdState,
434) -> Result<(), UblkError> {
435    let buf_size = q.dev.dev_info.max_io_buf_bytes as usize;
436    let buffer = IoBuf::<u8>::new(buf_size);
437    let addr = buffer.as_mut_ptr();
438
439    // Submit initial prep command - any error will exit the function
440    q.submit_io_prep_cmd(tag, BufDesc::Slice(buffer.as_slice()), 0, Some(&buffer))
441        .await?;
442
443    loop {
444        let res = handle_io(q, tag, addr, state);
445
446        // Any error (including QueueIsDown) will break the loop by exiting the function
447        q.submit_io_commit_cmd(tag, BufDesc::Slice(buffer.as_slice()), res)
448            .await?;
449    }
450}
451
452/// Poll and handle both QUEUE_RING and CTRL_URING concurrently
453async fn poll_and_handle_rings<R, I>(
454    run_ops: R,
455    is_done: I,
456    check_done: bool,
457) -> Result<(), UblkError>
458where
459    R: Fn(),
460    I: Fn() -> bool,
461{
462    // Helper to create async wrapper for file descriptor
463    let create_async_wrapper = |fd: i32| -> Result<smol::Async<File>, UblkError> {
464        let file = unsafe { File::from_raw_fd(fd) };
465        smol::Async::new(file).map_err(|_| UblkError::OtherError(-libc::EINVAL))
466    };
467
468    // Get file descriptors and create async wrappers
469    let queue_fd = libublk::io::with_task_io_ring(|ring| ring.as_raw_fd());
470    let ctrl_fd = libublk::ctrl::with_ctrl_ring(|ring| ring.as_raw_fd());
471    let async_queue = create_async_wrapper(queue_fd)?;
472    let async_ctrl = create_async_wrapper(ctrl_fd)?;
473
474    // Polling function for both rings
475    let poll_both_rings = || async {
476        // Submit and wait on both rings
477        libublk::io::with_task_io_ring_mut(|ring| ring.submit_and_wait(0))?;
478        libublk::ctrl::with_ctrl_ring_mut(|ring| ring.submit_and_wait(0))?;
479
480        // Wait for either ring to become readable
481        smol::future::race(async_queue.readable(), async_ctrl.readable())
482            .await
483            .map(|_| false) // No timeout
484            .map_err(UblkError::IOError)
485    };
486
487    // Helper to handle events from a ring
488    let handle_ring_events = |cqe: &io_uring::cqueue::Entry| {
489        ublk_wake_task(cqe.user_data(), cqe);
490        cqe.result() == libublk::sys::UBLK_IO_RES_ABORT
491    };
492
493    // Event reaping function for both rings
494    let reap_events = |_poll_timeout| {
495        let mut aborted = check_done;
496
497        // Reap events from both rings
498        let queue_result = libublk::io::with_task_io_ring_mut(|ring| {
499            ublk_reap_events_with_handler(ring, |cqe| {
500                if handle_ring_events(cqe) {
501                    aborted = true;
502                }
503            })
504        });
505
506        let ctrl_result = libublk::ctrl::with_ctrl_ring_mut(|ring| {
507            ublk_reap_events_with_handler(ring, |cqe| {
508                if handle_ring_events(cqe) {
509                    aborted = true;
510                }
511            })
512        });
513
514        queue_result.and(ctrl_result).map(|_| aborted)
515    };
516
517    run_uring_tasks(poll_both_rings, reap_events, run_ops, is_done).await?;
518
519    // Prevent file descriptors from being closed when async wrappers are dropped
520    let _ = async_queue.into_inner().map(|f| {
521        use std::os::fd::IntoRawFd;
522        f.into_raw_fd()
523    });
524    let _ = async_ctrl.into_inner().map(|f| {
525        use std::os::fd::IntoRawFd;
526        f.into_raw_fd()
527    });
528
529    Ok(())
530}
531
532/// Generic function to run ublk async uring tasks with local executor
533fn ublk_uring_run_async_task<T, F, Fut>(task: Fut) -> Result<T, UblkError>
534where
535    F: std::future::Future<Output = Result<T, UblkError>>,
536    Fut: FnOnce() -> F,
537{
538    let exe_rc = Rc::new(smol::LocalExecutor::new());
539    let task_done = Rc::new(std::cell::RefCell::new(false));
540    let task_done_clone = task_done.clone();
541    let exe = exe_rc.clone();
542
543    // Create the main task with the provided async block/closure
544    let main_task = exe.spawn(async move {
545        let result = task().await;
546        *task_done_clone.borrow_mut() = true;
547        result
548    });
549
550    // Create the event handling task
551    let exe2 = exe_rc.clone();
552    let event_task = exe_rc.spawn(async move {
553        let run_ops = || {
554            while exe2.try_tick() {}
555        };
556        let is_done = || *task_done.borrow();
557        poll_and_handle_rings(run_ops, is_done, true).await
558    });
559
560    // Run both tasks concurrently
561    smol::block_on(exe_rc.run(async {
562        let (task_result, _) = futures::join!(main_task, event_task);
563        task_result
564    }))
565}
566
567/// Create UblkCtrl using UblkCtrlBuilder::build_async() with smol executor
568fn create_ublk_ctrl_async(
569    dev_id: i32,
570    dev_flags: UblkFlags,
571    ctrl_flags: u64,
572) -> Result<UblkCtrlAsync, UblkError> {
573    ublk_uring_run_async_task(|| async move {
574        libublk::ctrl::UblkCtrlBuilder::default()
575            .name("test_block_device")
576            .id(dev_id)
577            .nr_queues(1_u16)
578            .depth(128_u16)
579            .dev_flags(dev_flags)
580            .ctrl_flags(ctrl_flags)
581            .build_async()
582            .await
583    })
584}
585
586fn run_device<F>(
587    dev_id: i32,
588    size: u64,
589    state: &mut data_pattern::TestBdState,
590    ctrl_flags: u64,
591    segments: Vec<SegmentInfo>,
592    on_ready: Option<F>,
593) -> Result<i32, UblkError>
594where
595    F: FnOnce(i32, Vec<SegmentInfo>) + 'static,
596{
597    log::info!(
598        "run_device called: dev_id={}, size={}, ctrl_flags={:#x}, segments={}",
599        dev_id,
600        size,
601        ctrl_flags,
602        segments.len()
603    );
604
605    let dev_flags = UblkFlags::UBLK_DEV_F_ADD_DEV;
606
607    // Initialize control ring for this thread
608    libublk::ctrl::ublk_init_ctrl_task_ring(|ring_opt| {
609        if ring_opt.is_none() {
610            log::debug!(
611                "run_device: Creating new control task ring for device {}",
612                dev_id
613            );
614            let ring = IoUring::<io_uring::squeue::Entry128>::builder()
615                .setup_cqsize(128)
616                .setup_coop_taskrun()
617                .build(128)
618                .map_err(UblkError::IOError)?;
619            *ring_opt = Some(ring);
620        }
621        Ok(())
622    })?;
623
624    log::debug!("run_device: Initializing task ring for device {}", dev_id);
625    // Initialize task ring for this thread
626    libublk::io::ublk_init_task_ring(|cell| {
627        use std::cell::RefCell;
628        if cell.get().is_none() {
629            log::debug!("run_device: Creating new task ring for device {}", dev_id);
630            let ring = IoUring::<io_uring::squeue::Entry, io_uring::cqueue::Entry>::builder()
631                .setup_cqsize(128)
632                .setup_coop_taskrun()
633                .build(128)
634                .map_err(|e| {
635                    log::error!(
636                        "run_device: Failed to build task ring for device {}: {}. \
637                        This likely indicates: \
638                        (1) System limit on io_uring instances reached (check /proc/sys/kernel/io_uring/max_*), \
639                        (2) Insufficient locked memory (check ulimit -l), \
640                        (3) Too many open file descriptors (check ulimit -n), \
641                        (4) Kernel resource exhaustion",
642                        dev_id, e
643                    );
644                    UblkError::IOError(e)
645                })?;
646
647            cell.set(RefCell::new(ring))
648                .map_err(|_| {
649                    log::error!("run_device: Failed to set task ring cell for device {} (EEXIST)", dev_id);
650                    UblkError::OtherError(-libc::EEXIST)
651                })?;
652            log::debug!("run_device: Task ring created successfully for device {}", dev_id);
653        } else {
654            log::debug!("run_device: Task ring already exists for device {}", dev_id);
655        }
656        Ok(())
657    }).map_err(|e| {
658        log::error!("run_device: ublk_init_task_ring failed for device {}: {:?}", dev_id, e);
659        e
660    })?;
661
662    // Create the control using the generic async task runner
663    log::debug!(
664        "run_device: Creating ublk control (requested dev_id={})",
665        dev_id
666    );
667    let ctrl = match create_ublk_ctrl_async(dev_id, dev_flags, ctrl_flags) {
668        Ok(c) => Rc::new(c),
669        Err(e) => {
670            log::error!("Failed to create ublk control device {}: {}", dev_id, e);
671            return Err(e);
672        }
673    };
674
675    let actual_dev_id = ctrl.dev_info().dev_id;
676    log::info!(
677        "run_device: Actual device ID assigned: {} (requested was {})",
678        actual_dev_id,
679        dev_id
680    );
681
682    let tgt_init = |dev: &mut UblkDev| {
683        dev.set_default_params(size);
684        Ok(())
685    };
686    log::debug!("run_device: Creating UblkDev for device {}", actual_dev_id);
687    let dev_rc = match UblkDev::new_async(ctrl.get_name(), tgt_init, &ctrl) {
688        Ok(d) => Arc::new(d),
689        Err(e) => {
690            log::error!("Failed to create ublk device {}: {}", actual_dev_id, e);
691            return Err(e);
692        }
693    };
694    let dev_clone = dev_rc.clone();
695    log::debug!(
696        "run_device: Creating UblkQueue for device {}",
697        actual_dev_id
698    );
699    let q_rc = match UblkQueue::new(0, &dev_clone) {
700        Ok(q) => Rc::new(q),
701        Err(e) => {
702            log::error!(
703                "Failed to create ublk queue for device {}: {}",
704                actual_dev_id,
705                e
706            );
707            return Err(e);
708        }
709    };
710    log::debug!(
711        "run_device: UblkQueue created successfully for device {}",
712        actual_dev_id
713    );
714    let exec_rc = Rc::new(smol::LocalExecutor::new());
715    let exec = exec_rc.clone();
716
717    // spawn async io tasks
718    let mut f_vec = Vec::new();
719
720    for tag in 0..ctrl.dev_info().queue_depth as u16 {
721        let q_clone = q_rc.clone();
722
723        let mut t_c = state.clone();
724        f_vec.push(exec.spawn(async move {
725            match io_task(&q_clone, tag, &mut t_c).await {
726                Err(UblkError::QueueIsDown) | Ok(_) => {}
727                Err(e) => log::warn!("io_task failed for tag {}: {}", tag, e),
728            }
729        }));
730    }
731
732    let ctrl_clone = ctrl.clone();
733    let dev_clone = dev_rc.clone();
734    let ready_callback = Rc::new(std::cell::RefCell::new(on_ready));
735    let ready_callback_clone = ready_callback.clone();
736    let segments_clone = segments.clone();
737    f_vec.push(exec.spawn(async move {
738        match ctrl_clone
739            .configure_queue_async(&dev_clone, 0, unsafe { libc::gettid() })
740            .await
741        {
742            Ok(r) if r >= 0 => match ctrl_clone.start_dev_async(&dev_clone).await {
743                Ok(_) => {
744                    log::info!("Device {} started successfully", actual_dev_id);
745                    // Call the ready callback if provided
746                    if let Some(callback) = ready_callback_clone.borrow_mut().take() {
747                        callback(actual_dev_id as i32, segments_clone);
748                    }
749                }
750                Err(e) => {
751                    log::error!("Failed to start device: {}", e);
752                }
753            },
754            Ok(r) => {
755                log::error!("configure_queue_async returned error code: {}", r);
756            }
757            Err(e) => {
758                log::error!("Failed to configure queue: {}", e);
759            }
760        }
761    }));
762    log::debug!(
763        "run_device: Entering smol::block_on executor loop for device {}",
764        actual_dev_id
765    );
766    smol::block_on(exec_rc.run(async move {
767        let run_ops = || while exec.try_tick() {};
768        let done = || {
769            let all_finished = f_vec.iter().all(|task| task.is_finished());
770            if !all_finished {
771                let unfinished_count = f_vec.iter().filter(|task| !task.is_finished()).count();
772                log::debug!(
773                    "run_device: Waiting for {} tasks to finish (out of {} total)",
774                    unfinished_count,
775                    f_vec.len()
776                );
777            }
778            all_finished
779        };
780
781        log::debug!(
782            "run_device: Calling poll_and_handle_rings for device {}",
783            actual_dev_id
784        );
785        if let Err(e) = poll_and_handle_rings(run_ops, done, false).await {
786            log::error!("poll_and_handle_rings failed: {}", e);
787        }
788        log::debug!(
789            "run_device: poll_and_handle_rings completed for device {}",
790            actual_dev_id
791        );
792    }));
793
794    log::debug!(
795        "run_device: Exited smol::block_on for device {}, returning Ok",
796        actual_dev_id
797    );
798    Ok(actual_dev_id as i32)
799}
800
801/// Main API for creating and managing test block devices.
802///
803/// `TestBlockDevice` provides static methods for creating, deleting,
804/// and inspecting ublk-based test block devices with procedurally generated data.
805///
806/// # Examples
807///
808/// ```no_run
809/// use test_bd::{TestBlockDevice, TestBlockDeviceConfig};
810///
811/// let config = TestBlockDeviceConfig {
812///     dev_id: -1,
813///     size: 1024 * 1024 * 1024,  // 1 GiB
814///     seed: 42,
815///     fill_percent: 40,
816///     duplicate_percent: 30,
817///     random_percent: 30,
818///     segments: 100,
819///     unprivileged: false,
820/// };
821///
822/// // This will block until the device is stopped
823/// match TestBlockDevice::run(config) {
824///     Ok(dev_id) => println!("Device created: /dev/ublkb{}", dev_id),
825///     Err(e) => eprintln!("Error: {}", e),
826/// }
827/// ```
828pub struct TestBlockDevice;
829
830impl TestBlockDevice {
831    /// Creates and runs a test block device with the given configuration.
832    ///
833    /// This method blocks until the device is stopped (e.g., via `delete()`).
834    /// The device will appear as `/dev/ublkb{dev_id}` and can be used like
835    /// any other block device.
836    ///
837    /// # Arguments
838    ///
839    /// * `config` - Configuration specifying device size, data patterns, etc.
840    ///
841    /// # Returns
842    ///
843    /// - `Ok(i32)` - The assigned device ID on success
844    /// - `Err(String)` - Error message if device creation fails
845    ///
846    /// # Examples
847    ///
848    /// ```no_run
849    /// use test_bd::{TestBlockDevice, TestBlockDeviceConfig};
850    ///
851    /// let config = TestBlockDeviceConfig {
852    ///     dev_id: -1,
853    ///     size: 10 * 1024 * 1024,  // 10 MiB
854    ///     seed: 42,
855    ///     fill_percent: 50,
856    ///     duplicate_percent: 25,
857    ///     random_percent: 25,
858    ///     segments: 20,
859    ///     unprivileged: false,
860    /// };
861    ///
862    /// let dev_id = TestBlockDevice::run(config).expect("Failed to create device");
863    /// println!("Created device: /dev/ublkb{}", dev_id);
864    /// ```
865    pub fn run(config: TestBlockDeviceConfig) -> Result<i32, String> {
866        Self::run_with_callback(config, None::<fn(i32, Vec<SegmentInfo>)>)
867    }
868
869    /// Creates and runs a test block device with a callback when ready.
870    ///
871    /// Similar to `run()`, but invokes a callback function once the device
872    /// is ready for I/O operations. This is useful for coordinating with
873    /// other tasks that need to wait for device initialization.
874    ///
875    /// # Arguments
876    ///
877    /// * `config` - Configuration specifying device size, data patterns, etc.
878    /// * `on_ready` - Optional callback invoked with device ID and segment info
879    ///
880    /// # Returns
881    ///
882    /// - `Ok(i32)` - The assigned device ID on success
883    /// - `Err(String)` - Error message if device creation fails
884    ///
885    /// # Examples
886    ///
887    /// ```no_run
888    /// use test_bd::{TestBlockDevice, TestBlockDeviceConfig, SegmentInfo};
889    ///
890    /// let config = TestBlockDeviceConfig {
891    ///     dev_id: -1,
892    ///     size: 10 * 1024 * 1024,
893    ///     seed: 42,
894    ///     fill_percent: 50,
895    ///     duplicate_percent: 25,
896    ///     random_percent: 25,
897    ///     segments: 20,
898    ///     unprivileged: false,
899    /// };
900    ///
901    /// TestBlockDevice::run_with_callback(config, Some(|dev_id, segments: Vec<SegmentInfo>| {
902    ///     println!("Device {} ready with {} segments", dev_id, segments.len());
903    /// })).expect("Failed to create device");
904    /// ```
905    pub fn run_with_callback<F>(
906        config: TestBlockDeviceConfig,
907        on_ready: Option<F>,
908    ) -> Result<i32, String>
909    where
910        F: FnOnce(i32, Vec<SegmentInfo>) + 'static,
911    {
912        config.validate()?;
913
914        let percents = config.percent_pattern();
915        let (pattern_gen, mapping) =
916            data_pattern::DataMix::create(config.size, config.seed, config.segments, &percents);
917        let m = Mutex::new(pattern_gen);
918        let mut state = data_pattern::TestBdState { s: Rc::new(m) };
919
920        // Convert mapping to SegmentInfo
921        let segments: Vec<SegmentInfo> = mapping
922            .into_iter()
923            .map(|(range, bucket)| SegmentInfo {
924                start: range.start,
925                end: range.end,
926                pattern: bucket,
927            })
928            .collect();
929
930        let ctrl_flags = if config.unprivileged {
931            libublk::sys::UBLK_F_UNPRIVILEGED_DEV as u64
932        } else {
933            0
934        };
935
936        run_device(
937            config.dev_id,
938            config.size,
939            &mut state,
940            ctrl_flags,
941            segments,
942            on_ready,
943        )
944        .map_err(|e| format!("Failed to run device: {}", e))
945    }
946
947    /// Deletes (stops and removes) a test block device.
948    ///
949    /// This stops the device and removes it from the system. The device node
950    /// `/dev/ublkb{dev_id}` will be removed.
951    ///
952    /// # Arguments
953    ///
954    /// * `dev_id` - The device ID to delete
955    /// * `_async_del` - Reserved for future use (currently ignored)
956    ///
957    /// # Returns
958    ///
959    /// - `Ok(())` on success
960    /// - `Err(String)` if deletion fails
961    ///
962    /// # Examples
963    ///
964    /// ```no_run
965    /// use test_bd::TestBlockDevice;
966    ///
967    /// // Delete device 0
968    /// TestBlockDevice::delete(0, false).expect("Failed to delete device");
969    /// ```
970    pub fn delete(dev_id: i32, _async_del: bool) -> Result<(), String> {
971        log::debug!(
972            "TestBlockDevice::delete ENTRY: dev_id={}, async={}",
973            dev_id,
974            _async_del
975        );
976
977        log::debug!(
978            "TestBlockDevice::delete: Creating UblkCtrl for device {}",
979            dev_id
980        );
981        let ctrl = UblkCtrl::new_simple(dev_id).map_err(|e| {
982            let err_msg = format!(
983                "Failed to open device {} for deletion: {}. \
984                    This may indicate: \
985                    (1) device doesn't exist, \
986                    (2) insufficient permissions, \
987                    (3) control ring initialization failed (possibly too many open devices/rings), \
988                    (4) resource exhaustion",
989                dev_id, e
990            );
991            log::error!("{}", err_msg);
992            err_msg
993        })?;
994        log::debug!(
995            "TestBlockDevice::delete: UblkCtrl created successfully for device {}",
996            dev_id
997        );
998
999        log::debug!(
1000            "TestBlockDevice::delete: Calling ctrl.kill_dev() for device {}",
1001            dev_id
1002        );
1003        ctrl.kill_dev().map_err(|e| {
1004            let err_msg = format!("Failed to kill device {}: {}", dev_id, e);
1005            log::error!("{}", err_msg);
1006            err_msg
1007        })?;
1008        log::debug!(
1009            "TestBlockDevice::delete: ctrl.kill_dev() completed for device {}",
1010            dev_id
1011        );
1012
1013        log::debug!("TestBlockDevice::delete: del_dev {dev_id}");
1014        ctrl.del_dev().map_err(|e| {
1015            let err_msg = format!("del_dev({}) failed {}", dev_id, e);
1016            log::error!("{}", err_msg);
1017            err_msg
1018        })?;
1019
1020        log::debug!("TestBlockDevice::delete: del_dev complete {dev_id}");
1021
1022        Ok(())
1023    }
1024
1025    /// Dumps information about a test block device to the log.
1026    ///
1027    /// This is primarily useful for debugging and displays internal device state.
1028    ///
1029    /// # Arguments
1030    ///
1031    /// * `dev_id` - The device ID to inspect
1032    ///
1033    /// # Returns
1034    ///
1035    /// - `Ok(())` on success
1036    /// - `Err(String)` if the device cannot be opened
1037    ///
1038    /// # Examples
1039    ///
1040    /// ```no_run
1041    /// use test_bd::TestBlockDevice;
1042    ///
1043    /// TestBlockDevice::dump(0).expect("Failed to dump device info");
1044    /// ```
1045    pub fn dump(dev_id: i32) -> Result<(), String> {
1046        let ctrl = UblkCtrl::new_simple(dev_id)
1047            .map_err(|e| format!("Failed to open device {}: {}", dev_id, e))?;
1048        ctrl.dump();
1049        Ok(())
1050    }
1051}
1052
1053/// A handle to a managed test block device.
1054///
1055/// This struct is returned by `DeviceManager::create()` and contains
1056/// information about a running device including its ID, configuration,
1057/// and segment layout.
1058///
1059/// # Examples
1060///
1061/// ```no_run
1062/// use test_bd::{DeviceManager, TestBlockDeviceConfig};
1063///
1064/// let mut manager = DeviceManager::new();
1065/// let config = TestBlockDeviceConfig {
1066///     dev_id: -1,
1067///     size: 10 * 1024 * 1024,
1068///     seed: 42,
1069///     fill_percent: 50,
1070///     duplicate_percent: 25,
1071///     random_percent: 25,
1072///     segments: 20,
1073///     unprivileged: false,
1074/// };
1075///
1076/// let device = manager.create(config).expect("Failed to create device");
1077/// println!("Created device {} with {} segments", device.dev_id, device.segments.len());
1078/// ```
1079#[derive(Debug, Clone)]
1080pub struct ManagedDevice {
1081    /// The assigned device ID.
1082    pub dev_id: i32,
1083
1084    /// The configuration used to create this device.
1085    pub config: TestBlockDeviceConfig,
1086
1087    /// Information about each segment in the device.
1088    pub segments: Vec<SegmentInfo>,
1089}
1090
1091/// Manager for multiple test block devices.
1092///
1093/// `DeviceManager` provides a higher-level API for managing multiple test block
1094/// devices. It handles device creation in background threads and automatic cleanup
1095/// on drop. This is the recommended way to create devices when you need to manage
1096/// their lifecycle.
1097///
1098/// # Examples
1099///
1100/// ```no_run
1101/// use test_bd::{DeviceManager, TestBlockDeviceConfig};
1102///
1103/// let mut manager = DeviceManager::new();
1104///
1105/// // Create first device
1106/// let config1 = TestBlockDeviceConfig {
1107///     dev_id: -1,
1108///     size: 10 * 1024 * 1024,
1109///     seed: 42,
1110///     fill_percent: 50,
1111///     duplicate_percent: 25,
1112///     random_percent: 25,
1113///     segments: 20,
1114///     unprivileged: false,
1115/// };
1116/// let device1 = manager.create(config1).expect("Failed to create device 1");
1117///
1118/// // Create second device
1119/// let config2 = TestBlockDeviceConfig {
1120///     dev_id: -1,
1121///     size: 20 * 1024 * 1024,
1122///     seed: 123,
1123///     fill_percent: 33,
1124///     duplicate_percent: 33,
1125///     random_percent: 34,
1126///     segments: 50,
1127///     unprivileged: false,
1128/// };
1129/// let device2 = manager.create(config2).expect("Failed to create device 2");
1130///
1131/// // List all managed devices
1132/// for device in manager.list() {
1133///     println!("Device {}: {} bytes", device.dev_id, device.config.size);
1134/// }
1135///
1136/// // Delete a specific device
1137/// manager.delete(device1.dev_id).expect("Failed to delete device");
1138///
1139/// // All remaining devices are automatically deleted when manager is dropped
1140/// ```
1141pub struct DeviceManager {
1142    devices: HashMap<i32, (JoinHandle<Result<i32, String>>, ManagedDevice)>,
1143}
1144
1145type SegInfo = (i32, Vec<SegmentInfo>);
1146
1147impl DeviceManager {
1148    /// Creates a new `DeviceManager`.
1149    ///
1150    /// # Examples
1151    ///
1152    /// ```
1153    /// use test_bd::DeviceManager;
1154    ///
1155    /// let manager = DeviceManager::new();
1156    /// ```
1157    pub fn new() -> Self {
1158        Self {
1159            devices: HashMap::new(),
1160        }
1161    }
1162
1163    /// Creates a new test block device and manages it.
1164    ///
1165    /// The device is created in a background thread and this method blocks until
1166    /// the device is ready for I/O. The device will continue running in the
1167    /// background until explicitly deleted or the manager is dropped.
1168    ///
1169    /// # Arguments
1170    ///
1171    /// * `config` - Configuration for the device
1172    ///
1173    /// # Returns
1174    ///
1175    /// - `Ok(ManagedDevice)` - Handle to the created device
1176    /// - `Err(String)` - Error message if creation fails
1177    ///
1178    /// # Examples
1179    ///
1180    /// ```no_run
1181    /// use test_bd::{DeviceManager, TestBlockDeviceConfig};
1182    ///
1183    /// let mut manager = DeviceManager::new();
1184    /// let config = TestBlockDeviceConfig {
1185    ///     dev_id: -1,
1186    ///     size: 10 * 1024 * 1024,
1187    ///     seed: 42,
1188    ///     fill_percent: 50,
1189    ///     duplicate_percent: 25,
1190    ///     random_percent: 25,
1191    ///     segments: 20,
1192    ///     unprivileged: false,
1193    /// };
1194    ///
1195    /// let device = manager.create(config).expect("Failed to create device");
1196    /// println!("Created /dev/ublkb{}", device.dev_id);
1197    /// ```
1198    pub fn create(&mut self, config: TestBlockDeviceConfig) -> Result<ManagedDevice, String> {
1199        // Validate configuration first
1200        config.validate()?;
1201
1202        // Create a channel for the device to signal when it's ready
1203        let (tx, rx): (Sender<SegInfo>, Receiver<SegInfo>) = mpsc::channel();
1204
1205        // Clone config for use in thread
1206        let config_clone = config.clone();
1207
1208        // Spawn a thread to run the device
1209        let handle = thread::Builder::new()
1210            .name(format!(
1211                "test-bd-{}",
1212                if config.dev_id >= 0 {
1213                    config.dev_id.to_string()
1214                } else {
1215                    "auto".to_string()
1216                }
1217            ))
1218            .spawn(move || {
1219                log::debug!("Device thread started for dev_id={}", config_clone.dev_id);
1220                let result = TestBlockDevice::run_with_callback(
1221                    config_clone.clone(),
1222                    Some(move |dev_id, segments| {
1223                        log::debug!("Device {} ready callback invoked", dev_id);
1224                        // Notify the main thread that the device is ready
1225                        let _ = tx.send((dev_id, segments));
1226                        drop(tx);
1227                    }),
1228                );
1229                log::debug!("Device thread: run_with_callback returned with result: {:?}", result.is_ok());
1230                log::debug!("Device thread: About to return from closure (this is the last line before thread exit)");
1231                result
1232            })
1233            .map_err(|e| format!("Failed to spawn device thread: {}", e))?;
1234
1235        // Wait for the device to be ready
1236        let (dev_id, segments) = rx.recv().map_err(|_| {
1237            log::error!("Failed to receive device ready signal from thread");
1238            "Failed to receive device ready signal from thread. \
1239                The device thread may have panicked or failed to start."
1240                .to_string()
1241        })?;
1242
1243        drop(rx);
1244
1245        log::debug!("Received ready signal for device {}", dev_id);
1246
1247        let managed_device = ManagedDevice {
1248            dev_id,
1249            config,
1250            segments,
1251        };
1252
1253        // Store the thread handle and device info
1254        self.devices
1255            .insert(dev_id, (handle, managed_device.clone()));
1256
1257        log::debug!(
1258            "Device {} successfully created and added to manager",
1259            dev_id
1260        );
1261
1262        // Run udevadm settle to ensure udev has finished processing the device
1263        // This prevents race conditions when creating multiple devices rapidly
1264        log::debug!(
1265            "Running udevadm settle for device {} with 10 second timeout...",
1266            dev_id
1267        );
1268        let settle_start = std::time::Instant::now();
1269        match std::process::Command::new("udevadm")
1270            .arg("settle")
1271            .arg("--timeout=10")
1272            .output()
1273        {
1274            Ok(output) => {
1275                let settle_duration = settle_start.elapsed();
1276                if output.status.success() {
1277                    log::debug!(
1278                        "udevadm settle completed successfully for device {} in {:?}",
1279                        dev_id,
1280                        settle_duration
1281                    );
1282                } else {
1283                    log::warn!(
1284                        "udevadm settle exited with status {} for device {} after {:?}: {}",
1285                        output.status,
1286                        dev_id,
1287                        settle_duration,
1288                        String::from_utf8_lossy(&output.stderr)
1289                    );
1290                }
1291            }
1292            Err(e) => {
1293                let settle_duration = settle_start.elapsed();
1294                log::warn!(
1295                    "Failed to run udevadm settle for device {} after {:?}: {}. \
1296                    This may cause issues when creating multiple devices rapidly. \
1297                    Continuing anyway.",
1298                    dev_id,
1299                    settle_duration,
1300                    e
1301                );
1302            }
1303        }
1304        log::debug!(
1305            "Finished udevadm settle for device {}, returning from create()",
1306            dev_id
1307        );
1308
1309        Ok(managed_device)
1310    }
1311
1312    /// Returns a list of all currently managed devices.
1313    ///
1314    /// # Examples
1315    ///
1316    /// ```no_run
1317    /// use test_bd::{DeviceManager, TestBlockDeviceConfig};
1318    ///
1319    /// let mut manager = DeviceManager::new();
1320    /// let config = TestBlockDeviceConfig {
1321    ///     dev_id: -1,
1322    ///     size: 10 * 1024 * 1024,
1323    ///     seed: 42,
1324    ///     fill_percent: 50,
1325    ///     duplicate_percent: 25,
1326    ///     random_percent: 25,
1327    ///     segments: 20,
1328    ///     unprivileged: false,
1329    /// };
1330    ///
1331    /// manager.create(config).expect("Failed to create device");
1332    ///
1333    /// for device in manager.list() {
1334    ///     println!("Device {}: {} segments", device.dev_id, device.segments.len());
1335    /// }
1336    /// ```
1337    pub fn list(&self) -> Vec<ManagedDevice> {
1338        self.devices
1339            .values()
1340            .map(|(_, device)| device.clone())
1341            .collect()
1342    }
1343
1344    /// Deletes a specific managed device.
1345    ///
1346    /// This stops and removes the device, then waits for the background thread
1347    /// to complete. The device is removed from the manager's tracking.
1348    ///
1349    /// # Arguments
1350    ///
1351    /// * `dev_id` - The device ID to delete
1352    ///
1353    /// # Returns
1354    ///
1355    /// - `Ok(i32)` - The device ID that was deleted
1356    /// - `Err(String)` - Error message if deletion fails
1357    ///
1358    /// # Examples
1359    ///
1360    /// ```no_run
1361    /// use test_bd::{DeviceManager, TestBlockDeviceConfig};
1362    ///
1363    /// let mut manager = DeviceManager::new();
1364    /// let config = TestBlockDeviceConfig {
1365    ///     dev_id: -1,
1366    ///     size: 10 * 1024 * 1024,
1367    ///     seed: 42,
1368    ///     fill_percent: 50,
1369    ///     duplicate_percent: 25,
1370    ///     random_percent: 25,
1371    ///     segments: 20,
1372    ///     unprivileged: false,
1373    /// };
1374    ///
1375    /// let device = manager.create(config).expect("Failed to create device");
1376    /// manager.delete(device.dev_id).expect("Failed to delete device");
1377    /// ```
1378    pub fn delete(&mut self, dev_id: i32) -> Result<i32, String> {
1379        log::debug!("DeviceManager::delete called for device {}", dev_id);
1380
1381        // Remove the device from our tracking
1382        let (handle, _) = self.devices.remove(&dev_id).ok_or_else(|| {
1383            let err_msg = format!(
1384                "Device {} is not managed by this DeviceManager. \
1385                    Currently managing {} devices: {:?}",
1386                dev_id,
1387                self.devices.len(),
1388                self.devices.keys().collect::<Vec<_>>()
1389            );
1390            log::error!("{}", err_msg);
1391            err_msg
1392        })?;
1393
1394        // Delete the device (this will cause the run() call to exit)
1395        TestBlockDevice::delete(dev_id, false).map_err(|e| {
1396            let err_msg = format!(
1397                "Failed to delete device {}: {}. \
1398                    Note: Thread will still be joined if possible.",
1399                dev_id, e
1400            );
1401            log::warn!("{}", err_msg);
1402            err_msg
1403        })?;
1404
1405        handle.join().map_err(|e| {
1406            let err_msg = format!("Error on join {}: {:?}", dev_id, e);
1407            log::warn!("{}", err_msg);
1408            err_msg
1409        })?
1410    }
1411
1412    /// Deletes all managed devices.
1413    ///
1414    /// This stops and removes all devices being managed. Devices are deleted
1415    /// sequentially. If any deletion fails, the method continues attempting to
1416    /// delete remaining devices and returns an error at the end.
1417    ///
1418    /// # Returns
1419    ///
1420    /// - `Ok(())` if all devices were deleted successfully
1421    /// - `Err(String)` if any deletions failed (with details about the first failure)
1422    ///
1423    /// # Examples
1424    ///
1425    /// ```no_run
1426    /// use test_bd::{DeviceManager, TestBlockDeviceConfig};
1427    ///
1428    /// let mut manager = DeviceManager::new();
1429    ///
1430    /// // Create multiple devices
1431    /// for i in 0..3 {
1432    ///     let config = TestBlockDeviceConfig {
1433    ///         dev_id: -1,
1434    ///         size: 10 * 1024 * 1024,
1435    ///         seed: i,
1436    ///         fill_percent: 50,
1437    ///         duplicate_percent: 25,
1438    ///         random_percent: 25,
1439    ///         segments: 20,
1440    ///         unprivileged: false,
1441    ///     };
1442    ///     manager.create(config).expect("Failed to create device");
1443    /// }
1444    ///
1445    /// // Delete all devices
1446    /// manager.delete_all().expect("Failed to delete all devices");
1447    /// ```
1448    pub fn delete_all(&mut self) -> Result<(), String> {
1449        let dev_ids: Vec<i32> = self.devices.keys().copied().collect();
1450        log::info!(
1451            "DeviceManager::delete_all called for {} devices: {:?}",
1452            dev_ids.len(),
1453            dev_ids
1454        );
1455
1456        let mut first_error = None;
1457        let mut deleted_count = 0;
1458        let mut failed_count = 0;
1459
1460        for (index, dev_id) in dev_ids.iter().enumerate() {
1461            log::debug!(
1462                "Deleting device {}/{}: dev_id={}",
1463                index + 1,
1464                dev_ids.len(),
1465                dev_id
1466            );
1467
1468            match self.delete(*dev_id) {
1469                Ok(rc) => {
1470                    deleted_count += 1;
1471                    log::debug!(
1472                        "Successfully deleted device {} ({}/{}) with return code {}",
1473                        dev_id,
1474                        deleted_count,
1475                        dev_ids.len(),
1476                        rc
1477                    );
1478                }
1479                Err(e) => {
1480                    failed_count += 1;
1481                    log::error!(
1482                        "Failed to delete device {} ({}/{}): {}",
1483                        dev_id,
1484                        index + 1,
1485                        dev_ids.len(),
1486                        e
1487                    );
1488                    if first_error.is_none() {
1489                        first_error = Some(e);
1490                    }
1491                }
1492            }
1493        }
1494
1495        log::info!(
1496            "DeviceManager::delete_all completed: {} succeeded, {} failed out of {} total",
1497            deleted_count,
1498            failed_count,
1499            dev_ids.len()
1500        );
1501
1502        if let Some(e) = first_error {
1503            Err(format!(
1504                "Failed to delete all devices: {} succeeded, {} failed. First error: {}",
1505                deleted_count, failed_count, e
1506            ))
1507        } else {
1508            Ok(())
1509        }
1510    }
1511}
1512
1513impl Default for DeviceManager {
1514    fn default() -> Self {
1515        Self::new()
1516    }
1517}
1518
1519impl Drop for DeviceManager {
1520    fn drop(&mut self) {
1521        let _ = self.delete_all();
1522    }
1523}