use crate::entry::{EntryType, FileEntry};
const S_IFMT: u32 = 0o170000;
const S_IFSOCK: u32 = 0o140000;
const S_IFLNK: u32 = 0o120000;
const S_IFREG: u32 = 0o100000;
const S_IFBLK: u32 = 0o060000;
const S_IFDIR: u32 = 0o040000;
const S_IFCHR: u32 = 0o020000;
const S_IFIFO: u32 = 0o010000;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UnixFileType {
Regular,
Directory,
Symlink,
BlockDevice,
CharDevice,
Fifo,
Socket,
Unknown,
}
pub fn unix_file_type(mode: u32) -> UnixFileType {
match mode & S_IFMT {
S_IFREG => UnixFileType::Regular,
S_IFDIR => UnixFileType::Directory,
S_IFLNK => UnixFileType::Symlink,
S_IFBLK => UnixFileType::BlockDevice,
S_IFCHR => UnixFileType::CharDevice,
S_IFIFO => UnixFileType::Fifo,
S_IFSOCK => UnixFileType::Socket,
_ => UnixFileType::Unknown,
}
}
pub use crate::display::unix_mode_string;
#[allow(clippy::too_many_arguments)] pub fn unix_entry_from_inode(
name: &str,
path: &str,
mode: u32,
size: u64,
inode_num: u64,
mtime: i64,
uid: u32,
gid: u32,
) -> FileEntry {
let ft = unix_file_type(mode);
let modified = if mtime != 0 {
Some(format_unix_timestamp(mtime))
} else {
None
};
let modified_unix: Option<u64> = if mtime > 0 { Some(mtime as u64) } else { None };
let (entry_type, special_type) = match ft {
UnixFileType::Regular | UnixFileType::Unknown => (EntryType::File, None),
UnixFileType::Directory => (EntryType::Directory, None),
UnixFileType::Symlink => (EntryType::Symlink, None),
UnixFileType::BlockDevice => (EntryType::Special, Some("block device".to_string())),
UnixFileType::CharDevice => (EntryType::Special, Some("char device".to_string())),
UnixFileType::Fifo => (EntryType::Special, Some("fifo".to_string())),
UnixFileType::Socket => (EntryType::Special, Some("socket".to_string())),
};
let display_size = match entry_type {
EntryType::Special | EntryType::Directory => 0,
_ => size,
};
FileEntry {
name: name.to_string(),
path: path.to_string(),
entry_type,
size: display_size,
location: inode_num,
modified,
modified_unix,
type_code: None,
creator_code: None,
symlink_target: None,
special_type,
mode: Some(mode),
uid: Some(uid),
gid: Some(gid),
resource_fork_size: None,
aux_type: None,
link_target_cnid: None,
amiga_protection: None,
amiga_comment: None,
amiga_date: None,
dos_attributes: None,
finder_flags: None,
prodos_file_type: None,
mac_dates: None,
owner_label: None,
native_attrs: None,
qnx2_attrs: None,
}
}
pub fn check_id_width(
value: u32,
bits: u32,
what: &str,
) -> Result<u32, crate::filesystem::FilesystemError> {
let max = if bits >= 32 {
u32::MAX
} else {
(1u32 << bits) - 1
};
if value > max {
return Err(crate::filesystem::FilesystemError::InvalidData(format!(
"{what} {value} does not fit this filesystem's {bits}-bit {what} field (max {max})"
)));
}
Ok(value)
}
pub fn with_permission_bits(current_mode: u32, new_perms: u32) -> u32 {
(current_mode & S_IFMT) | (new_perms & 0o7777)
}
pub fn device_major_minor(dev: u32) -> (u32, u32) {
let major = (dev >> 8) & 0xFFF;
let minor = (dev & 0xFF) | ((dev >> 12) & 0xFFF00);
(major, minor)
}
pub fn format_unix_timestamp(epoch: i64) -> String {
if epoch < 0 {
return "1970-01-01 00:00:00".to_string();
}
let secs = epoch as u64;
let sec_of_day = secs % 86400;
let hour = sec_of_day / 3600;
let minute = (sec_of_day % 3600) / 60;
let second = sec_of_day % 60;
let mut days = (secs / 86400) as i64;
days += 719468; let era = if days >= 0 { days } else { days - 146096 } / 146097;
let doe = (days - era * 146097) as u32; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = if mp < 10 { mp + 3 } else { mp - 9 }; let year = if m <= 2 { y + 1 } else { y };
format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
year, m, d, hour, minute, second
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mode_string_regular_file() {
assert_eq!(unix_mode_string(0o100644), "-rw-r--r--");
}
#[test]
fn test_mode_string_executable() {
assert_eq!(unix_mode_string(0o100755), "-rwxr-xr-x");
}
#[test]
fn test_mode_string_directory() {
assert_eq!(unix_mode_string(0o040755), "drwxr-xr-x");
}
#[test]
fn test_mode_string_symlink() {
assert_eq!(unix_mode_string(0o120777), "lrwxrwxrwx");
}
#[test]
fn test_mode_string_setuid() {
assert_eq!(unix_mode_string(0o104755), "-rwsr-xr-x");
}
#[test]
fn test_mode_string_setuid_no_exec() {
assert_eq!(unix_mode_string(0o104644), "-rwSr--r--");
}
#[test]
fn test_mode_string_setgid() {
assert_eq!(unix_mode_string(0o102755), "-rwxr-sr-x");
}
#[test]
fn test_mode_string_setgid_no_exec() {
assert_eq!(unix_mode_string(0o102644), "-rw-r-Sr--");
}
#[test]
fn test_mode_string_sticky() {
assert_eq!(unix_mode_string(0o041755), "drwxr-xr-t");
}
#[test]
fn test_mode_string_sticky_no_exec() {
assert_eq!(unix_mode_string(0o041754), "drwxr-xr-T");
}
#[test]
fn test_mode_string_block_device() {
assert_eq!(unix_mode_string(0o060660), "brw-rw----");
}
#[test]
fn test_mode_string_char_device() {
assert_eq!(unix_mode_string(0o020666), "crw-rw-rw-");
}
#[test]
fn test_mode_string_fifo() {
assert_eq!(unix_mode_string(0o010644), "prw-r--r--");
}
#[test]
fn test_mode_string_socket() {
assert_eq!(unix_mode_string(0o140755), "srwxr-xr-x");
}
#[test]
fn test_mode_string_no_permissions() {
assert_eq!(unix_mode_string(0o100000), "----------");
}
#[test]
fn test_mode_string_all_permissions() {
assert_eq!(unix_mode_string(0o100777), "-rwxrwxrwx");
}
#[test]
fn test_unix_file_type() {
assert_eq!(unix_file_type(0o100644), UnixFileType::Regular);
assert_eq!(unix_file_type(0o040755), UnixFileType::Directory);
assert_eq!(unix_file_type(0o120777), UnixFileType::Symlink);
assert_eq!(unix_file_type(0o060660), UnixFileType::BlockDevice);
assert_eq!(unix_file_type(0o020666), UnixFileType::CharDevice);
assert_eq!(unix_file_type(0o010644), UnixFileType::Fifo);
assert_eq!(unix_file_type(0o140755), UnixFileType::Socket);
assert_eq!(unix_file_type(0o000644), UnixFileType::Unknown);
}
#[test]
fn test_format_timestamp_epoch() {
assert_eq!(format_unix_timestamp(0), "1970-01-01 00:00:00");
}
#[test]
fn test_format_timestamp_known_date() {
assert_eq!(format_unix_timestamp(1700000000), "2023-11-14 22:13:20");
}
#[test]
fn test_format_timestamp_y2k() {
assert_eq!(format_unix_timestamp(946684800), "2000-01-01 00:00:00");
}
#[test]
fn test_format_timestamp_negative() {
assert_eq!(format_unix_timestamp(-1), "1970-01-01 00:00:00");
}
#[test]
fn test_format_timestamp_2038() {
assert_eq!(format_unix_timestamp(2147483647), "2038-01-19 03:14:07");
}
#[test]
fn test_format_timestamp_1971() {
assert_eq!(format_unix_timestamp(31536000), "1971-01-01 00:00:00");
}
#[test]
fn test_device_major_minor() {
assert_eq!(device_major_minor(0x0800), (8, 0));
assert_eq!(device_major_minor(0x0103), (1, 3));
}
#[test]
fn test_unix_entry_from_inode_regular() {
let fe = unix_entry_from_inode(
"hello.txt",
"/hello.txt",
0o100644,
42,
100,
1700000000,
1000,
1000,
);
assert_eq!(fe.entry_type, EntryType::File);
assert_eq!(fe.size, 42);
assert_eq!(fe.location, 100);
assert_eq!(fe.mode, Some(0o100644));
assert_eq!(fe.uid, Some(1000));
assert_eq!(fe.gid, Some(1000));
assert!(fe.modified.is_some());
assert!(fe.symlink_target.is_none());
assert!(fe.special_type.is_none());
}
#[test]
fn test_unix_entry_from_inode_directory() {
let fe = unix_entry_from_inode("subdir", "/subdir", 0o040755, 4096, 200, 1700000000, 0, 0);
assert_eq!(fe.entry_type, EntryType::Directory);
assert_eq!(fe.size, 0); }
#[test]
fn test_unix_entry_from_inode_symlink() {
let fe = unix_entry_from_inode("link", "/link", 0o120777, 10, 300, 1700000000, 0, 0);
assert_eq!(fe.entry_type, EntryType::Symlink);
assert!(fe.symlink_target.is_none()); }
#[test]
fn test_unix_entry_from_inode_block_device() {
let fe = unix_entry_from_inode("sda", "/dev/sda", 0o060660, 0, 400, 0, 0, 6);
assert_eq!(fe.entry_type, EntryType::Special);
assert_eq!(fe.special_type.as_deref(), Some("block device"));
}
#[test]
fn test_unix_entry_from_inode_char_device() {
let fe = unix_entry_from_inode("null", "/dev/null", 0o020666, 0, 401, 0, 0, 0);
assert_eq!(fe.entry_type, EntryType::Special);
assert_eq!(fe.special_type.as_deref(), Some("char device"));
}
#[test]
fn test_unix_entry_from_inode_fifo() {
let fe = unix_entry_from_inode("pipe", "/tmp/pipe", 0o010644, 0, 500, 0, 1000, 1000);
assert_eq!(fe.entry_type, EntryType::Special);
assert_eq!(fe.special_type.as_deref(), Some("fifo"));
}
#[test]
fn test_unix_entry_from_inode_socket() {
let fe = unix_entry_from_inode("sock", "/tmp/sock", 0o140755, 0, 600, 0, 0, 0);
assert_eq!(fe.entry_type, EntryType::Special);
assert_eq!(fe.special_type.as_deref(), Some("socket"));
}
}