use crate::le::{put_u16, put_u32, u16 as le16, u32 as le32};
use crate::spec::consts::{EXTENT_MAGIC, INODE_SIZE};
use crate::{corrupt, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)] pub struct Inode {
pub mode: u16,
pub uid: u32,
pub gid: u32,
pub size: u64,
pub atime: u32,
pub ctime: u32,
pub mtime: u32,
pub dtime: u32,
pub links_count: u16,
pub blocks: u64,
pub flags: u32,
pub version: u64,
pub block: [u8; 60],
pub generation: u32,
pub file_acl: u64,
pub extra_isize: u16,
pub checksum: u32,
pub ctime_extra: u32,
pub mtime_extra: u32,
pub atime_extra: u32,
pub crtime: u32,
pub crtime_extra: u32,
pub projid: u32,
pub ibody: Vec<u8>,
}
impl Default for Inode {
fn default() -> Self {
Inode {
mode: 0,
uid: 0,
gid: 0,
size: 0,
atime: 0,
ctime: 0,
mtime: 0,
dtime: 0,
links_count: 0,
blocks: 0,
flags: 0,
version: 0,
block: [0; 60],
generation: 0,
file_acl: 0,
extra_isize: 0,
checksum: 0,
ctime_extra: 0,
mtime_extra: 0,
atime_extra: 0,
crtime: 0,
crtime_extra: 0,
projid: 0,
ibody: Vec::new(),
}
}
}
impl Inode {
pub fn decode(b: &[u8]) -> Result<Inode> {
if b.len() < 128 {
return Err(corrupt("inode", "short buffer"));
}
let mut ino = Inode {
mode: le16(b, 0x00),
uid: u32::from(le16(b, 0x02)) | u32::from(le16(b, 0x78)) << 16,
gid: u32::from(le16(b, 0x18)) | u32::from(le16(b, 0x7A)) << 16,
size: u64::from(le32(b, 0x04)) | u64::from(le32(b, 0x6C)) << 32,
atime: le32(b, 0x08),
ctime: le32(b, 0x0C),
mtime: le32(b, 0x10),
dtime: le32(b, 0x14),
links_count: le16(b, 0x1A),
blocks: u64::from(le32(b, 0x1C)) | u64::from(le16(b, 0x74)) << 32,
flags: le32(b, 0x20),
version: u64::from(le32(b, 0x24)),
block: b[0x28..0x64].try_into().unwrap(),
generation: le32(b, 0x64),
file_acl: u64::from(le32(b, 0x68)) | u64::from(le16(b, 0x76)) << 32,
extra_isize: 0,
checksum: u32::from(le16(b, 0x7C)),
ctime_extra: 0,
mtime_extra: 0,
atime_extra: 0,
crtime: 0,
crtime_extra: 0,
projid: 0,
ibody: Vec::new(),
};
if b.len() > 0x80 {
ino.extra_isize = le16(b, 0x80);
let extra_end = 0x80 + usize::from(ino.extra_isize);
let has = |field_end: usize| b.len() >= field_end && extra_end >= field_end;
if has(0x84) {
ino.checksum |= u32::from(le16(b, 0x82)) << 16;
}
if has(0x88) {
ino.ctime_extra = le32(b, 0x84);
}
if has(0x8C) {
ino.mtime_extra = le32(b, 0x88);
}
if has(0x90) {
ino.atime_extra = le32(b, 0x8C);
}
if has(0x94) {
ino.crtime = le32(b, 0x90);
}
if has(0x98) {
ino.crtime_extra = le32(b, 0x94);
}
if has(0x9C) {
ino.version |= u64::from(le32(b, 0x98)) << 32;
}
if has(0xA0) {
ino.projid = le32(b, 0x9C);
}
let area = &b[extra_end.min(b.len())..];
if area.iter().any(|&x| x != 0) {
ino.ibody = area.to_vec();
}
}
Ok(ino)
}
pub fn encode(&self, out: &mut [u8]) {
let b = &mut out[..INODE_SIZE];
b.fill(0);
put_u16(b, 0x00, self.mode);
put_u16(b, 0x02, self.uid as u16);
put_u32(b, 0x04, self.size as u32);
put_u32(b, 0x08, self.atime);
put_u32(b, 0x0C, self.ctime);
put_u32(b, 0x10, self.mtime);
put_u32(b, 0x14, self.dtime);
put_u16(b, 0x18, self.gid as u16);
put_u16(b, 0x1A, self.links_count);
put_u32(b, 0x1C, self.blocks as u32);
put_u32(b, 0x20, self.flags);
put_u32(b, 0x24, self.version as u32);
b[0x28..0x64].copy_from_slice(&self.block);
put_u32(b, 0x64, self.generation);
put_u32(b, 0x68, self.file_acl as u32);
put_u32(b, 0x6C, (self.size >> 32) as u32);
put_u16(b, 0x74, (self.blocks >> 32) as u16);
put_u16(b, 0x76, (self.file_acl >> 32) as u16);
put_u16(b, 0x78, (self.uid >> 16) as u16);
put_u16(b, 0x7A, (self.gid >> 16) as u16);
put_u16(b, 0x7C, self.checksum as u16);
put_u16(b, 0x80, self.extra_isize);
if self.extra_isize >= 4 {
put_u16(b, 0x82, (self.checksum >> 16) as u16);
}
if self.extra_isize >= 8 {
put_u32(b, 0x84, self.ctime_extra);
}
if self.extra_isize >= 12 {
put_u32(b, 0x88, self.mtime_extra);
}
if self.extra_isize >= 16 {
put_u32(b, 0x8C, self.atime_extra);
}
if self.extra_isize >= 20 {
put_u32(b, 0x90, self.crtime);
}
if self.extra_isize >= 24 {
put_u32(b, 0x94, self.crtime_extra);
}
if self.extra_isize >= 28 {
put_u32(b, 0x98, (self.version >> 32) as u32);
}
if self.extra_isize >= 32 {
put_u32(b, 0x9C, self.projid);
}
if !self.ibody.is_empty() {
let start = 0x80 + usize::from(self.extra_isize);
b[start..start + self.ibody.len()].copy_from_slice(&self.ibody);
}
}
pub fn file_type(&self) -> FileType {
match self.mode >> 12 {
0o01 => FileType::Fifo,
0o02 => FileType::CharDev,
0o04 => FileType::Dir,
0o06 => FileType::BlockDev,
0o10 => FileType::Regular,
0o12 => FileType::Symlink,
0o14 => FileType::Socket,
_ => FileType::Unknown,
}
}
pub fn uses_extents(&self) -> bool {
self.flags & crate::spec::iflags::EXTENTS != 0
}
pub fn fast_symlink_target(&self) -> Option<&[u8]> {
if self.file_type() == FileType::Symlink && !self.uses_extents() && self.size <= 60 {
Some(&self.block[..self.size as usize])
} else {
None
}
}
pub fn dev_numbers(&self) -> Option<(u32, u32)> {
match self.file_type() {
FileType::CharDev | FileType::BlockDev => {
let w0 = le32(&self.block, 0);
let w1 = le32(&self.block, 4);
Some(if w0 != 0 {
(w0 >> 8 & 0xFF, w0 & 0xFF)
} else {
(w1 >> 8 & 0xFFF, (w1 & 0xFF) | (w1 >> 12 & !0xFFu32))
})
}
_ => None,
}
}
pub fn extent_root(&self) -> Option<&[u8]> {
if self.uses_extents() && le16(&self.block, 0) == EXTENT_MAGIC {
Some(&self.block[..])
} else {
None
}
}
pub fn timestamp(secs: u32, extra: u32) -> (i64, u32) {
let epoch_bits = i64::from(extra & 0x3);
(i64::from(secs as i32) + (epoch_bits << 32), extra >> 2)
}
pub fn encode_timestamp(secs: i64, nsec: u32) -> (u32, u32) {
let epoch_bits = (((secs + (1 << 31)) >> 32) & 0x3) as u32;
(secs as u32, (nsec << 2) | epoch_bits)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum FileType {
Regular,
Dir,
Symlink,
CharDev,
BlockDev,
Fifo,
Socket,
Unknown,
}
#[cfg(test)]
mod tests {
use super::*;
fn vector(name: &str) -> Vec<u8> {
std::fs::read(format!(
"{}/testdata/vectors/{name}",
env!("CARGO_MANIFEST_DIR")
))
.unwrap()
}
#[test]
fn roundtrip_all_inode_vectors() {
for name in [
"inode_1_bad_blocks.bin",
"inode_2_root.bin",
"inode_8_journal.bin",
"inode_11_lost_found.bin",
"inode_small_txt.bin",
"inode_empty.bin",
"inode_sym_59.bin",
"inode_sym_60.bin",
"inode_dev_c_old.bin",
"inode_dev_c_new.bin",
"inode_sparse_small.bin",
"inode_xattr_ibody.bin",
"inode_xattr_mixed.bin",
"inode_xattr_block.bin",
"inode_bigdir_dx.bin",
] {
let raw = vector(name);
let ino = Inode::decode(&raw).unwrap();
let mut out = vec![0u8; INODE_SIZE];
ino.encode(&mut out);
assert_eq!(out, raw, "{name} does not round-trip byte-exactly");
}
}
#[test]
fn typed_accessors() {
let root = Inode::decode(&vector("inode_2_root.bin")).unwrap();
assert_eq!(root.file_type(), FileType::Dir);
assert!(root.uses_extents());
assert!(root.extent_root().is_some());
let sym = Inode::decode(&vector("inode_sym_59.bin")).unwrap();
assert_eq!(sym.fast_symlink_target(), Some(&[b'a'; 59][..]));
let slow = Inode::decode(&vector("inode_sym_60.bin")).unwrap();
assert_eq!(slow.fast_symlink_target(), None);
assert!(slow.uses_extents());
let dev_old = Inode::decode(&vector("inode_dev_c_old.bin")).unwrap();
assert_eq!(dev_old.dev_numbers(), Some((5, 1)));
let dev_new = Inode::decode(&vector("inode_dev_c_new.bin")).unwrap();
assert_eq!(dev_new.dev_numbers(), Some((254, 300)));
let empty = Inode::decode(&vector("inode_empty.bin")).unwrap();
assert_eq!(empty.size, 0);
assert!(empty.extent_root().is_some(), "empty file keeps a header");
}
#[test]
fn timestamp_encoding() {
assert_eq!(Inode::timestamp(1_600_000_000, 0), (1_600_000_000, 0));
let secs = i64::from(u32::MAX) + 100;
let (s, e) = Inode::encode_timestamp(secs, 7);
assert_eq!(Inode::timestamp(s, e), (secs, 7));
let (s, e) = Inode::encode_timestamp(-1, 0);
assert_eq!(Inode::timestamp(s, e), (-1, 0));
}
}