Skip to main content

usb_gadget/function/
msd.rs

1//! Mass Storage Device (MSD) function.
2//!
3//! The Linux kernel configuration option `CONFIG_USB_CONFIGFS_MASS_STORAGE` must be enabled.
4
5use std::{
6    ffi::{OsStr, OsString},
7    fs,
8    io::{Error, ErrorKind, Result},
9    os::unix::prelude::OsStrExt,
10    path::{Path, PathBuf},
11};
12
13use super::{
14    util::{FunctionDir, Status},
15    Function, Handle,
16};
17
18pub(crate) fn driver() -> &'static OsStr {
19    OsStr::new("mass_storage")
20}
21
22/// Logical unit (LUN) of mass storage device (MSD).
23#[derive(Debug, Clone)]
24#[non_exhaustive]
25pub struct Lun {
26    /// Flag specifying access to the LUN shall be read-only.
27    ///
28    /// This is implied if CD-ROM emulation is enabled as well as
29    /// when it was impossible to open the backing file in R/W mode.
30    pub read_only: bool,
31    /// Flag specifying that LUN shall be reported as being a CD-ROM.
32    pub cdrom: bool,
33    /// Flag specifying that FUA flag in SCSI WRITE(10,12).
34    pub no_fua: bool,
35    /// Flag specifying that LUN shall be indicated as being removable.
36    pub removable: bool,
37    /// The path to the backing file for the LUN.
38    ///
39    /// Required if LUN is not marked as removable.
40    file: Option<PathBuf>,
41    /// Inquiry string.
42    pub inquiry_string: String,
43}
44
45impl Lun {
46    /// Create a new LUN backed by the specified file.
47    pub fn new(file: impl AsRef<Path>) -> Result<Self> {
48        let mut this = Self::default();
49        this.set_file(Some(file))?;
50        Ok(this)
51    }
52
53    /// Creates a new LUN without a medium.
54    pub fn empty() -> Self {
55        Self::default()
56    }
57
58    /// Set the path to the backing file for the LUN.
59    pub fn set_file<F: AsRef<Path>>(&mut self, file: Option<F>) -> Result<()> {
60        match file {
61            Some(file) => {
62                let file = file.as_ref();
63                if !file.is_absolute() {
64                    return Err(Error::new(ErrorKind::InvalidInput, "the LUN file path must be absolute"));
65                }
66                self.file = Some(file.to_path_buf());
67            }
68            None => self.file = None,
69        }
70
71        Ok(())
72    }
73
74    fn dir_name(idx: usize) -> String {
75        format!("lun.{idx}")
76    }
77}
78
79impl Default for Lun {
80    fn default() -> Self {
81        Self {
82            read_only: false,
83            cdrom: false,
84            no_fua: false,
85            removable: true,
86            file: None,
87            inquiry_string: String::new(),
88        }
89    }
90}
91
92/// Builder for USB Mass Storage Device (MSD) function.
93#[derive(Debug, Clone)]
94#[non_exhaustive]
95pub struct MsdBuilder {
96    /// Set to permit function to halt bulk endpoints.
97    ///
98    /// Disabled on some USB devices known not to work correctly.
99    pub stall: Option<bool>,
100    /// Logical units.
101    pub luns: Vec<Lun>,
102}
103
104impl MsdBuilder {
105    /// Build the USB function.
106    ///
107    /// The returned handle must be added to a USB gadget configuration.
108    #[must_use]
109    pub fn build(self) -> (Msd, Handle) {
110        let dir = FunctionDir::new();
111        (Msd { dir: dir.clone() }, Handle::new(MsdFunction { builder: self, dir }))
112    }
113
114    /// Adds a LUN.
115    pub fn add_lun(&mut self, lun: Lun) {
116        self.luns.push(lun);
117    }
118
119    /// Adds a LUN.
120    #[must_use]
121    pub fn with_lun(mut self, lun: Lun) -> Self {
122        self.add_lun(lun);
123        self
124    }
125}
126
127#[derive(Debug)]
128struct MsdFunction {
129    builder: MsdBuilder,
130    dir: FunctionDir,
131}
132
133impl Function for MsdFunction {
134    fn driver(&self) -> OsString {
135        driver().into()
136    }
137
138    fn dir(&self) -> FunctionDir {
139        self.dir.clone()
140    }
141
142    fn register(&self) -> Result<()> {
143        if self.builder.luns.is_empty() {
144            return Err(Error::new(ErrorKind::InvalidInput, "at least one LUN must exist"));
145        }
146
147        if let Some(stall) = self.builder.stall {
148            self.dir.write("stall", if stall { "1" } else { "0" })?;
149        }
150
151        for (idx, lun) in self.builder.luns.iter().enumerate() {
152            let lun_dir_name = Lun::dir_name(idx);
153
154            if idx != 0 {
155                self.dir.create_dir(&lun_dir_name)?;
156            }
157
158            self.dir.write(format!("{lun_dir_name}/ro"), if lun.read_only { "1" } else { "0" })?;
159            self.dir.write(format!("{lun_dir_name}/cdrom"), if lun.cdrom { "1" } else { "0" })?;
160            self.dir.write(format!("{lun_dir_name}/nofua"), if lun.no_fua { "1" } else { "0" })?;
161            self.dir.write(format!("{lun_dir_name}/removable"), if lun.removable { "1" } else { "0" })?;
162            self.dir.write(format!("{lun_dir_name}/inquiry_string"), &lun.inquiry_string)?;
163            if let Some(file) = &lun.file {
164                self.dir.write(format!("{lun_dir_name}/file"), file.as_os_str().as_bytes())?;
165            }
166        }
167
168        Ok(())
169    }
170}
171
172/// USB Mass Storage Device (MSD) function.
173#[derive(Debug)]
174pub struct Msd {
175    dir: FunctionDir,
176}
177
178impl Msd {
179    /// Creates a new USB Mass Storage Device (MSD) with the specified backing file.
180    pub fn new(file: impl AsRef<Path>) -> Result<(Msd, Handle)> {
181        let mut builder = Self::builder();
182        builder.luns.push(Lun::new(file)?);
183        Ok(builder.build())
184    }
185
186    /// Creates a new USB Mass Storage Device (MSD) builder.
187    pub fn builder() -> MsdBuilder {
188        MsdBuilder { stall: None, luns: Vec::new() }
189    }
190
191    /// Access to registration status.
192    pub fn status(&self) -> Status {
193        self.dir.status()
194    }
195
196    /// Forcibly detach the backing file from the LUN, regardless of whether the host has allowed
197    /// it.
198    pub fn force_eject(&self, lun: usize) -> Result<()> {
199        let lun_dir_name = Lun::dir_name(lun);
200        self.dir.write(format!("{lun_dir_name}/forced_eject"), "1")
201    }
202
203    /// Set the path to the backing file for the LUN.
204    pub fn set_file<P: AsRef<Path>>(&self, lun: usize, file: Option<P>) -> Result<()> {
205        let lun_dir_name = Lun::dir_name(lun);
206        let file = match file {
207            Some(file) => {
208                let file = file.as_ref();
209                if !file.is_absolute() {
210                    return Err(Error::new(ErrorKind::InvalidInput, "the LUN file path must be absolute"));
211                }
212                file.as_os_str().as_bytes().to_vec()
213            }
214            None => Vec::new(),
215        };
216        self.dir.write(format!("{lun_dir_name}/file"), file)
217    }
218}
219
220pub(crate) fn remove_handler(dir: PathBuf) -> Result<()> {
221    for entry in fs::read_dir(dir)? {
222        let Ok(entry) = entry else { continue };
223        if entry.file_type()?.is_dir()
224            && entry.file_name().as_bytes().contains(&b'.')
225            && entry.file_name() != "lun.0"
226        {
227            fs::remove_dir(entry.path())?;
228        }
229    }
230
231    Ok(())
232}