use { crate::{filetype::Flag, Error, Special}, std::{ cmp::Ordering, fmt, io::{Read, Seek, SeekFrom}, }, }; #[derive(Debug, PartialEq, Eq)] pub enum Kind { Normal(u64), HardLink(String), SoftLink(String), Directory, Character(Special), Block(Special), Fifo, Eof, } impl Kind { #[allow(clippy::cast_possible_wrap)] fn read(reader: &mut R, flag: Flag) -> Result { match flag { Flag::Normal => { let mut len = [0; 8]; reader.read_exact(&mut len)?; let len = u64::from_le_bytes(len); let mut buf = [0; 1]; reader.read_exact(&mut buf)?; match buf[0] { 0 => { let _bytes = reader.seek(SeekFrom::Current(len as i64 + 16))?; } 1 => { let _bytes = reader.seek(SeekFrom::Current(len as i64 + 20))?; } 2 => { let _bytes = reader.seek(SeekFrom::Current(len as i64 + 32))?; } _ => { let _bytes = reader.seek(SeekFrom::Current(len as i64))?; } } Ok(Self::Normal(len)) } Flag::HardLink => { let mut len = [0; 2]; reader.read_exact(&mut len)?; let len = u16::from_le_bytes(len); let mut buf = Vec::with_capacity(len.into()); let mut handle = reader.take(len.into()); handle.read_to_end(&mut buf)?; let s = String::from_utf8(buf)?; Ok(Self::HardLink(s)) } Flag::SoftLink => { let mut len = [0; 2]; reader.read_exact(&mut len)?; let len = u16::from_le_bytes(len); let mut buf = Vec::with_capacity(len.into()); let mut handle = reader.take(len.into()); handle.read_to_end(&mut buf)?; let s = String::from_utf8(buf)?; Ok(Self::SoftLink(s)) } Flag::Directory => Ok(Self::Directory), Flag::Character => { let sp = Special::read(reader)?; Ok(Self::Character(sp)) } Flag::Block => { let sp = Special::read(reader)?; Ok(Self::Block(sp)) } Flag::Fifo => Ok(Self::Fifo), Flag::Eof => Ok(Self::Eof), } } } #[derive(Debug, PartialEq, Eq)] pub struct Listing { pub name: String, pub uid: u32, pub gid: u32, pub mtime: u64, pub mode: u16, pub kind: Kind, } impl PartialOrd for Listing { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Listing { fn cmp(&self, other: &Self) -> Ordering { match (&self.kind, &other.kind) { (Kind::Directory, Kind::Directory) => self.name.cmp(&other.name), (Kind::Directory, _) => Ordering::Less, (_, Kind::Directory) => Ordering::Greater, _ => self.name.cmp(&other.name), } } } impl fmt::Display for Listing { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}{}{}{}{}{}{}{}{}{} {}:{} {:>10}", match self.kind { Kind::Normal(_) => "-", Kind::HardLink(_) => "L", Kind::SoftLink(_) => "l", Kind::Directory => "d", Kind::Character(_) => "c", Kind::Block(_) => "b", Kind::Fifo => "p", Kind::Eof => return Ok(()), }, match self.mode { m if m & 0o400 != 0 => "r", _ => "-", }, match self.mode { m if m & 0o200 != 0 => "w", _ => "-", }, match self.mode { m if m & 0o4100 != 0 => "S", m if m & 0o100 != 0 => "x", _ => "-", }, match self.mode { m if m & 0o40 != 0 => "r", _ => "-", }, match self.mode { m if m & 0o20 != 0 => "w", _ => "-", }, match self.mode { m if m & 0o2010 != 0 => "S", m if m & 0o10 != 0 => "x", _ => "-", }, match self.mode { m if m & 0o4 != 0 => "r", _ => "-", }, match self.mode { m if m & 0o2 != 0 => "w", _ => "-", }, match self.mode { m if m & 0o1001 != 0 => "t", m if m & 0o1 != 0 => "x", _ => "-", }, self.uid, self.gid, match self.kind { Kind::Normal(s) => s, _ => 0, }, )?; match self.kind { Kind::Directory | Kind::Fifo | Kind::Normal(_) => write!(f, "{}", self.name), Kind::HardLink(ref tgt) => write!(f, "{}=>{}", self.name, tgt), Kind::SoftLink(ref tgt) => write!(f, "{}->{}", self.name, tgt), Kind::Character(ref sp) | Kind::Block(ref sp) => { write!(f, "{} {},{}", self.name, sp.major, sp.minor) } Kind::Eof => unreachable!(), } } } impl Listing { /// Reads all metadata from a haggis Node without reading the file's actual /// data. /// # Errors /// Can return an error if IO fails #[allow(clippy::similar_names)] pub fn read(reader: &mut R) -> Result { let mut len = [0; 2]; reader.read_exact(&mut len)?; let len = u16::from_le_bytes(len); if len == 0 { return Ok(Self { name: String::new(), mode: 0, uid: 0, gid: 0, mtime: 0, kind: Kind::Eof, }); } let mut name = Vec::with_capacity(len.into()); let mut handle = reader.take(len.into()); handle.read_to_end(&mut name)?; let mut buf = [0; 18]; reader.read_exact(&mut buf)?; let uid: [u8; 4] = buf[0..4].try_into()?; let gid: [u8; 4] = buf[4..8].try_into()?; let mtime: [u8; 8] = buf[8..16].try_into()?; let raw_mode: [u8; 2] = buf[16..18].try_into()?; let raw_mode = u16::from_le_bytes(raw_mode); let (flag, mode) = Flag::extract_from_raw(raw_mode)?; let kind = Kind::read(reader, flag)?; Ok(Self { name: String::from_utf8(name)?, uid: u32::from_le_bytes(uid), gid: u32::from_le_bytes(gid), mtime: u64::from_le_bytes(mtime), mode, kind, }) } } #[cfg(test)] mod tests { use super::*; use crate::{Algorithm, Node}; use std::{ collections::HashMap, fs, io::{BufReader, BufWriter, Write}, sync::Mutex, }; #[test] fn ord() { let links = Mutex::new(HashMap::new()); { let mut node = Node::from_path("test", Algorithm::Skip, &links).unwrap(); let fd = fs::File::create("test/ord_test.hag").unwrap(); let mut writer = BufWriter::new(fd); node.write(&mut writer).unwrap(); node = Node::from_path("Cargo.toml", Algorithm::Skip, &links).unwrap(); node.write(&mut writer).unwrap(); node = Node::from_path("Cargo.lock", Algorithm::Skip, &links).unwrap(); node.write(&mut writer).unwrap(); writer.flush().unwrap(); } let fd = fs::File::open("test/ord_test.hag").unwrap(); let mut reader = BufReader::new(fd); let test_listing = Listing::read(&mut reader).unwrap(); let cargo_toml_listing = Listing::read(&mut reader).unwrap(); assert!(test_listing < cargo_toml_listing); let cargo_lock_listing = Listing::read(&mut reader).unwrap(); assert!(test_listing < cargo_lock_listing); assert!(cargo_lock_listing < cargo_toml_listing); } }