about summary refs log tree commit diff
path: root/library/std/src/sys/fs
diff options
context:
space:
mode:
authorLaurențiu Nicola <lnicola@users.noreply.github.com>2025-09-29 04:29:28 +0000
committerGitHub <noreply@github.com>2025-09-29 04:29:28 +0000
commit930451e17d47cc27b6c96cb358dfae2ed3a97c67 (patch)
tree722e3fdf88fdae0903cd1ff43cfc801db18345d3 /library/std/src/sys/fs
parentc0e45c896823664c49d631e961208d640dc43c58 (diff)
parent24f6f94c5ad90be5d1ced24d83b8485712bcc27a (diff)
downloadrust-930451e17d47cc27b6c96cb358dfae2ed3a97c67.tar.gz
rust-930451e17d47cc27b6c96cb358dfae2ed3a97c67.zip
Merge pull request #20761 from rust-lang/rustc-pull
Rustc pull update
Diffstat (limited to 'library/std/src/sys/fs')
-rw-r--r--library/std/src/sys/fs/mod.rs4
-rw-r--r--library/std/src/sys/fs/vexos.rs615
2 files changed, 619 insertions, 0 deletions
diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs
index 0276bf6e64c..64f5a6b36d3 100644
--- a/library/std/src/sys/fs/mod.rs
+++ b/library/std/src/sys/fs/mod.rs
@@ -35,6 +35,10 @@ cfg_select! {
         mod uefi;
         use uefi as imp;
     }
