usbh_fatfs/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::io::{Read, Seek, SeekFrom, Write};
4
5use fatfs::FatType;
6use rusb::{Device, GlobalContext};
7use thiserror::Error;
8use usbh_scsi::storage::{
9    Closed, Opened, UsbMassStorage, UsbMassStorageError, UsbMassStorageReadWriteError,
10};
11
12/// Re-export of the `bootsector` crate for partition parsing.
13pub use bootsector;
14/// Re-export of the `fatfs` crate for FAT filesystem operations.
15pub use fatfs;
16/// Re-export of the `rusb` crate for raw USB device handling.
17pub use rusb;
18
19/// Represents a USB mass-storage device connected to the system.
20///
21/// Holds both the USB device handle (`rusb::Device`) and
22/// the current state of its storage interface (`StorageUsbInner`).
23#[derive(Debug)]
24pub struct StorageUsb {
25    pub inner: StorageUsbInner,
26    pub usb_device: Device<GlobalContext>,
27}
28
29/// Represents the state of a `StorageUsb` device.
30///
31/// - `Closed`: The device is detected but not yet opened for I/O.
32/// - `Opened`: The device is ready for block-level access.
33/// - `ClosedDummy`: Temporary placeholder state during transitions.
34#[derive(Debug)]
35pub enum StorageUsbInner {
36    Closed(UsbMassStorage<Closed>),
37    Opened(UsbMassStorage<Opened>),
38    ClosedDummy,
39}
40
41/// Errors that can occur when working with [`StorageUsb`] or partitions.
42#[derive(Error, Debug)]
43pub enum StorageUsbError {
44    /// Wrapper around `UsbMassStorageError` (device communication issues).
45    #[error("usb mass storage error")]
46    UsbMassStorageError(#[from] UsbMassStorageError),
47
48    /// Failed to open a block device interface.
49    #[error("failed to open as block device")]
50    BlockDeviceOpenFail,
51
52    /// Partition listing failed (invalid or unreadable partition table).
53    #[error("listing partitions failed")]
54    ListingPartitionFail,
55}
56
57impl StorageUsb {
58    /// List all connected USB mass-storage devices and wrap them in [`StorageUsb`].
59    ///
60    /// Returns a vector of `StorageUsb` instances, all starting in the `Closed` state.
61    pub fn list_usbs() -> Result<Vec<Self>, StorageUsbError> {
62        let usbs: Vec<_> = UsbMassStorage::list()?
63            .into_iter()
64            .map(|usb| {
65                let device = usb.device.clone();
66
67                Self {
68                    inner: StorageUsbInner::Closed(usb),
69                    usb_device: device,
70                }
71            })
72            .collect();
73
74        Ok(usbs)
75    }
76
77    /// Open the USB mass-storage device for I/O.
78    ///
79    /// If the device is already open, it will simply return the existing `Opened` instance.
80    ///
81    /// Returns a mutable reference to the `UsbMassStorage<Opened>` object for performing block I/O.
82    pub fn open(&mut self) -> Result<&mut UsbMassStorage<Opened>, StorageUsbError> {
83        // Take ownership safely by swapping with None
84        let inner = std::mem::replace(&mut self.inner, StorageUsbInner::ClosedDummy);
85        self.inner = match inner {
86            StorageUsbInner::Closed(closed) => StorageUsbInner::Opened(closed.open()?),
87            opened @ StorageUsbInner::Opened(_) => opened,
88            _ => unreachable!(),
89        };
90
91        match &mut self.inner {
92            StorageUsbInner::Opened(opened) => Ok(opened),
93            _ => unreachable!(),
94        }
95    }
96}
97
98/// Represents a FAT partition discovered on a USB mass-storage device.
99#[derive(Debug, Clone)]
100pub struct FatPartition {
101    /// Underlying raw partition information from `bootsector`.
102    pub inner: bootsector::Partition,
103    /// Volume ID of the FAT filesystem.
104    pub volume_id: u32,
105    /// Volume label string.
106    pub volume_label: String,
107    /// FAT type (e.g., FAT12, FAT16, FAT32).
108    pub fat_type: FatType,
109    /// Cluster size in bytes.
110    pub cluster_size: u32,
111    /// Byte offset of the partition start on the device.
112    pub first_byte: u64,
113    /// Length of the partition in bytes.
114    pub length: u64,
115}
116
117impl FatPartition {
118    /// List FAT partitions on a given USB storage device.
119    ///
120    /// Attempts to:
121    /// 1. Open the device.
122    /// 2. Parse its partition table.
123    /// 3. Mount each partition as a FAT filesystem.
124    ///
125    /// Returns only valid FAT partitions (others are skipped).
126    pub fn list_partitions(usb: &mut StorageUsb) -> Result<Vec<Self>, StorageUsbError> {
127        let opened = usb.open()?;
128
129        let mut block_device = opened
130            .block_device()
131            .map_err(|_| StorageUsbError::BlockDeviceOpenFail)?;
132
133        let partitions =
134            bootsector::list_partitions(&block_device, &bootsector::Options::default())
135                .map_err(|_| StorageUsbError::ListingPartitionFail)?;
136
137        let mut results = Vec::new();
138
139        for partition in partitions {
140            let first_byte = partition.first_byte;
141            let length = partition.len;
142            let view = PartitionView::new(&mut block_device, first_byte, length).unwrap();
143
144            let fs = match fatfs::FileSystem::new(view, fatfs::FsOptions::new()) {
145                Ok(fs) => fs,
146                Err(err) => {
147                    log::debug!("Failed to open as fatfs file system {err:#}");
148                    continue;
149                }
150            };
151
152            results.push(Self {
153                inner: partition,
154                volume_id: fs.volume_id(),
155                volume_label: fs.volume_label(),
156                fat_type: fs.fat_type(),
157                cluster_size: fs.cluster_size(),
158                first_byte: first_byte,
159                length: length,
160            })
161        }
162
163        Ok(results)
164    }
165}
166
167/// Provides a "window" into a block device, restricted to a single partition.
168///
169/// Wraps a seekable/readable/writable device and clamps all operations
170/// so they cannot escape the defined partition region.
171#[derive(Debug)]
172pub struct PartitionView<D> {
173    /// The underlying device (e.g., USB block device).
174    pub inner: D,
175    /// Start offset of the partition in bytes.
176    pub start: u64,
177    /// Length of the partition in bytes.
178    pub len: u64,
179}
180
181impl<D: Seek> PartitionView<D> {
182    /// Create a new `PartitionView` wrapping a device.
183    ///
184    /// Seeks the device to the start of the partition immediately.
185    pub fn new(mut inner: D, start: u64, len: u64) -> Result<Self, FatError> {
186        // Seek the underlying device to partition start so first reads work as expected.
187        inner.seek(SeekFrom::Start(start)).map_err(FatError::from)?;
188        Ok(Self { inner, start, len })
189    }
190
191    /// Return the current relative position within the partition.
192    ///
193    /// Always non-negative and less than or equal to `len`.
194    fn current_rel_pos(&mut self) -> Result<u64, std::io::Error> {
195        let abs = self.inner.seek(SeekFrom::Current(0))?;
196        Ok(abs.saturating_sub(self.start))
197    }
198}
199
200impl<D> PartitionView<D> {
201    /// Clamp a relative offset into the valid partition range `[0, len]`.
202    fn clamp_rel(&self, rel: i128) -> u64 {
203        let len = self.len as i128;
204        rel.clamp(0, len) as u64
205    }
206}
207
208impl<D: Read + Seek> Read for PartitionView<D> {
209    /// Read data from within the partition without crossing its boundaries.
210    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
211        if buf.is_empty() {
212            return Ok(0);
213        }
214        // Ensure we never read past partition end
215        let cur_rel = self.current_rel_pos()?;
216        if cur_rel >= self.len {
217            return Ok(0);
218        }
219        let max_here = (self.len - cur_rel) as usize;
220        let want = buf.len().min(max_here);
221
222        // Issue read — underlying device is already positioned absolutely
223        let n = self.inner.read(&mut buf[..want])?;
224        Ok(n)
225    }
226}
227
228impl<D: Write + Seek> Write for PartitionView<D> {
229    /// Write data to the partition without crossing its boundaries.
230    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
231        if buf.is_empty() {
232            return Ok(0);
233        }
234        // Bound writes to the partition
235        let cur_rel = self.current_rel_pos()?;
236        if cur_rel >= self.len {
237            return Ok(0);
238        }
239        let max_here = (self.len - cur_rel) as usize;
240        let want = buf.len().min(max_here);
241
242        let n = self.inner.write(&buf[..want])?;
243        if n == 0 && want != 0 {
244            return Err(std::io::Error::new(std::io::ErrorKind::Other, "WriteZero"));
245        }
246        Ok(n)
247    }
248
249    fn flush(&mut self) -> std::io::Result<()> {
250        self.inner.flush()
251    }
252}
253
254impl<D: Seek> Seek for PartitionView<D> {
255    /// Seek to a new relative offset within the partition.
256    ///
257    /// Guarantees that the position never moves outside `[0, len]`.
258    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
259        // Compute desired RELATIVE offset within [0, len]
260        let rel_target: u64 = match pos {
261            SeekFrom::Start(o) => self.clamp_rel(o as i128),
262            SeekFrom::End(off) => {
263                let rel = self.len as i128 + off as i128;
264                self.clamp_rel(rel)
265            }
266            SeekFrom::Current(off) => {
267                let cur_rel = self.current_rel_pos()? as i128;
268                self.clamp_rel(cur_rel + off as i128)
269            }
270        };
271
272        // Convert to absolute on the underlying device and seek.
273        let abs = self.start + rel_target;
274        self.inner.seek(SeekFrom::Start(abs))?;
275        Ok(rel_target)
276    }
277}
278
279/// Errors that can occur when reading/writing FAT partitions.
280#[derive(Error, Debug)]
281pub enum FatError {
282    /// Write was interrupted.
283    #[error("write interrupted")]
284    Interrupted,
285
286    /// Unexpected end of file during read.
287    #[error("unexpected end of file")]
288    UnexpectedEof,
289
290    /// Attempted to write zero bytes unexpectedly.
291    #[error("zero write")]
292    WriteZero,
293
294    /// USB-level I/O error during read/write.
295    #[error("usb read/write failed: {0}")]
296    UsbIo(#[from] UsbMassStorageReadWriteError),
297
298    /// Generic I/O error from the standard library.
299    #[error("io error: {0}")]
300    StdIo(#[from] std::io::Error),
301}