commit 0ddfb168f50494ee3b1304228f90472fd4b23dc3 Author: Nathan Fisher Date: Sun Jul 2 00:44:39 2023 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d3d8c6a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "pk-rs" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a9c50fc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pk-rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..453ed02 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,191 @@ +use std::{string::FromUtf8Error, array::TryFromSliceError, fmt, io::{Write, self, Read}, num::TryFromIntError}; + +#[derive(Debug)] +pub enum Error { + Int(TryFromIntError), + Io(io::Error), + Utf8(FromUtf8Error), + Slice(TryFromSliceError), + UnknownFileType, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Int(e) => write!(f, "{e}"), + Self::Utf8(e) => write!(f, "{e}"), + Self::Slice(e) => write!(f, "{e}"), + Self::Io(e) => write!(f, "{e}"), + Self::UnknownFileType => write!(f, "unknown file type"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Int(e) => Some(e), + Self::Io(e) => Some(e), + Self::Utf8(e) => Some(e), + Self::Slice(e) => Some(e), + _ => None, + } + } +} + +impl From for Error { + fn from(value: TryFromIntError) -> Self { + Self::Int(value) + } +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::Io(value) + } +} + +impl From for Error { + fn from(value: FromUtf8Error) -> Self { + Self::Utf8(value) + } +} + +impl From for Error { + fn from(value: TryFromSliceError) -> Self { + Self::Slice(value) + } +} + +#[derive(Debug)] +pub struct Special { + pub major: u32, + pub minor: u32, +} + +impl Special { + pub fn read(reader: &mut T) -> Result { + let mut buf = [0; 8]; + reader.read_exact(&mut buf)?; + let major: [u8; 4] = buf[..4].try_into()?; + let major = u32::from_le_bytes(major); + let minor: [u8; 4] = buf[4..].try_into()?; + let minor = u32::from_le_bytes(minor); + Ok(Self { major, minor }) + } + + pub fn write(&self, writer: &mut T) -> Result<(), Error> { + writer.write_all(&self.major.to_le_bytes())?; + writer.write_all(&self.minor.to_le_bytes())?; + Ok(()) + } +} + +#[repr(u8)] +#[derive(Debug)] +pub enum FileType { + Normal = 0, + HardLink(String), + SoftLink(String), + Directory, + Character(Special), + Block(Special), + Fifo, +} + +impl FileType { + pub fn read(reader: &mut T) -> Result { + let mut buf = [0; 1]; + reader.read_exact(&mut buf)?; + match buf[0] { + 0 => Ok(Self::Normal), + 1 => { + let mut l = [0; 8]; + reader.read_exact(&mut l)?; + let len = u64::from_le_bytes(l); + let mut buf = Vec::with_capacity(len.try_into()?); + let mut handle = reader.take(len); + handle.read(&mut buf)?; + let s = String::from_utf8(buf)?; + Ok(Self::HardLink(s)) + }, + 2 => { + let mut l = [0; 8]; + reader.read_exact(&mut l)?; + let len = u64::from_le_bytes(l); + let mut buf = Vec::with_capacity(len.try_into()?); + let mut handle = reader.take(len); + handle.read(&mut buf)?; + let s = String::from_utf8(buf)?; + Ok(Self::SoftLink(s)) + }, + 3 => Ok(Self::Directory), + 4 => { + let sp = Special::read(reader)?; + Ok(Self::Character(sp)) + }, + 5 => { + let sp = Special::read(reader)?; + Ok(Self::Block(sp)) + }, + 6 => Ok(Self::Fifo), + _ => Err(Error::UnknownFileType), + } + } + + pub fn write(&self, writer: &mut T) -> Result<(), Error> { + match self { + Self::Normal => writer.write_all(&[0])?, + Self::HardLink(s) => { + writer.write_all(&[1])?; + let len = s.len() as u64; + writer.write_all(&len.to_le_bytes())?; + writer.write_all(s.as_bytes())?; + }, + Self::SoftLink(s) => { + writer.write_all(&[2])?; + let len = s.len() as u64; + writer.write_all(&len.to_le_bytes())?; + writer.write_all(s.as_bytes())?; + }, + Self::Directory => writer.write_all(&[3])?, + Self::Character(s) => { + writer.write_all(&[4])?; + s.write(writer)?; + }, + Self::Block(s) => { + writer.write_all(&[5])?; + s.write(writer)?; + }, + Self::Fifo => writer.write_all(&[6])?, + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct Header { + pub name: String, + pub filetype: FileType, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub size: u64, + pub mtime: u64, +} + +impl Header { + 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.filetype.write(writer)?; + [self.mode, self.uid, self.gid].iter().try_for_each(|f| { + writer.write_all(&f.to_le_bytes()) + })?; + [self.size, self.mtime].iter().try_for_each(|f| { + writer.write_all(&f.to_le_bytes()) + })?; + Ok(()) + } +}