usb_gadget/function/
msd.rs1use 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#[derive(Debug, Clone)]
24#[non_exhaustive]
25pub struct Lun {
26 pub read_only: bool,
31 pub cdrom: bool,
33 pub no_fua: bool,
35 pub removable: bool,
37 file: Option<PathBuf>,
41 pub inquiry_string: String,
43}
44
45impl Lun {
46 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 pub fn empty() -> Self {
55 Self::default()
56 }
57
58 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#[derive(Debug, Clone)]
94#[non_exhaustive]
95pub struct MsdBuilder {
96 pub stall: Option<bool>,
100 pub luns: Vec<Lun>,
102}
103
104impl MsdBuilder {
105 #[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 pub fn add_lun(&mut self, lun: Lun) {
116 self.luns.push(lun);
117 }
118
119 #[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#[derive(Debug)]
174pub struct Msd {
175 dir: FunctionDir,
176}
177
178impl Msd {
179 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 pub fn builder() -> MsdBuilder {
188 MsdBuilder { stall: None, luns: Vec::new() }
189 }
190
191 pub fn status(&self) -> Status {
193 self.dir.status()
194 }
195
196 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 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}