compio-fs 0.12.0-rc.1

Filesystem IO for compio
Documentation
use std::{io, os::windows::fs::MetadataExt, path::Path, time::SystemTime};

#[cfg(dirfd)]
use compio_driver::ToSharedFd;
use compio_runtime::ResumeUnwind;

#[cfg(dirfd)]
use crate::File;

pub async fn metadata(path: impl AsRef<Path>) -> io::Result<Metadata> {
    let path = path.as_ref().to_path_buf();
    compio_runtime::spawn_blocking(move || std::fs::metadata(path))
        .await
        .resume_unwind()
        .expect("shouldn't be cancelled")
        .map(Metadata::from)
}

pub async fn symlink_metadata(path: impl AsRef<Path>) -> io::Result<Metadata> {
    let path = path.as_ref().to_path_buf();
    compio_runtime::spawn_blocking(move || std::fs::symlink_metadata(path))
        .await
        .resume_unwind()
        .expect("shouldn't be cancelled")
        .map(Metadata::from)
}

#[cfg(dirfd)]
async fn metadata_at_impl(
    dir: &File,
    path: impl AsRef<Path>,
    follow_symlinks: bool,
) -> io::Result<Metadata> {
    let path = path.as_ref().to_path_buf();
    crate::spawn_blocking_with(dir.to_shared_fd(), move |dir| {
        cap_primitives::fs::stat(
            dir,
            &path,
            if follow_symlinks {
                cap_primitives::fs::FollowSymlinks::Yes
            } else {
                cap_primitives::fs::FollowSymlinks::No
            },
        )
    })
    .await
    .map(Metadata::from)
}

#[cfg(dirfd)]
pub async fn metadata_at(dir: &File, path: impl AsRef<Path>) -> io::Result<Metadata> {
    metadata_at_impl(dir, path, true).await
}

#[cfg(dirfd)]
pub async fn symlink_metadata_at(dir: &File, path: impl AsRef<Path>) -> io::Result<Metadata> {
    metadata_at_impl(dir, path, false).await
}

pub async fn set_permissions(path: impl AsRef<Path>, perm: Permissions) -> io::Result<()> {
    let path = path.as_ref().to_path_buf();
    compio_runtime::spawn_blocking(move || {
        // Reduce syscalls at best effort.
        if let Some(p) = perm.original {
            std::fs::set_permissions(&path, p)
        } else {
            let mut p = std::fs::metadata(&path)?.permissions();
            p.set_readonly(perm.readonly());
            std::fs::set_permissions(&path, p)
        }
    })
    .await
    .resume_unwind()
    .expect("shouldn't be cancelled")
}

#[derive(Clone)]
pub struct Metadata {
    attributes: u32,
    creation_time: u64,
    last_access_time: u64,
    last_write_time: u64,
    file_size: u64,
    #[cfg(feature = "windows_by_handle")]
    volume_serial_number: Option<u32>,
    #[cfg(feature = "windows_by_handle")]
    number_of_links: Option<u32>,
    #[cfg(feature = "windows_by_handle")]
    file_index: Option<u64>,
    file_type: FileType,
    permissions: Permissions,
    modified: SystemTime,
    accessed: SystemTime,
    created: SystemTime,
}

impl Metadata {
    pub fn file_type(&self) -> FileType {
        self.file_type
    }

    pub fn is_dir(&self) -> bool {
        self.file_type.is_dir()
    }

    pub fn is_file(&self) -> bool {
        self.file_type.is_file()
    }

    pub fn is_symlink(&self) -> bool {
        self.file_type.is_symlink()
    }

    pub fn len(&self) -> u64 {
        self.file_size
    }

    pub fn permissions(&self) -> Permissions {
        self.permissions.clone()
    }

    pub fn modified(&self) -> io::Result<SystemTime> {
        Ok(self.modified)
    }

    pub fn accessed(&self) -> io::Result<SystemTime> {
        Ok(self.accessed)
    }

    pub fn created(&self) -> io::Result<SystemTime> {
        Ok(self.created)
    }

    pub fn file_attributes(&self) -> u32 {
        self.attributes
    }

    pub fn creation_time(&self) -> u64 {
        self.creation_time
    }

    pub fn last_access_time(&self) -> u64 {
        self.last_access_time
    }

    pub fn last_write_time(&self) -> u64 {
        self.last_write_time
    }

    #[cfg(feature = "windows_by_handle")]
    pub fn volume_serial_number(&self) -> Option<u32> {
        self.volume_serial_number
    }

