use serde::{de::Visitor, ser::SerializeStruct, Deserialize, Deserializer, Serialize};
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use std::{
fmt,
fs::Metadata,
io::ErrorKind,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use crate::utils;
#[derive(Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct FileAttr(u32);
#[derive(Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct FileMode(u32);
#[derive(Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct FilePermissionFlags(u32);
bitflags! {
impl FileAttr: u32 {
const SIZE = 0x00000001;
const UIDGID = 0x00000002;
const PERMISSIONS = 0x00000004;
const ACMODTIME = 0x00000008;
const EXTENDED = 0x80000000;
}
impl FileMode: u32 {
const FIFO = 0x1000;
const CHR = 0x2000;
const DIR = 0x4000;
const NAM = 0x5000;
const BLK = 0x6000;
const REG = 0x8000;
const LNK = 0xA000;
const SOCK = 0xC000;
}
impl FilePermissionFlags: u32 {
const OTHER_READ = 0o4;
const OTHER_WRITE = 0o2;
const OTHER_EXEC = 0o1;
const GROUP_READ = 0o40;
const GROUP_WRITE = 0o20;
const GROUP_EXEC = 0o10;
const OWNER_READ = 0o400;
const OWNER_WRITE = 0o200;
const OWNER_EXEC = 0o100;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileType {
Dir,
File,
Symlink,
Other,
}
impl FileType {
pub fn is_dir(&self) -> bool {
matches!(self, Self::Dir)
}
pub fn is_file(&self) -> bool {
matches!(self, Self::File)
}
pub fn is_symlink(&self) -> bool {
matches!(self, Self::Symlink)
}
pub fn is_other(&self) -> bool {
matches!(self, Self::Other)
}
}
impl From<FileMode> for FileType {
fn from(mode: FileMode) -> Self {
if mode == FileMode::DIR {
FileType::Dir
} else if mode == FileMode::LNK {
FileType::Symlink
} else if mode == FileMode::REG {
FileType::File
} else {
FileType::Other
}
}
}
impl From<u32> for FileType {
fn from(mode: u32) -> Self {
FileMode::from_bits_truncate(mode).into()
}
}
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub struct FilePermissions {
pub other_exec: bool,
pub other_read: bool,
pub other_write: bool,
pub group_exec: bool,
pub group_read: bool,
pub group_write: bool,
pub owner_exec: bool,
pub owner_read: bool,
pub owner_write: bool,
}
impl FilePermissions {
pub fn is_readonly(&self) -> bool {
!self.other_write && !self.group_write && !self.owner_write
}
pub fn set_readonly(&mut self, readonly: bool) {
self.other_exec = !readonly;
self.other_write = !readonly;
self.group_exec = !readonly;
self.group_write = !readonly;
self.owner_exec = !readonly;
self.owner_write = !readonly;
if readonly {
self.other_read = true;
self.group_read = true;
self.owner_read = true;
}
}
}
impl fmt::Display for FilePermissions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}{}{}{}{}{}",
if self.owner_read { "r" } else { "-" },
if self.owner_write { "w" } else { "-" },
if self.owner_exec { "x" } else { "-" },
if self.group_read { "r" } else { "-" },
if self.group_write { "w" } else { "-" },
if self.group_exec { "x" } else { "-" },
if self.other_read { "r" } else { "-" },
if self.other_write { "w" } else { "-" },
if self.other_exec { "x" } else { "-" },
)
}
}
impl From<FilePermissionFlags> for FilePermissions {
fn from(flags: FilePermissionFlags) -> Self {
Self {
other_read: flags.contains(FilePermissionFlags::OTHER_READ),
other_write: flags.contains(FilePermissionFlags::OTHER_WRITE),
other_exec: flags.contains(FilePermissionFlags::OTHER_EXEC),
group_read: flags.contains(FilePermissionFlags::GROUP_READ),
group_write: flags.contains(FilePermissionFlags::GROUP_WRITE),
group_exec: flags.contains(FilePermissionFlags::GROUP_EXEC),
owner_read: flags.contains(FilePermissionFlags::OWNER_READ),
owner_write: flags.contains(FilePermissionFlags::OWNER_WRITE),
owner_exec: flags.contains(FilePermissionFlags::OWNER_EXEC),
}
}
}
impl From<u32> for FilePermissions {
fn from(mode: u32) -> Self {
FilePermissionFlags::from_bits_truncate(mode).into()
}
}
#[derive(Debug, Clone)]
pub struct FileAttributes {
pub size: Option<u64>,
pub uid: Option<u32>,
pub user: Option<String>,
pub gid: Option<u32>,
pub group: Option<String>,
pub permissions: Option<u32>,
pub atime: Option<u32>,
pub mtime: Option<u32>,
}
macro_rules! impl_fn_type {
($get_name:ident, $set_name:ident, $doc_name:expr, $flag:ident) => {
#[doc = "Returns `true` if is a "]
#[doc = $doc_name]
pub fn $get_name(&self) -> bool {
self.permissions.map_or(false, |b| {
FileMode::from_bits_truncate(b).contains(FileMode::$flag)
})
}
#[doc = "Set flag if is a "]
#[doc = $doc_name]
#[doc = " or not"]
pub fn $set_name(&mut self, $get_name: bool) {
match $get_name {
true => self.set_type(FileMode::$flag),
false => self.remove_type(FileMode::$flag),
}
}
};
}
impl FileAttributes {
impl_fn_type!(is_dir, set_dir, "dir", DIR);
impl_fn_type!(is_regular, set_regular, "regular", REG);
impl_fn_type!(is_symlink, set_symlink, "symlink", LNK);
impl_fn_type!(is_character, set_character, "character", CHR);
impl_fn_type!(is_block, set_block, "block", BLK);
impl_fn_type!(is_fifo, set_fifo, "fifo", FIFO);
pub fn set_type(&mut self, mode: FileMode) {
let perms = self.permissions.unwrap_or(0);
self.permissions = Some(perms | mode.bits());
}
pub fn remove_type(&mut self, mode: FileMode) {
let perms = self.permissions.unwrap_or(0);
self.permissions = Some(perms & !mode.bits());
}
pub fn file_type(&self) -> FileType {
FileMode::from_bits_truncate(self.permissions.unwrap_or_default()).into()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> u64 {
self.size.unwrap_or(0)
}
pub fn permissions(&self) -> FilePermissions {
FilePermissionFlags::from_bits_truncate(self.permissions.unwrap_or_default()).into()
}
pub fn accessed(&self) -> std::io::Result<SystemTime> {
match self.atime {
Some(time) => Ok(UNIX_EPOCH + Duration::from_secs(time as u64)),
None => Err(ErrorKind::InvalidData.into()),
}
}
pub fn modified(&self) -> std::io::Result<SystemTime> {
match self.mtime {
Some(time) => Ok(UNIX_EPOCH + Duration::from_secs(time as u64)),
None => Err(ErrorKind::InvalidData.into()),
}
}
pub fn empty() -> Self {
Self {
size: None,
uid: None,
user: None,
gid: None,
group: None,
permissions: None,
atime: None,
mtime: None,
}
}
}
impl Default for FileAttributes {
fn default() -> Self {
Self {
size: Some(0),
uid: Some(0),
user: None,
gid: Some(0),
group: None,
permissions: Some(0o777 | FileMode::DIR.bits()),
atime: Some(0),
mtime: Some(0),
}
}
}
impl From<&Metadata> for FileAttributes {
fn from(metadata: &Metadata) -> Self {
let mut attrs = Self {
size: Some(metadata.len()),
#[cfg(unix)]
uid: Some(metadata.uid()),
#[cfg(unix)]
gid: Some(metadata.gid()),
#[cfg(windows)]
permissions: Some(if metadata.permissions().readonly() {
0o555
} else {
0o777
}),
#[cfg(unix)]
permissions: Some(metadata.mode()),
atime: Some(utils::unix(metadata.accessed().unwrap_or(UNIX_EPOCH))),
mtime: Some(utils::unix(metadata.modified().unwrap_or(UNIX_EPOCH))),
..Default::default()
};
attrs.set_dir(metadata.is_dir());
attrs.set_regular(!metadata.is_dir());
attrs
}
}
impl Serialize for FileAttributes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut attrs = FileAttr::default();
let mut field_count = 1;
if self.size.is_some() {
attrs |= FileAttr::SIZE;
field_count += 1;
}
if self.uid.is_some() || self.gid.is_some() {
attrs |= FileAttr::UIDGID;
field_count += 2;
}
if self.permissions.is_some() {
attrs |= FileAttr::PERMISSIONS;
field_count += 1;
}
if self.atime.is_some() || self.mtime.is_some() {
attrs |= FileAttr::ACMODTIME;
field_count += 2;
}
let mut s = serializer.serialize_struct("FileAttributes", field_count)?;
s.serialize_field("attrs", &attrs)?;
if let Some(size) = self.size {
s.serialize_field("size", &size)?;
}
if self.uid.is_some() || self.gid.is_some() {
s.serialize_field("uid", &self.uid.unwrap_or(0))?;
s.serialize_field("gid", &self.gid.unwrap_or(0))?;
}
if let Some(permissions) = self.permissions {
s.serialize_field("permissions", &permissions)?;
}
if self.atime.is_some() || self.mtime.is_some() {
s.serialize_field("atime", &self.atime.unwrap_or(0))?;
s.serialize_field("mtime", &self.mtime.unwrap_or(0))?;
}
s.end()
}
}
impl<'de> Deserialize<'de> for FileAttributes {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct FileAttributesVisitor;
impl<'de> Visitor<'de> for FileAttributesVisitor {
type Value = FileAttributes;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("file attributes")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let attrs = FileAttr::from_bits_truncate(seq.next_element::<u32>()?.unwrap_or(0));
Ok(FileAttributes {
size: if attrs.contains(FileAttr::SIZE) {
seq.next_element::<u64>()?
} else {
None
},
uid: if attrs.contains(FileAttr::UIDGID) {
seq.next_element::<u32>()?
} else {
None
},
user: None,
gid: if attrs.contains(FileAttr::UIDGID) {
seq.next_element::<u32>()?
} else {
None
},
group: None,
permissions: if attrs.contains(FileAttr::PERMISSIONS) {
seq.next_element::<u32>()?
} else {
None
},
atime: if attrs.contains(FileAttr::ACMODTIME) {
seq.next_element::<u32>()?
} else {
None
},
mtime: if attrs.contains(FileAttr::ACMODTIME) {
seq.next_element::<u32>()?
} else {
None
},
})
}
}
deserializer.deserialize_any(FileAttributesVisitor)
}
}