karmaio 0.1.0

A modern, fast, share-nothing asynchronous runtime for Rust
Documentation
use std::{
    io,
    os::windows::fs::{FileTypeExt, MetadataExt},
    time::{Duration, SystemTime},
};

use windows_sys::Win32::Storage::FileSystem::{
    BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_REPARSE_POINT,
    FILE_ATTRIBUTE_TAG_INFO, FileAttributeTagInfo, GetFileInformationByHandle, GetFileInformationByHandleEx,
};

#[derive(Clone)]
pub(crate) struct Metadata {
    attributes: u32,
    creation_time: windows_sys::Win32::Foundation::FILETIME,
    last_access_time: windows_sys::Win32::Foundation::FILETIME,
    last_write_time: windows_sys::Win32::Foundation::FILETIME,
    file_size: u64,
    reparse_tag: u32,
    volume_serial_number: Option<u32>,
    number_of_links: Option<u32>,
    file_index: Option<u64>,
    change_time: Option<u64>,
}

impl Metadata {
    pub(crate) fn from_handle(handle: windows_sys::Win32::Foundation::HANDLE) -> io::Result<Self> {
        unsafe {
            let mut info: BY_HANDLE_FILE_INFORMATION = std::mem::zeroed();
            if GetFileInformationByHandle(handle, &mut info) == 0 {
                return Err(io::Error::last_os_error());
            }

            let mut reparse_tag = 0;
            if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
                let mut attr_tag: FILE_ATTRIBUTE_TAG_INFO = std::mem::zeroed();
                if GetFileInformationByHandleEx(
                    handle,
                    FileAttributeTagInfo,
                    (&raw mut attr_tag).cast(),
                    std::mem::size_of::<FILE_ATTRIBUTE_TAG_INFO>() as u32,
                ) != 0
                    && attr_tag.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
                {
                    reparse_tag = attr_tag.ReparseTag;
                }
            }

            Ok(Self {
                attributes: info.dwFileAttributes,
                creation_time: info.ftCreationTime,
                last_access_time: info.ftLastAccessTime,
                last_write_time: info.ftLastWriteTime,
                file_size: info.nFileSizeLow as u64 | ((info.nFileSizeHigh as u64) << 32),
                reparse_tag,
                volume_serial_number: Some(info.dwVolumeSerialNumber),
                number_of_links: Some(info.nNumberOfLinks),
                file_index: Some(info.nFileIndexLow as u64 | ((info.nFileIndexHigh as u64) << 32)),
                change_time: None,
            })
        }
    }

    pub(crate) fn from_std(metadata: std::fs::Metadata) -> Self {
        let file_type = metadata.file_type();
        let mut attributes = metadata.file_attributes();
        if file_type.is_symlink_dir() {
            attributes |= FILE_ATTRIBUTE_DIRECTORY;
        }

        Self {
            attributes,
            creation_time: u64_to_filetime(metadata.creation_time()),
            last_access_time: u64_to_filetime(metadata.last_access_time()),
            last_write_time: u64_to_filetime(metadata.last_write_time()),
            file_size: metadata.file_size(),
            reparse_tag: if file_type.is_symlink() { 0x2000_0000 } else { 0 },
            volume_serial_number: None,
            number_of_links: None,
            file_index: None,
            change_time: None,
        }
    }

    pub(crate) fn file_type(&self) -> FileType {
        FileType::new(self.attributes, self.reparse_tag)
    }

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

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

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

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

    pub(crate) fn permissions(&self) -> Permissions {
        Permissions { attrs: self.attributes }
    }

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

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

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

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

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

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

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

    pub(crate) fn volume_serial_number(&self) -> Option<u32> {
        self.volume_serial_number
    }

    pub(crate) fn number_of_links(&self) -> Option<u32> {
        self.number_of_links
    }

    pub(crate) fn file_index(&self) -> Option<u64> {
        self.file_index
    }

    pub(crate) fn change_time(&self) -> Option<u64> {
        self.change_time
    }
}

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

impl FileType {
    pub(crate) fn from_std(file_type: std::fs::FileType) -> Self {
        Self {
            is_directory: file_type.is_dir() || file_type.is_symlink_dir(),
            is_symlink: file_type.is_symlink(),
        }
    }

    fn new(attributes: u32, reparse_tag: u32) -> Self {
        let is_directory = attributes & FILE_ATTRIBUTE_DIRECTORY != 0;
        let is_symlink = {
            let is_reparse_point = attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0;
            let is_reparse_tag_name_surrogate = reparse_tag & 0x2000_0000 != 0;
            is_reparse_point && is_reparse_tag_name_surrogate
        };

        Self {
            is_directory,
            is_symlink,
        }
    }

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

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

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

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

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

#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct Permissions {
    attrs: u32,
}

impl Permissions {
    pub(crate) fn readonly(&self) -> bool {
        self.attrs & FILE_ATTRIBUTE_READONLY != 0
    }

    pub(crate) fn set_readonly(&mut self, readonly: bool) {
        if readonly {
            self.attrs |= FILE_ATTRIBUTE_READONLY;
        } else {
            self.attrs &= !FILE_ATTRIBUTE_READONLY;
        }
    }

    pub(crate) fn attrs(&self) -> u32 {
        self.attrs
    }
}

fn filetime_to_u64(ft: &windows_sys::Win32::Foundation::FILETIME) -> u64 {
    (ft.dwLowDateTime as u64) | ((ft.dwHighDateTime as u64) << 32)
}

fn u64_to_filetime(value: u64) -> windows_sys::Win32::Foundation::FILETIME {
    windows_sys::Win32::Foundation::FILETIME {
        dwLowDateTime: value as u32,
        dwHighDateTime: (value >> 32) as u32,
    }
}

fn filetime_to_system_time(ft: &windows_sys::Win32::Foundation::FILETIME) -> io::Result<SystemTime> {
    const INTERVALS_PER_SEC: u64 = 10_000_000;
    const SECS_BETWEEN_EPOCHS: u64 = 11644473600;

    let intervals = filetime_to_u64(ft);
    let secs = intervals / INTERVALS_PER_SEC;
    let subsec_intervals = intervals % INTERVALS_PER_SEC;
    let nanos = subsec_intervals * 100;

    Ok(SystemTime::UNIX_EPOCH + Duration::new(secs.saturating_sub(SECS_BETWEEN_EPOCHS), nanos as u32))
}