packtool 0.6.0

In memory packing made easy
Documentation
use packtool::{Packed, View};

fn main() {
    assert_eq!(Header::SIZE, 512,);

    let bytes = std::fs::read("example.tar").expect("need example tar file");
    let mut sub = bytes.as_slice();
    while sub.len() > Header::SIZE {
        let header_view = View::<'_, Header>::try_from_slice(&sub[..Header::SIZE])
            .expect("should start with a header");
        let header = header_view.unpack();

        let file = std::str::from_utf8(&header.filename.0).expect("valid filename");

        let file_size = header.file_size.to_size();
        let file_padding = 512 - file_size % 512;

        println!("compressed file: {} ({} bytes)", file, file_size);

        sub = &sub[Header::SIZE + file_size + file_padding..];

        if sub.len() == 1024 {
            break;
        }
    }
}

#[derive(Packed)]
pub struct FileName([u8; 100]);

#[derive(Packed)]
pub struct FileMode([u8; 8]);

#[derive(Packed)]
pub struct Owner(u64);

#[derive(Packed)]
pub struct Group(u64);

#[derive(Packed)]
pub struct FileSize([u8; 12]);

#[derive(Packed)]
pub struct LastUpdate([u8; 12]);

#[derive(Packed)]
pub struct Checksum([u8; 8]);

#[derive(Packed)]
#[repr(u8)]
pub enum TypeFlag {
    NormalFile = 0x30,
    HardLink = 0x31,
    SymbolicLink = 0x32,
    CharacterSpecial = 0x33,
    BlockSpecial = 0x34,
    Directory = 0x35,
    Fifo = 0x36,
    ContiguousFile = 0x37,
    GlobalExtended = 0x67,
    ExtendedHeader = 0x78,
    // TODO: support for enum with field value size == enum size
    // #[packed(range = "0x41..=0x5a")]
    // Vendor(u8)
}

#[derive(Packed)]
#[packed(value = b"ustar\x00")]
pub struct UStar;

#[derive(Packed)]
#[packed(value = b"00")]
pub struct Version;

#[derive(Packed)]
pub struct OwnerUserName([u8; 32]);

#[derive(Packed)]
pub struct OwnerGroupName([u8; 32]);

#[derive(Packed)]
pub struct DeviceMajorNumber([u8; 8]);

#[derive(Packed)]
pub struct DeviceMinorNumber([u8; 8]);

#[derive(Packed)]
pub struct FileNamePrefix([u8; 155]);

#[derive(Packed)]
#[packed(value = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")]
pub struct HeaderPadding;

#[derive(Packed)]
pub struct Header {
    filename: FileName,
    file_mode: FileMode,
    owner: Owner,
    group: Group,
    file_size: FileSize,
    last_update: LastUpdate,
    checksum: Checksum,
    type_flag: TypeFlag,
    linked_file: FileName,
    ustar: UStar,
    version: Version,
    user_name: OwnerUserName,
    group_name: OwnerGroupName,
    device_major_number: DeviceMajorNumber,
    device_minor_number: DeviceMinorNumber,
    filename_prefix: FileNamePrefix,
    _padding: HeaderPadding,
}

impl FileSize {
    fn to_size(&self) -> usize {
        let mut size = 0;
        for o in self.0.iter().copied() {
            match o {
                b'0'..=b'8' => {
                    size = (size * 8) + (o - b'0') as usize;
                }
                0x00 | 0x20 => {}
                _ => {
                    panic!("unknown octal value: {:?}", o)
                }
            }
        }
        size
    }
}