use super::constants::{N_BLOCKS, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFREG, S_IFSOCK};
pub const INODE_BASE_SIZE: usize = 128;
#[derive(Debug, Clone, Copy, Default)]
pub struct Inode {
pub mode: u16,
pub uid: u16,
pub size: u32,
pub atime: u32,
pub ctime: u32,
pub mtime: u32,
pub dtime: u32,
pub gid: u16,
pub links_count: u16,
pub blocks_512: u32,
pub flags: u32,
pub osd1: u32,
pub block: [u32; N_BLOCKS],
pub generation: u32,
pub file_acl: u32,
pub size_hi_or_dir_acl: u32,
pub faddr: u32,
pub osd2: [u8; 12],
}
impl Inode {
pub fn regular(size: u32, mode_perms: u16, uid: u32, gid: u32, mtime: u32) -> Self {
Self {
mode: S_IFREG | (mode_perms & 0o7777),
uid: (uid & 0xffff) as u16,
size,
atime: mtime,
ctime: mtime,
mtime,
dtime: 0,
gid: (gid & 0xffff) as u16,
links_count: 1,
blocks_512: 0,
flags: 0,
osd1: 0,
block: [0; N_BLOCKS],
generation: 0,
file_acl: 0,
size_hi_or_dir_acl: 0,
faddr: 0,
osd2: [0; 12],
}
}
pub fn directory(size: u32, mode_perms: u16, uid: u32, gid: u32, mtime: u32) -> Self {
Self {
mode: S_IFDIR | (mode_perms & 0o7777),
uid: (uid & 0xffff) as u16,
size,
atime: mtime,
ctime: mtime,
mtime,
dtime: 0,
gid: (gid & 0xffff) as u16,
links_count: 2,
blocks_512: 0,
flags: 0,
osd1: 0,
block: [0; N_BLOCKS],
generation: 0,
file_acl: 0,
size_hi_or_dir_acl: 0,
faddr: 0,
osd2: [0; 12],
}
}
pub fn symlink(size: u32, mode_perms: u16, uid: u32, gid: u32, mtime: u32) -> Self {
Self {
mode: S_IFLNK | (mode_perms & 0o7777),
uid: (uid & 0xffff) as u16,
size,
atime: mtime,
ctime: mtime,
mtime,
dtime: 0,
gid: (gid & 0xffff) as u16,
links_count: 1,
blocks_512: 0,
flags: 0,
osd1: 0,
block: [0; N_BLOCKS],
generation: 0,
file_acl: 0,
size_hi_or_dir_acl: 0,
faddr: 0,
osd2: [0; 12],
}
}
pub fn special(
kind: SpecialKind,
major: u32,
minor: u32,
mode_perms: u16,
uid: u32,
gid: u32,
mtime: u32,
) -> Self {
let m = match kind {
SpecialKind::Char => S_IFCHR,
SpecialKind::Block => S_IFBLK,
SpecialKind::Fifo => S_IFIFO,
SpecialKind::Socket => S_IFSOCK,
};
let mut block = [0u32; N_BLOCKS];
if matches!(kind, SpecialKind::Char | SpecialKind::Block) {
block[0] = encode_devnum(major, minor);
}
Self {
mode: m | (mode_perms & 0o7777),
uid: (uid & 0xffff) as u16,
size: 0,
atime: mtime,
ctime: mtime,
mtime,
dtime: 0,
gid: (gid & 0xffff) as u16,
links_count: 1,
blocks_512: 0,
flags: 0,
osd1: 0,
block,
generation: 0,
file_acl: 0,
size_hi_or_dir_acl: 0,
faddr: 0,
osd2: [0; 12],
}
}
pub fn encode(&self) -> [u8; INODE_BASE_SIZE] {
let mut buf = [0u8; INODE_BASE_SIZE];
buf[0..2].copy_from_slice(&self.mode.to_le_bytes());
buf[2..4].copy_from_slice(&self.uid.to_le_bytes());
buf[4..8].copy_from_slice(&self.size.to_le_bytes());
buf[8..12].copy_from_slice(&self.atime.to_le_bytes());
buf[12..16].copy_from_slice(&self.ctime.to_le_bytes());
buf[16..20].copy_from_slice(&self.mtime.to_le_bytes());
buf[20..24].copy_from_slice(&self.dtime.to_le_bytes());
buf[24..26].copy_from_slice(&self.gid.to_le_bytes());
buf[26..28].copy_from_slice(&self.links_count.to_le_bytes());
buf[28..32].copy_from_slice(&self.blocks_512.to_le_bytes());
buf[32..36].copy_from_slice(&self.flags.to_le_bytes());
buf[36..40].copy_from_slice(&self.osd1.to_le_bytes());
for (i, b) in self.block.iter().enumerate() {
let off = 40 + i * 4;
buf[off..off + 4].copy_from_slice(&b.to_le_bytes());
}
buf[100..104].copy_from_slice(&self.generation.to_le_bytes());
buf[104..108].copy_from_slice(&self.file_acl.to_le_bytes());
buf[108..112].copy_from_slice(&self.size_hi_or_dir_acl.to_le_bytes());
buf[112..116].copy_from_slice(&self.faddr.to_le_bytes());
buf[116..128].copy_from_slice(&self.osd2);
buf
}
pub fn decode(buf: &[u8; INODE_BASE_SIZE]) -> Self {
let mut block = [0u32; N_BLOCKS];
for (i, slot) in block.iter_mut().enumerate() {
let off = 40 + i * 4;
*slot = u32::from_le_bytes(buf[off..off + 4].try_into().unwrap());
}
let mut osd2 = [0u8; 12];
osd2.copy_from_slice(&buf[116..128]);
Inode {
mode: u16::from_le_bytes(buf[0..2].try_into().unwrap()),
uid: u16::from_le_bytes(buf[2..4].try_into().unwrap()),
size: u32::from_le_bytes(buf[4..8].try_into().unwrap()),
atime: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
ctime: u32::from_le_bytes(buf[12..16].try_into().unwrap()),
mtime: u32::from_le_bytes(buf[16..20].try_into().unwrap()),
dtime: u32::from_le_bytes(buf[20..24].try_into().unwrap()),
gid: u16::from_le_bytes(buf[24..26].try_into().unwrap()),
links_count: u16::from_le_bytes(buf[26..28].try_into().unwrap()),
blocks_512: u32::from_le_bytes(buf[28..32].try_into().unwrap()),
flags: u32::from_le_bytes(buf[32..36].try_into().unwrap()),
osd1: u32::from_le_bytes(buf[36..40].try_into().unwrap()),
block,
generation: u32::from_le_bytes(buf[100..104].try_into().unwrap()),
file_acl: u32::from_le_bytes(buf[104..108].try_into().unwrap()),
size_hi_or_dir_acl: u32::from_le_bytes(buf[108..112].try_into().unwrap()),
faddr: u32::from_le_bytes(buf[112..116].try_into().unwrap()),
osd2,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum SpecialKind {
Char,
Block,
Fifo,
Socket,
}
pub fn encode_devnum(major: u32, minor: u32) -> u32 {
(minor & 0xff) | ((major & 0xfff) << 8) | ((minor & 0xfff00) << 12)
}
pub fn decode_devnum(raw: u32) -> (u32, u32) {
let major = (raw >> 8) & 0xfff;
let minor = (raw & 0xff) | ((raw >> 12) & 0xfff00);
(major, minor)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn regular_inode_roundtrip() {
let ino = Inode::regular(12345, 0o644, 1000, 1000, 0xdeadbeef);
let buf = ino.encode();
let decoded = Inode::decode(&buf);
assert_eq!(decoded.mode, ino.mode);
assert_eq!(decoded.size, ino.size);
assert_eq!(decoded.uid, ino.uid);
assert_eq!(decoded.gid, ino.gid);
assert_eq!(decoded.mtime, 0xdeadbeef);
assert_eq!(decoded.links_count, 1);
assert_eq!(decoded.mode & 0o170000, S_IFREG);
}
#[test]
fn dir_inode_starts_with_two_links() {
let ino = Inode::directory(1024, 0o755, 0, 0, 0);
assert_eq!(ino.links_count, 2);
assert_eq!(ino.mode & 0o170000, S_IFDIR);
}
#[test]
fn devnum_encoding_matches_kernel() {
let v = encode_devnum(1, 3);
assert_eq!(v, 0x103);
let v = encode_devnum(8, 0x1234);
let expected: u32 = 0x34 | 0x800 | 0x0120_0000;
assert_eq!(v, expected);
}
}