+    target_os = "vexos" => {
+        mod vexos;
+        use vexos as imp;
+    }
     target_os = "wasi" => {
         mod wasi;
         use wasi as imp;
diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs
new file mode 100644
index 00000000000..f642e7cb074
--- /dev/null
+++ b/library/std/src/sys/fs/vexos.rs
@@ -0,0 +1,615 @@
+use crate::ffi::{OsString, c_char};
+use crate::fmt;
+use crate::fs::TryLockError;
+use crate::hash::Hash;
+use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom};
+use crate::path::{Path, PathBuf};
+use crate::sys::common::small_c_string::run_path_with_cstr;
+use crate::sys::time::SystemTime;
+use crate::sys::{unsupported, unsupported_err};
+
+#[expect(dead_code)]
+#[path = "unsupported.rs"]
+mod unsupported_fs;
+pub use unsupported_fs::{
+    DirBuilder, FileTimes, canonicalize, link, readlink, remove_dir_all, rename, rmdir, symlink,
+    unlink,
+};
+
+/// VEXos file descriptor.
+///
+/// This stores an opaque pointer to a [FatFs file object structure] managed by VEXos
+/// representing an open file on disk.
+///
+/// [FatFs file object structure]: https://github.com/Xilinx/embeddedsw/blob/master/lib/sw_services/xilffs/src/include/ff.h?rgh-link-date=2025-09-23T20%3A03%3A43Z#L215
+///
+/// # Safety
+///
+/// Since this platform uses a pointer to to an internal filesystem structure with a lifetime
+/// associated with it (rather than a UNIX-style file descriptor table), care must be taken to
+/// ensure that the pointer held by `FileDesc` is valid for as long as it exists.
+#[derive(Debug)]
+struct FileDesc(*mut vex_sdk::FIL);
+
+// SAFETY: VEXos's FDs can be used on a thread other than the one they were created on.
+unsafe impl Send for FileDesc {}
+// SAFETY: We assume an environment without threads (i.e. no RTOS).
+// (If there were threads, it is possible that a mutex would be required.)
+unsafe impl Sync for FileDesc {}
+
+pub struct File {
+    fd: FileDesc,
+}
+
+#[derive(Clone)]
+pub enum FileAttr {
+    Dir,
+    File { size: u64 },
+}
+
+pub struct ReadDir(!);
+
+pub struct DirEntry {
+    path: PathBuf,
+}
+
+#[derive(Clone, Debug)]
+pub struct OpenOptions {
+    read: bool,
+    write: bool,
+    append: bool,
+    truncate: bool,
+    create: bool,
+    create_new: bool,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct FilePermissions {}
+
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+pub struct FileType {
+    is_dir: bool,
+}
+
+impl FileAttr {
+    pub fn size(&self) -> u64 {
+        match self {
+            Self::File { size } => *size,
+            Self::Dir => 0,
+        }
+    }
+
+    pub fn perm(&self) -> FilePermissions {
+        FilePermissions {}
+    }
+
+    pub fn file_type(&self) -> FileType {
+        FileType { is_dir: matches!(self, FileAttr::Dir) }
+    }
+
+    pub fn modified(&self) -> io::Result<SystemTime> {
+        unsupported()
+    }
+
+    pub fn accessed(&self) -> io::Result<SystemTime> {
+        unsupported()
+    }
+
+    pub fn created(&self) -> io::Result<SystemTime> {
+        unsupported()
+    }
+}
+
+impl FilePermissions {
+    pub fn readonly(&self) -> bool {
+        false
+    }
+
+    pub fn set_readonly(&mut self, _readonly: bool) {
+        panic!("Perimissions do not exist")
+    }
+}
+
+impl FileType {
+    pub fn is_dir(&self) -> bool {
+        self.is_dir
+    }
+
+    pub fn is_file(&self) -> bool {
+        !self.is_dir
+    }
+
+    pub fn is_symlink(&self) -> bool {
+        // No symlinks in VEXos - entries are either files or directories.
+        false
+    }
+}
+
+impl fmt::Debug for ReadDir {
+    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0
+    }
+}
+
+impl Iterator for ReadDir {
+    type Item = io::Result<DirEntry>;
+
+    fn next(&mut self) -> Option<io::Result<DirEntry>> {
+        self.0
+    }
+}
+
+impl DirEntry {
+    pub fn path(&self) -> PathBuf {
+        self.path.clone()
+    }
+
+    pub fn file_name(&self) -> OsString {
+        self.path.file_name().unwrap_or_default().into()
+    }
+
+    pub fn metadata(&self) -> io::Result<FileAttr> {
+        stat(&self.path)
+    }
+
+    pub fn file_type(&self) -> io::Result<FileType> {
+        Ok(self.metadata()?.file_type())
+    }
+}
+
+impl OpenOptions {
+    pub fn new() -> OpenOptions {
+        OpenOptions {
+            read: false,
+            write: false,
+            append: false,
+            truncate: false,
+            create: false,
+            create_new: false,
+        }
+    }
+
+    pub fn read(&mut self, read: bool) {
+        self.read = read;
+    }
+    pub fn write(&mut self, write: bool) {
+        self.write = write;
+    }
+    pub fn append(&mut self, append: bool) {
+        self.append = append;
+    }
+    pub fn truncate(&mut self, truncate: bool) {
+        self.truncate = truncate;
+    }
+    pub fn create(&mut self, create: bool) {
+        self.create = create;
+    }
+    pub fn create_new(&mut self, create_new: bool) {
+        self.create_new = create_new;
+    }
+}
+
+impl File {
+    pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
+        run_path_with_cstr(path, &|path| {
+            // Enforce the invariants of `create_new`/`create`.
+            //
+            // Since VEXos doesn't have anything akin to POSIX's `oflags`, we need to enforce
+            // the requirements that `create_new` can't have an existing file and `!create`
+            // doesn't create a file ourselves.
+            if !opts.read && (opts.write || opts.append) && (opts.create_new || !opts.create) {
+                let status = unsafe { vex_sdk::vexFileStatus(path.as_ptr()) };
+
+                if opts.create_new && status != 0 {
+                    return Err(io::const_error!(io::ErrorKind::AlreadyExists, "file exists",));
+                } else if !opts.create && status == 0 {
+                    return Err(io::const_error!(
+                        io::ErrorKind::NotFound,
+                        "no such file or directory",
+                    ));
+                }
+            }
+
+            let file = match opts {
+                // read + write - unsupported
+                OpenOptions { read: true, write: true, .. } => {
+                    return Err(io::const_error!(
+                        io::ErrorKind::InvalidInput,
+                        "opening files with read and write access is unsupported on this target",
+                    ));
+                }
+
+                // read
+                OpenOptions {
+                    read: true,
+                    write: false,
+                    append: _,
+                    truncate: false,
+                    create: false,
+                    create_new: false,
+                } => unsafe { vex_sdk::vexFileOpen(path.as_ptr(), c"".as_ptr()) },
+
+                // append
+                OpenOptions {
+                    read: false,
+                    write: _,
+                    append: true,
+                    truncate: false,
+                    create: _,
+                    create_new: _,
+                } => unsafe { vex_sdk::vexFileOpenWrite(path.as_ptr()) },
+
+                // write
+                OpenOptions {
+                    read: false,
+                    write: true,
+                    append: false,
+                    truncate,
+                    create: _,
+                    create_new: _,
+                } => unsafe {
+                    if *truncate {
+                        vex_sdk::vexFileOpenCreate(path.as_ptr())
+                    } else {
+                        // Open in append, but jump to the start of the file.
+                        let fd = vex_sdk::vexFileOpenWrite(path.as_ptr());
+                        vex_sdk::vexFileSeek(fd, 0, 0);
+                        fd
+                    }
+                },
+
+                _ => {
+                    return Err(io::const_error!(io::ErrorKind::InvalidInput, "invalid argument"));
+                }
+            };
+
+            if file.is_null() {
+                Err(io::const_error!(io::ErrorKind::NotFound, "could not open file"))
+            } else {
+                Ok(Self { fd: FileDesc(file) })
+            }
+        })
+    }
+
+    pub fn file_attr(&self) -> io::Result<FileAttr> {
+        // `vexFileSize` returns -1 upon error, so u64::try_from will fail on error.
+        if let Ok(size) = u64::try_from(unsafe {
+            // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+            vex_sdk::vexFileSize(self.fd.0)
+        }) {
+            Ok(FileAttr::File { size })
+        } else {
+            Err(io::const_error!(io::ErrorKind::InvalidData, "failed to get file size"))
+        }
+    }
+
+    pub fn fsync(&self) -> io::Result<()> {
+        self.flush()
+    }
+
+    pub fn datasync(&self) -> io::Result<()> {
+        self.flush()
+    }
+
+    pub fn lock(&self) -> io::Result<()> {
+        unsupported()
+    }
+
+    pub fn lock_shared(&self) -> io::Result<()> {
+        unsupported()
+    }
+
+    pub fn try_lock(&self) -> Result<(), TryLockError> {
+        Err(TryLockError::Error(unsupported_err()))
+    }
+
+    pub fn try_lock_shared(&self) -> Result<(), TryLockError> {
+        Err(TryLockError::Error(unsupported_err()))
+    }
+
+    pub fn unlock(&self) -> io::Result<()> {
+        unsupported()
+    }
+
+    pub fn truncate(&self, _size: u64) -> io::Result<()> {
+        unsupported()
+    }
+
+    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
+        let len = buf.len() as u32;
+        let buf_ptr = buf.as_mut_ptr();
+        let read = unsafe {
+            // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+            vex_sdk::vexFileRead(buf_ptr.cast::<c_char>(), 1, len, self.fd.0)
+        };
+
+        if read < 0 {
+            Err(io::const_error!(io::ErrorKind::Other, "could not read from file"))
+        } else {
+            Ok(read as usize)
+        }
+    }
+
+    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+        crate::io::default_read_vectored(|b| self.read(b), bufs)
+    }
+
+    #[inline]
+    pub fn is_read_vectored(&self) -> bool {
+        false
+    }
+
+    pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
+        crate::io::default_read_buf(|b| self.read(b), cursor)
+    }
+
+    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
+        let len = buf.len() as u32;
+        let buf_ptr = buf.as_ptr();
+        let written = unsafe {
+            // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+            vex_sdk::vexFileWrite(buf_ptr.cast_mut().cast::<c_char>(), 1, len, self.fd.0)
+        };
+
+        if written < 0 {
+            Err(io::const_error!(io::ErrorKind::Other, "could not write to file"))
+        } else {
+            Ok(written as usize)
+        }
+    }
+
+    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+        crate::io::default_write_vectored(|b| self.write(b), bufs)
+    }
+
+    #[inline]
+    pub fn is_write_vectored(&self) -> bool {
+        false
+    }
+
+    pub fn flush(&self) -> io::Result<()> {
+        unsafe {
+            // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+            vex_sdk::vexFileSync(self.fd.0);
+        }
+        Ok(())
+    }
+
+    pub fn tell(&self) -> io::Result<u64> {
+        // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+        let position = unsafe { vex_sdk::vexFileTell(self.fd.0) };
+
+        position.try_into().map_err(|_| {
+            io::const_error!(io::ErrorKind::InvalidData, "failed to get current location in file")
+        })
+    }
+
+    pub fn size(&self) -> Option<io::Result<u64>> {
+        None
+    }
+
+    pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
+        const SEEK_SET: i32 = 0;
+        const SEEK_CUR: i32 = 1;
+        const SEEK_END: i32 = 2;
+
+        fn try_convert_offset<T: TryInto<u32>>(offset: T) -> io::Result<u32> {
+            offset.try_into().map_err(|_| {
+                io::const_error!(
+                    io::ErrorKind::InvalidInput,
+                    "cannot seek to an offset too large to fit in a 32 bit integer",
+                )
+            })
+        }
+
+        // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime.
+        match pos {
+            SeekFrom::Start(offset) => unsafe {
+                map_fresult(vex_sdk::vexFileSeek(self.fd.0, try_convert_offset(offset)?, SEEK_SET))?
+            },
+            SeekFrom::End(offset) => unsafe {
+                if offset >= 0 {
+                    map_fresult(vex_sdk::vexFileSeek(
+                        self.fd.0,
+                        try_convert_offset(offset)?,
+                        SEEK_END,
+                    ))?
+                } else {
+                    // `vexFileSeek` does not support seeking with negative offset, meaning
+                    // we have to calculate the offset from the end of the file ourselves.
+
+                    // Seek to the end of the file to get the end position in the open buffer.
+                    map_fresult(vex_sdk::vexFileSeek(self.fd.0, 0, SEEK_END))?;
+                    let end_position = self.tell()?;
+
+                    map_fresult(vex_sdk::vexFileSeek(
+                        self.fd.0,
+                        // NOTE: Files internally use a 32-bit representation for stream
+                        // position, so `end_position as i64` should never overflow.
+                        try_convert_offset(end_position as i64 + offset)?,
+                        SEEK_SET,
+                    ))?
+                }
+            },
+            SeekFrom::Current(offset) => unsafe {
+                if offset >= 0 {
+                    map_fresult(vex_sdk::vexFileSeek(
+                        self.fd.0,
+                        try_convert_offset(offset)?,
+                        SEEK_CUR,
+                    ))?
+                } else {
+                    // `vexFileSeek` does not support seeking with negative offset, meaning
+                    // we have to calculate the offset from the stream position ourselves.
+                    map_fresult(vex_sdk::vexFileSeek(
+                        self.fd.0,
+                        try_convert_offset((self.tell()? as i64) + offset)?,
+                        SEEK_SET,
+                    ))?
+                }
+            },
+        }
+
+        Ok(self.tell()?)
+    }
+
+    pub fn duplicate(&self) -> io::Result<File> {
+        unsupported()
+    }
+
+    pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
+        unsupported()
+    }
+
+    pub fn set_times(&self, _times: FileTimes) -> io::Result<()> {
+        unsupported()
+    }
+}
+
+impl fmt::Debug for File {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("File").field("fd", &self.fd.0).finish()
+    }
+}
+impl Drop for File {
+    fn drop(&mut self) {
+        unsafe { vex_sdk::vexFileClose(self.fd.0) };
+    }
+}
+
+pub fn readdir(_p: &Path) -> io::Result<ReadDir> {
+    // While there *is* a userspace function for reading file directories,
+    // the necessary implementation cannot currently be done cleanly, as
+    // VEXos does not expose directory length to user programs.
+    //
+    // This means that we would need to create a large fixed-length buffer
+    // and hope that the folder's contents didn't exceed that buffer's length,
+    // which obviously isn't behavior we want to rely on in the standard library.
+    unsupported()
+}
+
+pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
+    unsupported()
+}
+
+pub fn exists(path: &Path) -> io::Result<bool> {
+    run_path_with_cstr(path, &|path| Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0))
+}
+
+pub fn stat(p: &Path) -> io::Result<FileAttr> {
+    // `vexFileStatus` returns 3 if the given path is a directory, 1 if the path is a
+    // file, or 0 if no such path exists.
+    const FILE_STATUS_DIR: u32 = 3;
+
+    run_path_with_cstr(p, &|c_path| {
+        let file_type = unsafe { vex_sdk::vexFileStatus(c_path.as_ptr()) };
+
+        // We can't get the size if its a directory because we cant open it as a file
+        if file_type == FILE_STATUS_DIR {
+            Ok(FileAttr::Dir)
+        } else {
+            let mut opts = OpenOptions::new();
+            opts.read(true);
+            let file = File::open(p, &opts)?;
+            file.file_attr()
+        }
+    })
+}
+
+pub fn lstat(p: &Path) -> io::Result<FileAttr> {
+    // Symlinks aren't supported in this filesystem
+    stat(p)
+}
+
+// Cannot use `copy` from `common` here, since `File::set_permissions` is unsupported on this target.
+pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
+    use crate::fs::File;
+
+    // NOTE: If `from` is a directory, this call should fail due to vexFileOpen* returning null.
+    let mut reader = File::open(from)?;
+    let mut writer = File::create(to)?;
+
+    io::copy(&mut reader, &mut writer)
+}
+
+fn map_fresult(fresult: vex_sdk::FRESULT) -> io::Result<()> {
+    // VEX uses a derivative of FatFs (Xilinx's xilffs library) for filesystem operations.
+    match fresult {
+        vex_sdk::FRESULT::FR_OK => Ok(()),
+        vex_sdk::FRESULT::FR_DISK_ERR => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "internal function reported an unrecoverable hard error",
+        )),
+        vex_sdk::FRESULT::FR_INT_ERR => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "internal error in filesystem runtime",
+        )),
+        vex_sdk::FRESULT::FR_NOT_READY => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "the storage device could not be prepared to work",
+        )),
+        vex_sdk::FRESULT::FR_NO_FILE => Err(io::const_error!(
+            io::ErrorKind::NotFound,
+            "could not find the file in the directory"
+        )),
+        vex_sdk::FRESULT::FR_NO_PATH => Err(io::const_error!(
+            io::ErrorKind::NotFound,
+            "a directory in the path name could not be found",
+        )),
+        vex_sdk::FRESULT::FR_INVALID_NAME => Err(io::const_error!(
+            io::ErrorKind::InvalidInput,
+            "the given string is invalid as a path name",
+        )),
+        vex_sdk::FRESULT::FR_DENIED => Err(io::const_error!(
+            io::ErrorKind::PermissionDenied,
+            "the required access for this operation was denied",
+        )),
+        vex_sdk::FRESULT::FR_EXIST => Err(io::const_error!(
+            io::ErrorKind::AlreadyExists,
+            "an object with the same name already exists in the directory",
+        )),
+        vex_sdk::FRESULT::FR_INVALID_OBJECT => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "invalid or null file/directory object",
+        )),
+        vex_sdk::FRESULT::FR_WRITE_PROTECTED => Err(io::const_error!(
+            io::ErrorKind::PermissionDenied,
+            "a write operation was performed on write-protected media",
+        )),
+        vex_sdk::FRESULT::FR_INVALID_DRIVE => Err(io::const_error!(
+            io::ErrorKind::InvalidInput,
+            "an invalid drive number was specified in the path name",
+        )),
+        vex_sdk::FRESULT::FR_NOT_ENABLED => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "work area for the logical drive has not been registered",
+        )),
+        vex_sdk::FRESULT::FR_NO_FILESYSTEM => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "valid FAT volume could not be found on the drive",
+        )),
+        vex_sdk::FRESULT::FR_MKFS_ABORTED => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "failed to create filesystem volume"
+        )),
+        vex_sdk::FRESULT::FR_TIMEOUT => Err(io::const_error!(
+            io::ErrorKind::TimedOut,
+            "the function was canceled due to a timeout of thread-safe control",
+        )),
+        vex_sdk::FRESULT::FR_LOCKED => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "the operation to the object was rejected by file sharing control",
+        )),
+        vex_sdk::FRESULT::FR_NOT_ENOUGH_CORE => {
+            Err(io::const_error!(io::ErrorKind::OutOfMemory, "not enough memory for the operation"))
+        }
+        vex_sdk::FRESULT::FR_TOO_MANY_OPEN_FILES => Err(io::const_error!(
+            io::ErrorKind::Uncategorized,
+            "maximum number of open files has been reached",
+        )),
+        vex_sdk::FRESULT::FR_INVALID_PARAMETER => {
+            Err(io::const_error!(io::ErrorKind::InvalidInput, "a given parameter was invalid"))
+        }
+        _ => unreachable!(), // C-style enum
+    }
+}