// SPDX-License-Identifier: Apache-2.0 OR MIT //! Host filesystem manipulation operations. //! //! This module contains basic methods to manipulate the contents of the host filesystem. //! //! This module is a subset of the [`std::fs`] module except that [`CStr`] is used for the path //! instead of [`Path`]. //! //! [`Path`] is not available in `core`, so this module uses [`CStr`] instead in the API where //! `std` uses [`Path`]. When creating a path from a string literal, it is recommended to use the //! C string literals (`c"..."`, available since Rust 1.77) or [c!](c!()) macro. //! //! ```no_run //! use semihosting::{c, fs}; //! //! // with C string literal //! fs::write(c"a.txt", "abc")?; //! // with c! macro //! fs::write(c!("b.txt"), "123")?; //! # Ok::<(), semihosting::io::Error>(()) //! ``` //! //! # Platform-specific behavior //! //! The operations provided by this module are mapped to the corresponding operations of the host //! system. The details of its operation depend on the host system. //! //! [`std::fs`]: https://doc.rust-lang.org/std/fs/index.html //! [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html use core::{ffi::CStr, fmt}; use crate::{ fd::{AsFd as _, OwnedFd}, io::{self, Write as _}, sys, }; /// Write a slice as the entire contents of a file. /// /// See [`std::fs::write` documentation][std] for details. /// /// [std]: https://doc.rust-lang.org/std/fs/fn.write.html pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> { fn inner(path: &CStr, contents: &[u8]) -> io::Result<()> { File::create(path)?.write_all(contents) } inner(path.as_ref(), contents.as_ref()) } /// Removes a file from the host filesystem. /// /// See [`std::fs::remove_file` documentation][std] for details. /// /// [std]: https://doc.rust-lang.org/std/fs/fn.remove_file.html /// /// # Platform-specific behavior /// /// The following semihosting calls are currently being used: /// /// | Platform | Semihosting call | /// | ------------------------------------------------------------- | ---------------- | /// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_REMOVE] | /// | MIPS32, MIPS64 | UHI_unlink | /// /// [SYS_REMOVE]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-remove-0x0e /// /// **Disclaimer:** These semihosting calls might change over time. #[doc(alias = "rm", alias = "unlink", alias = "DeleteFile")] #[doc(alias = "SYS_REMOVE")] // arm_compat #[doc(alias = "UHI_unlink")] // mips pub fn remove_file>(path: P) -> io::Result<()> { sys::fs::unlink(path.as_ref()) } /// Rename a file or directory to a new name, replacing the original file if /// `to` already exists. /// /// See [`std::fs::rename` documentation][std] for details. /// /// [std]: https://doc.rust-lang.org/std/fs/fn.rename.html /// /// # Platform-specific behavior /// /// The following semihosting calls are currently being used: /// /// | Platform | Semihosting call | /// | ------------------------------------------------------------- | ---------------- | /// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_RENAME] | /// | MIPS32, MIPS64 | (Unsupported) | /// /// [SYS_RENAME]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-rename-0x0f /// /// **Disclaimer:** These semihosting calls might change over time. #[doc(alias = "mv", alias = "MoveFile", alias = "MoveFileEx")] #[doc(alias = "SYS_RENAME")] // arm_compat pub fn rename, Q: AsRef>(from: P, to: Q) -> io::Result<()> { sys::fs::rename(from.as_ref(), to.as_ref()) } /// An object providing access to an open file on the host filesystem. /// /// See [`std::fs::File` documentation][std] for details. /// /// [std]: https://doc.rust-lang.org/std/fs/struct.File.html pub struct File(OwnedFd); impl File { /// Attempts to open a file in read-only mode. pub fn open>(path: P) -> io::Result { OpenOptions::new().read(true).open(path.as_ref()) } /// Opens a file in write-only mode. pub fn create>(path: P) -> io::Result { OpenOptions::new().write(true).create(true).truncate(true).open(path.as_ref()) } /// Returns a new OpenOptions object. #[must_use] pub fn options() -> OpenOptions { OpenOptions::new() } /// Queries metadata about the underlying file. /// /// # Platform-specific behavior /// /// The following semihosting calls are currently being used: /// /// | Platform | Semihosting call | /// | ------------------------------------------------------------- | ---------------- | /// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_FLEN] | /// | MIPS32, MIPS64 | UHI_fstat | /// /// [SYS_FLEN]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-flen-0x0c /// /// **Disclaimer:** These semihosting calls might change over time. #[doc(alias = "SYS_FLEN")] // arm_compat #[doc(alias = "UHI_fstat")] // mips pub fn metadata(&self) -> io::Result { sys::fs::metadata(self.as_fd()).map(Metadata) } } impl_as_fd!(File); impl_from_fd!(File); impl fmt::Debug for File { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("File").field("fd", &self.as_fd().as_raw_fd()).finish() } } impl io::Read for File { fn read(&mut self, buf: &mut [u8]) -> io::Result { sys::read(self.as_fd(), buf) } } impl io::Write for File { fn write(&mut self, bytes: &[u8]) -> io::Result { sys::write(self.as_fd(), bytes) } #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl io::Seek for File { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { sys::fs::seek(self.as_fd(), pos) } } impl io::Read for &File { fn read(&mut self, buf: &mut [u8]) -> io::Result { sys::read(self.as_fd(), buf) } } impl io::Write for &File { fn write(&mut self, bytes: &[u8]) -> io::Result { sys::write(self.as_fd(), bytes) } #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl io::Seek for &File { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { sys::fs::seek(self.as_fd(), pos) } } /// Options and flags which can be used to configure how a file is opened. /// /// See [`std::fs::OpenOptions` documentation][std] for details. /// /// [std]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html #[derive(Clone, Debug)] pub struct OpenOptions { // generic pub(crate) read: bool, pub(crate) write: bool, pub(crate) append: bool, pub(crate) truncate: bool, pub(crate) create: bool, #[allow(dead_code)] pub(crate) create_new: bool, // system-specific #[allow(dead_code)] pub(crate) mode: u32, } #[allow(missing_docs)] // TODO impl OpenOptions { pub fn new() -> Self { Self { // generic read: false, write: false, append: false, truncate: false, create: false, create_new: false, // system-specific mode: 0o666, } } pub fn read(&mut self, read: bool) -> &mut Self { self.read = read; self } pub fn write(&mut self, write: bool) -> &mut Self { self.write = write; self } pub fn append(&mut self, append: bool) -> &mut Self { self.append = append; self } pub fn truncate(&mut self, truncate: bool) -> &mut Self { self.truncate = truncate; self } pub fn create(&mut self, create: bool) -> &mut Self { self.create = create; self } // pub fn create_new(&mut self, create_new: bool) { // self.create_new = create_new; // } // pub fn custom_flags(&mut self, flags: i32) { // self.custom_flags = flags; // } // pub fn mode(&mut self, mode: u32) { // self.mode = mode as mode_t; // } pub fn open>(&self, path: P) -> io::Result { sys::fs::open(path.as_ref(), self).map(File) } } /// Metadata information about a file. pub struct Metadata(sys::fs::Metadata); impl Metadata { /// Returns the size of the file, in bytes, this metadata is for. #[must_use] pub fn len(&self) -> u64 { self.0.size() } } impl fmt::Debug for Metadata { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Metadata").finish_non_exhaustive() } }