use std::{
ffi::{OsStr, OsString},
fs,
io::{Error, ErrorKind, Result},
os::unix::prelude::OsStrExt,
path::{Path, PathBuf},
};
use super::{
util::{FunctionDir, Status},
Function, Handle,
};
pub(crate) fn driver() -> &'static OsStr {
OsStr::new("mass_storage")
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Lun {
pub read_only: bool,
pub cdrom: bool,
pub no_fua: bool,
pub removable: bool,
file: Option<PathBuf>,
pub inquiry_string: String,
}
impl Lun {
pub fn new(file: impl AsRef<Path>) -> Result<Self> {
let mut this = Self::default();
this.set_file(Some(file))?;
Ok(this)
}
pub fn empty() -> Self {
Self::default()
}
pub fn set_file<F: AsRef<Path>>(&mut self, file: Option<F>) -> Result<()> {
match file {
Some(file) => {
let file = file.as_ref();
if !file.is_absolute() {
return Err(Error::new(ErrorKind::InvalidInput, "the LUN file path must be absolute"));
}
self.file = Some(file.to_path_buf());
}
None => self.file = None,
}
Ok(())
}
fn dir_name(idx: usize) -> String {
format!("lun.{idx}")
}
}
impl Default for Lun {
fn default() -> Self {
Self {
read_only: false,
cdrom: false,
no_fua: false,
removable: true,
file: None,
inquiry_string: String::new(),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct MsdBuilder {
pub stall: Option<bool>,
pub luns: Vec<Lun>,
}
impl MsdBuilder {
pub fn build(self) -> (Msd, Handle) {
let dir = FunctionDir::new();
(Msd { dir: dir.clone() }, Handle::new(MsdFunction { builder: self, dir }))
}
pub fn add_lun(&mut self, lun: Lun) {
self.luns.push(lun);
}
#[must_use]
pub fn with_lun(mut self, lun: Lun) -> Self {
self.add_lun(lun);
self
}
}
#[derive(Debug)]
struct MsdFunction {
builder: MsdBuilder,
dir: FunctionDir,
}
impl Function for MsdFunction {
fn driver(&self) -> OsString {
driver().into()
}
fn dir(&self) -> FunctionDir {
self.dir.clone()
}
fn register(&self) -> Result<()> {
if self.builder.luns.is_empty() {
return Err(Error::new(ErrorKind::InvalidInput, "at least one LUN must exist"));
}
if let Some(stall) = self.builder.stall {
self.dir.write("stall", if stall { "1" } else { "0" })?;
}
for (idx, lun) in self.builder.luns.iter().enumerate() {
let lun_dir_name = Lun::dir_name(idx);
if idx != 0 {
self.dir.create_dir(&lun_dir_name)?;
}
self.dir.write(format!("{lun_dir_name}/ro"), if lun.read_only { "1" } else { "0" })?;
self.dir.write(format!("{lun_dir_name}/cdrom"), if lun.cdrom { "1" } else { "0" })?;
self.dir.write(format!("{lun_dir_name}/nofua"), if lun.no_fua { "1" } else { "0" })?;
self.dir.write(format!("{lun_dir_name}/removable"), if lun.removable { "1" } else { "0" })?;
self.dir.write(format!("{lun_dir_name}/inquiry_string"), &lun.inquiry_string)?;
if let Some(file) = &lun.file {
self.dir.write(format!("{lun_dir_name}/file"), file.as_os_str().as_bytes())?;
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct Msd {
dir: FunctionDir,
}
impl Msd {
pub fn new(file: impl AsRef<Path>) -> Result<(Msd, Handle)> {
let mut builder = Self::builder();
builder.luns.push(Lun::new(file)?);
Ok(builder.build())
}
pub fn builder() -> MsdBuilder {
MsdBuilder { stall: None, luns: Vec::new() }
}
pub fn status(&self) -> Status {
self.dir.status()
}
pub fn force_eject(&self, lun: usize) -> Result<()> {
let lun_dir_name = Lun::dir_name(lun);
self.dir.write(format!("{lun_dir_name}/forced_eject"), "1")
}
pub fn set_file<P: AsRef<Path>>(&self, lun: usize, file: Option<P>) -> Result<()> {
let lun_dir_name = Lun::dir_name(lun);
let file = match file {
Some(file) => {
let file = file.as_ref();
if !file.is_absolute() {
return Err(Error::new(ErrorKind::InvalidInput, "the LUN file path must be absolute"));
}
file.as_os_str().as_bytes().to_vec()
}
None => Vec::new(),
};
self.dir.write(format!("{lun_dir_name}/file"), file)
}
}
pub(crate) fn remove_handler(dir: PathBuf) -> Result<()> {
for entry in fs::read_dir(dir)? {
let Ok(entry) = entry else { continue };
if entry.file_type()?.is_dir()
&& entry.file_name().as_bytes().contains(&b'.')
&& entry.file_name() != "lun.0"
{
fs::remove_dir(entry.path())?;
}
}
Ok(())
}