#![allow(non_upper_case_globals)]
use crate::{BLOCKSIZE, NAME_LEN, PREFIX_LEN, TarFormatDecimal, TarFormatOctal, TarFormatString};
use core::error::Error;
use core::fmt::{Debug, Display, Formatter};
use core::num::ParseIntError;
#[derive(Debug)]
pub enum ModeError {
ParseInt(ParseIntError),
IllegalMode,
}
impl Display for ModeError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
Debug::fmt(self, f)
}
}
impl Error for ModeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::ParseInt(e) => Some(e),
Self::IllegalMode => None,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct Mode(TarFormatOctal<8>);
impl Mode {
pub fn to_flags(self) -> Result<ModeFlags, ModeError> {
let bits = self.0.as_number::<u64>().map_err(ModeError::ParseInt)?;
ModeFlags::from_bits(bits).ok_or(ModeError::IllegalMode)
}
}
impl Debug for Mode {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
Debug::fmt(&self.to_flags(), f)
}
}
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq)]
pub struct InvalidTypeFlagError(u8);
impl Display for InvalidTypeFlagError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!("{:x} is not a valid TypeFlag", self.0))
}
}
impl core::error::Error for InvalidTypeFlagError {}
#[derive(Copy, Clone, PartialOrd, PartialEq, Eq)]
pub struct TypeFlagRaw(u8);
impl TypeFlagRaw {
pub fn try_to_type_flag(self) -> Result<TypeFlag, InvalidTypeFlagError> {
TypeFlag::try_from(self)
}
}
impl Debug for TypeFlagRaw {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
Debug::fmt(&self.try_to_type_flag(), f)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
#[allow(unused)]
pub enum TypeFlag {
REGTYPE = b'0',
AREGTYPE = b'\0',
LINK = b'1',
SYMTYPE = b'2',
CHRTYPE = b'3',
BLKTYPE = b'4',
DIRTYPE = b'5',
FIFOTYPE = b'6',
CONTTYPE = b'7',
XHDTYPE = b'x',
XGLTYPE = b'g',
}
impl TypeFlag {
#[must_use]
pub fn is_regular_file(self) -> bool {
self == Self::AREGTYPE || self == Self::REGTYPE
}
}
impl TryFrom<TypeFlagRaw> for TypeFlag {
type Error = InvalidTypeFlagError;
fn try_from(value: TypeFlagRaw) -> Result<Self, Self::Error> {
match value.0 {
b'0' => Ok(Self::REGTYPE),
b'\0' => Ok(Self::AREGTYPE),
b'1' => Ok(Self::LINK),
b'2' => Ok(Self::SYMTYPE),
b'3' => Ok(Self::CHRTYPE),
b'4' => Ok(Self::BLKTYPE),
b'5' => Ok(Self::DIRTYPE),
b'6' => Ok(Self::FIFOTYPE),
b'7' => Ok(Self::CONTTYPE),
b'x' => Ok(Self::XHDTYPE),
b'g' => Ok(Self::XGLTYPE),
e => Err(InvalidTypeFlagError(e)),
}
}
}
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ModeFlags: u64 {
const SetUID = 0o4000;
const SetGID = 0o2000;
const TSVTX = 0o1000;
const OwnerRead = 0o400;
const OwnerWrite = 0o200;
const OwnerExec = 0o100;
const GroupRead = 0o040;
const GroupWrite = 0o020;
const GroupExec = 0o010;
const OthersRead = 0o004;
const OthersWrite = 0o002;
const OthersExec = 0o001;
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(C, packed)]
pub struct PosixHeader {
pub name: TarFormatString<NAME_LEN>,
pub mode: Mode,
pub uid: TarFormatOctal<8>,
pub gid: TarFormatOctal<8>,
pub size: TarFormatOctal<12>,
pub mtime: TarFormatDecimal<12>,
pub cksum: TarFormatOctal<8>,
pub typeflag: TypeFlagRaw,
pub linkname: TarFormatString<NAME_LEN>,
pub magic: TarFormatString<6>,
pub version: TarFormatString<2>,
pub uname: TarFormatString<32>,
pub gname: TarFormatString<32>,
pub dev_major: TarFormatOctal<8>,
pub dev_minor: TarFormatOctal<8>,
pub prefix: TarFormatString<PREFIX_LEN>,
pub _pad: [u8; 12],
}
impl PosixHeader {
pub fn payload_block_count(&self) -> Result<usize, ParseIntError> {
let parsed_size = self.size.as_number::<usize>()?;
Ok(parsed_size.div_ceil(BLOCKSIZE))
}
#[must_use]
pub fn is_zero_block(&self) -> bool {
let ptr = core::ptr::addr_of!(*self);
let ptr = ptr.cast::<u8>();
let self_bytes = unsafe { core::slice::from_raw_parts(ptr, BLOCKSIZE) };
self_bytes.iter().filter(|x| **x == 0).count() == BLOCKSIZE
}
}
#[cfg(test)]
mod tests {
use crate::BLOCKSIZE;
use crate::header::{PosixHeader, TypeFlag};
use std::mem::size_of;
fn bytes_to_archive(tar_archive_data: &[u8]) -> &PosixHeader {
unsafe { (tar_archive_data.as_ptr().cast::<PosixHeader>()).as_ref() }.unwrap()
}
#[test]
fn test_display_header() {
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_default.tar"));
assert_eq!(archive.name.as_str(), Ok("bye_world_513b.txt"));
println!("{archive:#?}'");
}
#[test]
fn test_payload_block_count() {
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_default.tar"));
assert_eq!(archive.payload_block_count(), Ok(2));
}
#[test]
fn test_show_tar_header_magics() {
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_default.tar"));
println!(
"default: magic='{:?}', version='{:?}'",
archive.magic, archive.version
);
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_gnu.tar"));
println!(
"gnu: magic='{:?}', version='{:?}'",
archive.magic, archive.version
);
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_oldgnu.tar"));
println!(
"oldgnu: magic='{:?}', version='{:?}'",
archive.magic, archive.version
);
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_pax.tar"));
println!(
"pax: magic='{:?}', version='{:?}'",
archive.magic, archive.version
);
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_posix.tar"));
println!(
"posix: magic='{:?}', version='{:?}'",
archive.magic, archive.version
);
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_ustar.tar"));
println!(
"ustar: magic='{:?}', version='{:?}'",
archive.magic, archive.version
);
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_v7.tar"));
println!(
"v7: magic='{:?}', version='{:?}'",
archive.magic, archive.version
);
}
#[test]
fn test_parse_tar_header_filename() {
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_default.tar"));
assert_eq!(
archive.typeflag.try_to_type_flag(),
Ok(TypeFlag::REGTYPE),
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_str(), Ok("bye_world_513b.txt"));
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_gnu.tar"));
assert_eq!(
archive.typeflag.try_to_type_flag(),
Ok(TypeFlag::REGTYPE),
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_str(), Ok("bye_world_513b.txt"));
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_oldgnu.tar"));
assert_eq!(
archive.typeflag.try_to_type_flag(),
Ok(TypeFlag::REGTYPE),
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_str(), Ok("bye_world_513b.txt"));
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_ustar.tar"));
assert_eq!(
archive.typeflag.try_to_type_flag(),
Ok(TypeFlag::REGTYPE),
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_str(), Ok("bye_world_513b.txt"));
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_v7.tar"));
assert_eq!(
archive.typeflag.try_to_type_flag(),
Ok(TypeFlag::AREGTYPE),
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_str(), Ok("bye_world_513b.txt"));
}
#[test]
fn test_size() {
assert_eq!(BLOCKSIZE, size_of::<PosixHeader>());
}
}