taro 0.1.0

In-place tar file extractor for memory-limited systems
//! Utilities and datatypes relating to tar header blocks.

pub mod file_type;
use self::file_type::FileType;

/// Number of bytes in a tar header; all tar data blocks are aligned in null-byte padded blocks of
/// this size.
pub const TAR_BLOCKSIZE: u64 = 512;

/// Represents the data within a tar header.
pub struct TarHeader {
    pub name: String,
    pub mode: usize,
    pub uid: usize,
    pub gid: usize,
    pub size: usize,
    pub mtime: usize,
    pub chksum: usize,
    pub typeflag: FileType,
    pub linkname: String,
    pub uname: String,
    pub gname: String,
    pub devmajor: String,
    pub devminor: String,
    pub prefix: String,
    pub atime: Option<usize>,
    pub ctime: Option<usize>,
}

/// Creates a TarHeader directly from a string read out of a tar archive.
impl std::convert::From<&[u8; TAR_BLOCKSIZE as usize]> for TarHeader {
    fn from(b: &[u8; TAR_BLOCKSIZE as usize]) -> Self {
        let magic = std::str::from_utf8(&b[257..263]).unwrap();
        if magic != "ustar " {
            panic!("This does not look like a tar archive");
        }
        let _version = std::str::from_utf8(&b[263..265]).unwrap();

        let parse_octal = |start: usize, length: usize| {
            usize::from_str_radix(std::str::from_utf8(&b[start..start+length]).unwrap(), 8).unwrap()
        };

        let parse_string = |start: usize, length: usize| {
            std::str::from_utf8(&b[start..start+length]).unwrap().trim_end_matches('\0').to_owned()
        };

        let parse_time = |start: usize, length: usize| {
            let s = std::str::from_utf8(&b[start..start+length]).unwrap();
            if s.trim_end_matches('\0') == "" {
                None
            } else {
                Some(usize::from_str_radix(s, 8).unwrap())
            }
        };

        TarHeader {
            name: parse_string(0, 100),
            mode: parse_octal(100, 7),
            uid: parse_octal(108, 7),
            gid: parse_octal(116, 7),
            size: parse_octal(124, 11),
            mtime: parse_time(136, 11).unwrap(),
            chksum: parse_octal(148, 6),
            typeflag: (b[156] as char).into(),
            linkname: parse_string(157, 100),
            uname: parse_string(265, 32),
            gname: parse_string(297, 32),
            devmajor: parse_string(329, 8),
            devminor: parse_string(337, 8),
            prefix: parse_string(345, 131),
            atime: parse_time(476, 11),
            ctime: parse_time(488, 11),
        }
    }
}

impl std::fmt::Display for TarHeader {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        let atime = match self.atime {
            Some(atime) => atime,
            None => 0,
        };
        let ctime = match self.ctime {
            Some(ctime) => ctime,
            None => 0,
        };
        match self.typeflag {
            FileType::SoftLink => {
                write!(f, "{}{:o} {}({}):{}({}) {} {} -> {}, {}, {}, ({})??, {}, {}",
                    self.typeflag,
                    self.mode,
                    self.gname,
                    self.gid,
                    self.uname,
                    self.uid,
                    self.size,
                    self.name,
                    self.linkname,
                    self.mtime,
                    self.chksum,
                    self.prefix,
                    atime,
                    ctime)
            }
            FileType::HardLink => {
                write!(f, "{}{:o} {}({}):{}({}) {} {} link to {}, {}, {}, ({})??, {}, {}",
                    self.typeflag,
                    self.mode,
                    self.gname,
                    self.gid,
                    self.uname,
                    self.uid,
                    self.size,
                    self.name,
                    self.linkname,
                    self.mtime,
                    self.chksum,
                    self.prefix,
                    atime,
                    ctime)
            }
            FileType::CharacterSpecial => {
                write!(f, "{}{:o} {}({}):{}({}) {} {} = {}/{}, {}, {}, ({})??, {}, {}",
                    self.typeflag,
                    self.mode,
                    self.gname,
                    self.gid,
                    self.uname,
                    self.uid,
                    self.size,
                    self.name,
                    self.devmajor,
                    self.devminor,
                    self.mtime,
                    self.chksum,
                    self.prefix,
                    atime,
                    ctime)
            }
            _ => {
                write!(f, "{}{:o} {}({}):{}({}) {} {}, {}, {}, ({})??, {}, {}",
                    self.typeflag,
                    self.mode,
                    self.gname,
                    self.gid,
                    self.uname,
                    self.uid,
                    self.size,
                    self.name,
                    self.mtime,
                    self.chksum,
                    self.prefix,
                    atime,
                    ctime)
            }
        }
    }
}