use std::{path::PathBuf, time::SystemTime};
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FileMode: u32 {
const OWNER_R = 0o0400;
const OWNER_W = 0o0200;
const OWNER_X = 0o0100;
const OWNER_RWX = 0o0700;
const GROUP_R = 0o0040;
const GROUP_W = 0o0020;
const GROUP_X = 0o0010;
const GROUP_RWX = 0o0070;
const OTHER_R = 0o0004;
const OTHER_W = 0o0002;
const OTHER_X = 0o0001;
const OTHER_RWX = 0o0007;
const SUID = 0o4000;
const SGID = 0o2000;
const STICKY = 0o1000;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileMetadata {
File {
mode: Option<FileMode>,
mtime: Option<SystemTime>,
size: u64,
},
Dir {
mode: Option<FileMode>,
mtime: Option<SystemTime>,
},
Symlink {
mtime: Option<SystemTime>,
target: PathBuf,
},
}
impl FileMetadata {
pub fn kind(&self) -> FileType {
match self {
Self::File { .. } => FileType::File,
Self::Dir { .. } => FileType::Dir,
Self::Symlink { .. } => FileType::Symlink,
}
}
pub fn mtime(&self) -> Option<SystemTime> {
match self {
Self::File { mtime, .. } | Self::Dir { mtime, .. } | Self::Symlink { mtime, .. } => {
*mtime
}
}
}
pub fn mode(&self) -> Option<FileMode> {
match self {
Self::File { mode, .. } | Self::Dir { mode, .. } => *mode,
Self::Symlink { .. } => Some(mode_from_umask(FileType::Symlink, FileMode::empty())),
}
}
pub fn is_file(&self) -> bool {
matches!(self, Self::File { .. })
}
pub fn is_dir(&self) -> bool {
matches!(self, Self::Dir { .. })
}
pub fn is_symlink(&self) -> bool {
matches!(self, Self::Symlink { .. })
}
}
pub const TYPE_MASK: u32 = 0o170000;
pub const FILE_MODE: u32 = 0o100000;
pub const DIR_MODE: u32 = 0o040000;
pub const SYMLINK_MODE: u32 = 0o120000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FileType {
File,
Dir,
Symlink,
}
impl From<FileMetadata> for FileType {
fn from(metadata: FileMetadata) -> Self {
metadata.kind()
}
}
impl FileMode {
pub(super) fn to_file_mode(self) -> u32 {
self.bits() | FILE_MODE
}
pub(super) fn to_dir_mode(self) -> u32 {
self.bits() | DIR_MODE
}
pub(super) fn to_symlink_mode(self) -> u32 {
self.bits() | SYMLINK_MODE
}
pub(super) fn from_mode(mode: u32) -> Self {
Self::from_bits_truncate(mode & !TYPE_MASK)
}
}
pub fn mode_from_umask(kind: FileType, umask: FileMode) -> FileMode {
match kind {
FileType::File => {
!umask
& (FileMode::OWNER_R
| FileMode::OWNER_W
| FileMode::GROUP_R
| FileMode::GROUP_W
| FileMode::OTHER_R
| FileMode::OTHER_W)
}
FileType::Dir => !umask & (FileMode::OWNER_RWX | FileMode::GROUP_RWX | FileMode::OTHER_RWX),
FileType::Symlink => FileMode::OWNER_RWX | FileMode::GROUP_RWX | FileMode::OTHER_RWX,
}
}
#[cfg(test)]
mod tests {
use xpct::{equal, expect};
use super::*;
fn test_file_mode() -> FileMode {
FileMode::OWNER_R
| FileMode::OWNER_W
| FileMode::GROUP_R
| FileMode::GROUP_W
| FileMode::OTHER_R
}
fn test_dir_mode() -> FileMode {
FileMode::OWNER_RWX | FileMode::GROUP_RWX | FileMode::OTHER_R | FileMode::OTHER_X
}
fn test_symlink_mode() -> FileMode {
FileMode::OWNER_RWX | FileMode::GROUP_RWX | FileMode::OTHER_RWX
}
#[test]
fn get_file_mode_from_permissions() {
expect!(test_file_mode().to_file_mode()).to(equal(0o100664));
}
#[test]
fn get_dir_mode_from_permissions() {
expect!(test_dir_mode().to_dir_mode()).to(equal(0o040775));
}
#[test]
fn get_symlink_mode_from_permissions() {
expect!(test_symlink_mode().to_symlink_mode()).to(equal(0o120777));
}
#[test]
fn get_file_permissions_from_mode() {
expect!(FileMode::from_mode(0o100664)).to(equal(test_file_mode()));
expect!(FileMode::from_mode(0o040775)).to(equal(test_dir_mode()));
}
}