    #[cfg(feature = "windows_by_handle")]
    pub fn number_of_links(&self) -> Option<u32> {
        self.number_of_links
    }

    #[cfg(feature = "windows_by_handle")]
    pub fn file_index(&self) -> Option<u64> {
        self.file_index
    }
}

impl From<std::fs::Metadata> for Metadata {
    fn from(value: std::fs::Metadata) -> Self {
        Self {
            attributes: value.file_attributes(),
            creation_time: value.creation_time(),
            last_access_time: value.last_access_time(),
            last_write_time: value.last_write_time(),
            file_size: value.file_size(),
            #[cfg(feature = "windows_by_handle")]
            volume_serial_number: value.volume_serial_number(),
            #[cfg(feature = "windows_by_handle")]
            number_of_links: value.number_of_links(),
            #[cfg(feature = "windows_by_handle")]
            file_index: value.file_index(),
            file_type: value.file_type().into(),
            permissions: value.permissions().into(),
            modified: value
                .modified()
                .expect("std::fs::Metadata::modified() should never fail on Windows"),
            accessed: value
                .accessed()
                .expect("std::fs::Metadata::accessed() should never fail on Windows"),
            created: value
                .created()
                .expect("std::fs::Metadata::created() should never fail on Windows"),
        }
    }
}

#[cfg(dirfd)]
impl From<cap_primitives::fs::Metadata> for Metadata {
    fn from(value: cap_primitives::fs::Metadata) -> Self {
        use cap_primitives::fs::MetadataExt;

        Self {
            attributes: value.file_attributes(),
            creation_time: value.creation_time(),
            last_access_time: value.last_access_time(),
            last_write_time: value.last_write_time(),
            file_size: value.file_size(),
            #[cfg(feature = "windows_by_handle")]
            volume_serial_number: value.volume_serial_number(),
            #[cfg(feature = "windows_by_handle")]
            number_of_links: value.number_of_links(),
            #[cfg(feature = "windows_by_handle")]
            file_index: value.file_index(),
            file_type: value.file_type().into(),
            permissions: value.permissions().into(),
            modified: value
                .modified()
                .expect("cap_primitives::fs::Metadata::modified() should never fail on Windows")
                .into_std(),
            accessed: value
                .accessed()
                .expect("cap_primitives::fs::Metadata::accessed() should never fail on Windows")
                .into_std(),
            created: value
                .created()
                .expect("cap_primitives::fs::Metadata::created() should never fail on Windows")
                .into_std(),
        }
    }
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Permissions {
    readonly: bool,
    pub(crate) original: Option<std::fs::Permissions>,
}

impl Permissions {
    pub fn readonly(&self) -> bool {
        self.readonly
    }

    pub fn set_readonly(&mut self, readonly: bool) {
        self.readonly = readonly;
        if let Some(p) = &mut self.original {
            p.set_readonly(readonly);
        }
    }
}

impl From<std::fs::Permissions> for Permissions {
    fn from(value: std::fs::Permissions) -> Self {
        Self {
            readonly: value.readonly(),
            original: Some(value),
        }
    }
}

#[cfg(dirfd)]
impl From<cap_primitives::fs::Permissions> for Permissions {
    fn from(value: cap_primitives::fs::Permissions) -> Self {
        Self {
            readonly: value.readonly(),
            original: None,
        }
    }
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct FileType {
    is_dir: bool,
    is_symlink: bool,
}

impl FileType {
    pub fn is_dir(&self) -> bool {
        self.is_dir && !self.is_symlink
    }

    pub fn is_file(&self) -> bool {
        !self.is_dir && !self.is_symlink
    }

    pub fn is_symlink(&self) -> bool {
        self.is_symlink
    }

    pub fn is_symlink_dir(&self) -> bool {
        self.is_symlink && self.is_dir
    }

    pub fn is_symlink_file(&self) -> bool {
        self.is_symlink && !self.is_dir
    }
}

impl From<std::fs::FileType> for FileType {
    fn from(value: std::fs::FileType) -> Self {
        Self {
            is_dir: value.is_dir(),
            is_symlink: value.is_symlink(),
        }
    }
}

#[cfg(dirfd)]
impl From<cap_primitives::fs::FileType> for FileType {
    fn from(value: cap_primitives::fs::FileType) -> Self {
        Self {
            is_dir: value.is_dir(),
            is_symlink: value.is_symlink(),
        }
    }
}