diff --git a/src/services/fs.rs b/src/services/fs.rs index 01df070..fb8309c 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -1,3 +1,9 @@ +//! Filesystem service +//! +//! This module contains basic methods to manipulate the contents of the 3DS's filesystem. +//! Only the SD card is currently supported. + + use core::marker::PhantomData; use core::ptr; use core::slice; @@ -45,26 +51,106 @@ pub enum ArchiveID { DemoSavedata, } - +/// Represents the filesystem service. No file IO can be performed +/// until an instance of this struct is created. +/// +/// The service exits when this struct goes out of scope. pub struct Fs { pd: PhantomData, } +/// Handle to an open filesystem archive. +/// +/// Archives are automatically closed when they go out of scope. +/// +/// # Examples +/// +/// ```no_run +/// use ctru::services::fs::Fs +/// +/// let fs = Fs::init().unwrap(); +/// let sdmc_archive = fs.sdmc().unwrap(); +/// ``` pub struct Archive { id: ArchiveID, handle: u64, } +/// A reference to an open file on the filesystem. +/// +/// An instance of a `File` can be read and/or written to depending +/// on what options It was opened with. +/// +/// Files are automatically closed when they go out of scope. pub struct File { handle: u32, offset: u64, } +/// Metadata information about a file. +/// +/// This structure is returned from the [`metadata`] function and +/// represents known metadata about a file. +/// +/// [`metadata`]: fn.metadata.html pub struct Metadata { attributes: u32, size: u64, } +/// Options and flags which can be used to configure how a [`File`] is opened. +/// This builder exposes the ability to configure how a `File` is opened +/// and what operations are permitted on the open file. The [`File::open`] +/// and [`File::create`] methods are aliases for commonly used options +/// using this builder. +/// +/// [`File`]: struct.File.html +/// [`File::open`]: struct.File.html#method.open +/// [`File::create`]: struct.File.html#method.create +/// +/// Generally speaking, when using `OpenOptions`, you'll first call [`new()`], +/// then chain calls to methods to set each option, then call [`open()`], +/// passing the path of the file you're trying to open. +/// +/// It is required to also pass a reference to the [`Archive`] that the +/// file lives in. +/// +/// [`new()`]: struct.OpenOptions.html#method.new +/// [`open()`]: struct.OpenOptions.html#method.open +/// [`Archive`]: struct.Archive.html +/// +/// # Examples +/// +/// Opening a file to read: +/// +/// ```no_run +/// use ctru::services::fs::OpenOptions; +/// +/// let fs = Fs::init().unwrap(); +/// let sdmc_archive = fs.sdmc().unwrap(); +/// let file = OpenOptions::new() +/// .read(true) +/// .archive(&sdmc_archive) +/// .open("foo.txt") +/// .unwrap(); +/// ``` +/// +/// Opening a file for both reading and writing, as well as creating it if it +/// doesn't exist: +/// +/// ```no_run +/// use ctru::services::fs::OpenOptions; +/// +/// let fs = Fs::init().unwrap(); +/// let sdmc_archive = fs.sdmc().unwrap(); +/// let file = OpenOptions::new() +/// .read(true) +/// .write(true) +/// .create(true) +/// .archive(&sdmc_archive) +/// .open("foo.txt") +/// .unwrap(); +/// ``` #[derive(Clone)] pub struct OpenOptions { read: bool, @@ -73,24 +159,55 @@ pub struct OpenOptions { arch_handle: u64, } +/// Iterator over the entries in a directory. +/// +/// This iterator is returned from the [`read_dir`] function of this module and +/// will yield instances of `Result`. Through a [`DirEntry`] +/// information like the entry's path and possibly other metadata can be +/// learned. +/// +/// [`read_dir`]: fn.read_dir.html +/// [`DirEntry`]: struct.DirEntry.html +/// +/// # Errors +/// +/// This Result will return Err if there's some sort of intermittent IO error +/// during iteration. pub struct ReadDir<'a> { handle: Dir, root: Arc, arch: &'a Archive, } +/// Entries returned by the [`ReadDir`] iterator. +/// +/// [`ReadDir`]: struct.ReadDir.html +/// +/// An instance of `DirEntry` represents an entry inside of a directory on the +/// filesystem. Each entry can be inspected via methods to learn about the full +/// path or possibly other metadata. pub struct DirEntry<'a> { entry: FS_DirectoryEntry, root: Arc, arch: &'a Archive, } +#[doc(hidden)] struct Dir(u32); +#[doc(hidden)] unsafe impl Send for Dir {} +#[doc(hidden)] unsafe impl Sync for Dir {} impl Fs { + /// Initializes the FS service. + /// + /// # Errors + /// + /// This function will return Err if there was an error initializing the + /// FS service. This typically reflects a problem with the execution + /// environment and not necessarily your program itself. pub fn init() -> Result { unsafe { let r = fsInit(); @@ -102,6 +219,7 @@ impl Fs { } } + /// Returns a handle to the SDMC (memory card) Archive. pub fn sdmc(&self) -> Result { unsafe { let mut handle = 0; @@ -121,23 +239,73 @@ impl Fs { } impl Archive { + /// Retrieves an Archive's [`ArchiveID`] + /// + /// [`ArchiveID`]: enum.ArchiveID.html pub fn get_id(&self) -> ArchiveID { self.id } } impl File { + /// Attempts to open a file in read-only mode. + /// + /// See the [`OpenOptions::open`] method for more details. + /// + /// # Errors + /// + /// This function will return an error if `path` does not already exit. + /// Other errors may also be returned accoridng to [`OpenOptions::open`] + /// + /// [`OpenOptions::open`]: struct.OpenOptions.html#method.open + /// + /// # Examples + /// + /// ```no_run + /// use ctru::servies::fs::{Fs, File}; + /// + /// let fs = Fs::init().unwrap() + /// let sdmc_archive = fs.sdmc().unwrap() + /// let mut f = File::open("/foo.txt").unwrap(); + /// ``` pub fn open>(arch: &Archive, path: P) -> Result { OpenOptions::new().read(true).archive(arch).open(path.as_ref()) } + /// Opens a file in write-only mode. + /// + /// This function will create a file if it does not exist. + /// + /// See the [`OpenOptions::create`] method for more details. + /// + /// # Errors + /// + /// This function will return an error if `path` does not already exit. + /// Other errors may also be returned accoridng to [`OpenOptions::create`] + /// + /// [`OpenOptions::create`]: struct.OpenOptions.html#method.create + /// + /// # Examples + /// + /// ```no_run + /// use ctru::servies::fs::{Fs, File}; + /// + /// let fs = Fs::init().unwrap() + /// let sdmc_archive = fs.sdmc().unwrap() + /// let mut f = File::create("/foo.txt").unwrap(); + /// ``` pub fn create>(arch: &Archive, path: P) -> Result { OpenOptions::new().write(true).create(true).archive(arch).open(path.as_ref()) } - pub fn set_len(&mut self, len: u64) -> Result<(), i32> { + /// Truncates or extends the underlying file, updating the size of this file to become size. + /// + /// If the size is less than the current file's size, then the file will be shrunk. If it is + /// greater than the current file's size, then the file will be extended to size and have all + /// of the intermediate data filled in with 0s. + pub fn set_len(&mut self, size: u64) -> Result<(), i32> { unsafe { - let r = FSFILE_SetSize(self.handle, len); + let r = FSFILE_SetSize(self.handle, size); if r < 0 { Err(r) } else { @@ -146,9 +314,10 @@ impl File { } } - // Right now the only file metadata we really have is file size - // This will probably expand later on + /// Queries metadata about the underlying file. pub fn metadata(&self) -> Result { + // The only metadata we have for files right now is file size. + // This is likely to change in the future. unsafe { let mut size = 0; let r = FSFILE_GetSize(self.handle, &mut size); @@ -160,6 +329,11 @@ impl File { } } + /// Pull some bytes from the file into the specified buffer, returning + /// how many bytes were read. + /// + /// This function will become private when std::io support is ported + /// to this library. pub fn read(&mut self, buf: &mut [u8]) -> Result { unsafe { let mut n_read = 0; @@ -179,12 +353,20 @@ impl File { } } + /// Read all bytes until EOF in this source, placing them into buf. + /// + /// This function will become private when std::io support is ported + /// to this library. pub fn read_to_end(&mut self, buf: &mut Vec) -> Result { unsafe { read_to_end_uninitialized(self, buf) } } + /// Write a buffer into this object, returning how many bytes were written. + /// + /// This function will become private when std::io support is ported + /// to this library. pub fn write(&mut self, buf: &[u8]) -> Result { unsafe { let mut n_written = 0; @@ -207,20 +389,28 @@ impl File { } impl Metadata { + /// Returns whether this metadata is for a directory. pub fn is_dir(&self) -> bool { self.attributes == self.attributes | FS_ATTRIBUTE_DIRECTORY } + /// Returns whether this metadata is for a regular file. pub fn is_file(&self) -> bool { !self.is_dir() } + /// Returns the size, in bytes, this metadata is for. + /// + /// Directories return size = 0. pub fn len(&self) -> u64 { self.size } } impl OpenOptions { + /// Creates a blank set of options ready for configuration. + /// + /// All options are initially set to `false` pub fn new() -> OpenOptions { OpenOptions { read: false, @@ -230,26 +420,61 @@ impl OpenOptions { } } + /// Sets the option for read access. + /// + /// This option, when true, will indicate that the file should be + /// `read`-able if opened. pub fn read(&mut self, read: bool) -> &mut OpenOptions { self.read = read; self } + /// Sets the option for write access. + /// + /// This option, when true, will indicate that the file should be + /// `write`-able if opened. + /// + /// If the file already exists, any write calls on it will overwrite + /// its contents, without truncating it. pub fn write(&mut self, write: bool) -> &mut OpenOptions { self.write = write; self } + /// Sets the option for creating a new file. + /// + /// This option indicates whether a new file will be created + /// if the file does not yet already + /// exist. + /// + /// In order for the file to be created, write access must also be used. pub fn create(&mut self, create: bool) -> &mut OpenOptions { self.create = create; self } + /// Sets which archive the file is to be opened in. + /// + /// Failing to pass in an archive will result in the file failing to open. pub fn archive(&mut self, archive: &Archive) -> &mut OpenOptions { self.arch_handle = archive.handle; self } + /// Opens a file at `path` with the options specified by `self` + /// + /// # Errors + /// + /// This function will return an error under a number of different + /// circumstances, including but not limited to: + /// + /// * Opening a file that doesn't exist without setting `create`. + /// * Attempting to open a file without passing an [`Archive`] reference + /// to the `archive` method. + /// * Filesystem-level errors (full disk, etc). + /// * Invalid combinations of open options. + /// + /// [`Archive`]: struct.Archive.html pub fn open>(&self, path: P) -> Result { self._open(path.as_ref(), self.get_open_flags()) } @@ -310,20 +535,34 @@ impl<'a> Iterator for ReadDir<'a> { } impl<'a> DirEntry<'a> { + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to `read_dir` + /// with the filename of this entry. pub fn path(&self) -> PathBuf { self.root.join(&self.file_name()) } + /// Return the metadata for the file that this entry points at. pub fn metadata(&self) -> Result { metadata(self.arch, self.path()) } + /// Return the file type for the file that this entry points at. pub fn file_name(&self) -> OsString { let filename = truncate_utf16_at_nul(&self.entry.name); OsString::from_wide(filename) } } +/// Creates a new, empty directory at the provided path +/// +/// # Errors +/// +/// This function will return an error in the following situations, +/// but is not limited to just these cases: +/// +/// * User lacks permissions to create directory at `path` pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let path = to_utf16(path.as_ref()); @@ -337,6 +576,15 @@ pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { } } +/// Recursively create a directory and all of its parent components if they are missing. +/// +/// # Errors +/// +/// This function will return an error in the following situations, +/// but is not limited to just these cases: +/// +/// * If any directory in the path specified by `path` does not already exist +/// and it could not be created otherwise. pub fn create_dir_all>(arch: &Archive, path: P) -> Result<(), i32> { let path = path.as_ref(); let mut dir = PathBuf::new(); @@ -350,6 +598,7 @@ pub fn create_dir_all>(arch: &Archive, path: P) -> Result<(), i32 result } +/// Given a path, query the file system to get information about a file, directory, etc pub fn metadata>(arch: &Archive, path: P) -> Result { let maybe_file = File::open(&arch, path.as_ref()); let maybe_dir = read_dir(&arch, path.as_ref()); @@ -360,6 +609,15 @@ pub fn metadata>(arch: &Archive, path: P) -> Result>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let path = to_utf16(path.as_ref()); @@ -373,6 +631,11 @@ pub fn remove_dir>(arch: &Archive, path: P) -> Result<(), i32> { } } +/// Removes a directory at this path, after removing all its contents. Use carefully! +/// +/// # Errors +/// +/// see `file::remove_file` and `fs::remove_dir` pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let path = to_utf16(path.as_ref()); @@ -386,6 +649,17 @@ pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32 } } +/// Returns an iterator over the entries within a directory. +/// +/// The iterator will yield instances of Result. New errors +/// may be encountered after an iterator is initially constructed. +/// +/// This function will return an error in the following situations, but is not limited to just +/// these cases: +/// +/// * The provided path doesn't exist. +/// * The process lacks permissions to view the contents. +/// * The path points at a non-directory file. pub fn read_dir>(arch: &Archive, path: P) -> Result { unsafe { let mut handle = 0; @@ -401,6 +675,15 @@ pub fn read_dir>(arch: &Archive, path: P) -> Result } } +/// Removes a file from the filesystem. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not limited to just +/// these cases: +/// +/// * path points to a directory. +/// * The user lacks permissions to remove the file. pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let path = to_utf16(path.as_ref()); @@ -414,6 +697,16 @@ pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { } } +/// Rename a file or directory to a new name, replacing the original file +/// if to already exists. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not limited to just +/// these cases: +/// +/// * from does not exist. +/// * The user lacks permissions to view contents. pub fn rename(arch: &Archive, from: P, to: Q) -> Result<(), i32> where P: AsRef, Q: AsRef {