use { crate::{Checksum, Error, File, FileType, Special}, md5::{Digest, Md5}, sha1::Sha1, sha2::Sha256, std::{ collections::HashMap, fs, io::{BufReader, Read, Write}, os::unix::fs::MetadataExt, sync::Mutex, }, }; #[derive(Debug, PartialEq)] enum Kind { Normal, Dir, Char, Block, Pipe, } impl From for Kind { fn from(value: u32) -> Self { if value & 0o60000 != 0 { Self::Block } else if value & 0o20000 != 0 { Self::Char } else if value & 0o10000 != 0 { Self::Pipe } else if value & 0o40000 != 0 { Self::Dir } else if value & 0o100000 != 0 { Self::Normal } else { panic!(); } } } #[derive(Debug)] pub struct Node { pub name: String, pub mode: u32, pub uid: u32, pub gid: u32, pub mtime: u64, pub filetype: FileType, } impl Node { pub fn read(reader: &mut T) -> Result { let mut len = [0; 8]; reader.read_exact(&mut len)?; let len = u64::from_le_bytes(len); let mut name = Vec::with_capacity(len.try_into()?); let mut handle = reader.take(len); handle.read_exact(&mut name)?; let mut buf = [0; 20]; reader.read_exact(&mut buf)?; let mode: [u8; 4] = buf[..4].try_into()?; let uid: [u8; 4] = buf[4..8].try_into()?; let gid: [u8; 4] = buf[8..12].try_into()?; let mtime: [u8; 8] = buf[12..].try_into()?; let filetype = FileType::read(reader)?; Ok(Self { name: String::from_utf8(name)?, mode: u32::from_le_bytes(mode), uid: u32::from_le_bytes(uid), gid: u32::from_le_bytes(gid), mtime: u64::from_le_bytes(mtime), filetype, }) } pub fn write(&self, writer: &mut T) -> Result<(), Error> { let len = self.name.len() as u64; writer.write_all(&len.to_le_bytes())?; writer.write_all(self.name.as_bytes())?; [self.mode, self.uid, self.gid] .iter() .try_for_each(|f| writer.write_all(&f.to_le_bytes()))?; writer.write_all(&self.mtime.to_le_bytes())?; self.filetype.write(writer)?; Ok(()) } pub fn from_path( path: &str, checksum: Checksum, links: Mutex>, ) -> Result { let name = String::from(path); let fd = fs::File::open(path)?; let meta = fd.metadata()?; let mode = meta.mode(); let uid = meta.uid(); let gid = meta.gid(); let mtime = meta.mtime().try_into()?; let mut reader = BufReader::new(fd); let ft = meta.file_type(); let filetype = 'blk: { if ft.is_dir() { FileType::Directory } else if ft.is_symlink() { let target = fs::read_link(path)?; let target = target .to_str() .ok_or(Error::Other("bad path".to_string()))? .to_string(); FileType::SoftLink(target) } else { if meta.nlink() > 1 { if let Ok(mut list) = links.lock() { let inode = meta.ino(); if let Some(target) = list.get(&inode).cloned() { break 'blk FileType::HardLink(target); } else { list.insert(inode, name.clone()); } } } let kind = Kind::from(mode); if kind == Kind::Char { let device = Special::from_rdev(meta.rdev().try_into()?); break 'blk FileType::Character(device); } else if kind == Kind::Block { let device = Special::from_rdev(meta.rdev().try_into()?); break 'blk FileType::Block(device); } else if kind == Kind::Pipe { break 'blk FileType::Fifo; } else if kind == Kind::Normal { let mut len = meta.len(); let mut data = Vec::with_capacity(len.try_into()?); len = reader.read_to_end(&mut data)?.try_into()?; let checksum = match checksum { Checksum::Md5(mut cs) => { let mut hasher = Md5::new(); hasher.update(&data); cs = hasher.finalize().into(); Checksum::Md5(cs) } Checksum::Sha1(mut cs) => { let mut hasher = Sha1::new(); hasher.update(&data); cs = hasher.finalize().into(); Checksum::Sha1(cs) } Checksum::Sha256(mut cs) => { let mut hasher = Sha256::new(); hasher.update(&data); cs = hasher.finalize().into(); Checksum::Sha256(cs) } Checksum::Skip => checksum, }; break 'blk FileType::Normal(File { len, checksum, data, }); } else { return Err(Error::UnknownFileType); } } }; Ok(Self { name, mode, uid, gid, mtime, filetype, }) } }