#![allow(non_upper_case_globals)]
use crate::BLOCKSIZE;
use arrayvec::ArrayString;
use core::fmt::{Debug, Formatter};
#[derive(Copy, Clone)]
#[repr(transparent)]
pub struct Size(StaticCString<12>);
impl Size {
pub fn val(&self) -> usize {
usize::from_str_radix(self.0.as_string().as_str(), 8).unwrap()
}
}
impl Debug for Size {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let mut debug = f.debug_tuple("Size");
debug.field(&self.val());
debug.finish()
}
}
#[derive(Copy, Clone)]
#[repr(transparent)]
pub struct Mode(StaticCString<8>);
impl Mode {
pub fn to_flags(self) -> ModeFlags {
let octal_number_str = self.0.as_string();
let bits = u64::from_str_radix(octal_number_str.as_str(), 8).unwrap();
ModeFlags::from_bits(bits).unwrap()
}
}
impl Debug for Mode {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let mut debug = f.debug_tuple("Mode");
debug.field(&self.to_flags());
debug.finish()
}
}
#[derive(Copy, Clone)]
#[repr(transparent)]
pub struct StaticCString<const N: usize>([u8; N]);
#[allow(unused)]
impl<const N: usize> StaticCString<N> {
const fn new(bytes: [u8; N]) -> Self {
Self(bytes)
}
pub fn len(&self) -> usize {
self.as_string().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn as_string(&self) -> ArrayString<N> {
let mut string = ArrayString::new();
self.0
.clone()
.iter()
.filter(|x| **x != 0)
.for_each(|b| string.push(*b as char));
string
}
}
impl<const N: usize> Debug for StaticCString<N> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let mut debug = f.debug_tuple("Name");
let str = self.as_string();
if str.is_empty() {
debug.field(&"<empty>");
} else {
debug.field(&str);
}
debug.finish()
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
pub struct PosixHeader {
pub name: StaticCString<100>,
pub mode: Mode,
pub uid: [u8; 8],
pub gid: [u8; 8],
pub size: Size,
pub mtime: [u8; 12],
pub cksum: [u8; 8],
pub typeflag: TypeFlag,
pub linkname: StaticCString<100>,
pub magic: StaticCString<6>,
pub version: StaticCString<2>,
pub uname: StaticCString<32>,
pub gname: StaticCString<32>,
pub dev_major: [u8; 8],
pub dev_minor: [u8; 8],
pub prefix: StaticCString<155>,
pub _pad: [u8; 12],
}
impl PosixHeader {
pub fn payload_block_count(&self) -> usize {
let div = self.size.val() / BLOCKSIZE;
let modulo = self.size.val() % BLOCKSIZE;
if modulo > 0 {
(div + 1) as usize
} else {
div as usize
}
}
pub fn is_zero_block(&self) -> bool {
let ptr = self as *const Self as *const u8;
let self_bytes = unsafe { core::slice::from_raw_parts(ptr, BLOCKSIZE) };
self_bytes.iter().filter(|x| **x == 0).count() == BLOCKSIZE
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
#[allow(unused)]
pub enum TypeFlag {
REGTYPE = b'0',
AREGTYPE = b'\0',
LINK = 1,
SYMTYPE = 2,
CHRTYPE = 3,
BLKTYPE = 4,
DIRTYPE = 5,
FIFOTYPE = 6,
CONTTYPE = 7,
XHDTYPE = b'x',
XGLTYPE = b'g',
}
bitflags::bitflags! {
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;
}
}
#[cfg(test)]
mod tests {
use crate::header::{PosixHeader, StaticCString, TypeFlag};
use crate::BLOCKSIZE;
use std::mem::size_of;
fn bytes_to_archive(bytes: &[u8]) -> &PosixHeader {
unsafe { (bytes.as_ptr() as *const PosixHeader).as_ref() }.unwrap()
}
#[test]
fn test_display_header() {
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_default.tar"));
println!("{:#?}'", archive);
}
#[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,
TypeFlag::REGTYPE,
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_string().as_str(), "bye_world_513b.txt");
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_gnu.tar"));
assert_eq!(
archive.typeflag,
TypeFlag::REGTYPE,
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_string().as_str(), "bye_world_513b.txt");
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_oldgnu.tar"));
assert_eq!(
archive.typeflag,
TypeFlag::REGTYPE,
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_string().as_str(), "bye_world_513b.txt");
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_ustar.tar"));
assert_eq!(
archive.typeflag,
TypeFlag::REGTYPE,
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_string().as_str(), "bye_world_513b.txt");
let archive = bytes_to_archive(include_bytes!("../tests/gnu_tar_v7.tar"));
assert_eq!(
archive.typeflag,
TypeFlag::AREGTYPE,
"the first entry is a regular file!"
);
assert_eq!(archive.name.as_string().as_str(), "bye_world_513b.txt");
}
#[test]
fn test_size() {
assert_eq!(BLOCKSIZE, size_of::<PosixHeader>());
}
#[test]
fn test_static_str() {
let str = StaticCString::new(*b"0000633\0");
assert_eq!(str.len(), 7);
assert_eq!(str.as_string().as_str(), "0000633");
}
}