178 lines
5.7 KiB
Rust
178 lines
5.7 KiB
Rust
|
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<u32> 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<T: Read>(reader: &mut T) -> Result<Self, Error> {
|
||
|
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<T: 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<HashMap<u64, String>>,
|
||
|
) -> Result<Self, Error> {
|
||
|
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,
|
||
|
})
|
||
|
}
|
||
|
}
|