use crate::BasicTarError;
use std::{ iter, mem };
pub const BLOCK_LEN: usize = 512;
pub mod header {
use super::{ mem, BLOCK_LEN };
pub type Raw = [u8; BLOCK_LEN];
pub const fn raw() -> Raw {
[0; BLOCK_LEN]
}
#[repr(packed)]
#[derive(Copy, Clone)]
pub struct Header {
pub name: [u8; 100],
pub mode: [u8; 8],
pub uid: [u8; 8],
pub gid: [u8; 8],
pub size: [u8; 12],
pub mtime: [u8; 12],
pub checksum: [u8; 8],
pub typeflag: [u8; 1],
pub linkname: [u8; 100],
pub _extra: [u8; 243],
pub _pad: [u8; 12]
}
pub fn header() -> Header {
Header::from(raw())
}
impl From<Raw> for Header {
fn from(raw: Raw) -> Self {
assert_eq!(mem::size_of_val(&raw), mem::size_of::<Self>());
unsafe{ mem::transmute(raw) }
}
}
impl Into<Raw> for Header {
fn into(self) -> Raw {
assert_eq!(mem::size_of_val(&self), mem::size_of::<Raw>());
unsafe{ mem::transmute(self) }
}
}
}
pub struct TypeFlag;
impl TypeFlag {
pub const REGULAR: u8 = b'0';
pub const HARDLINK: u8 = b'1';
pub const SYMLINK: u8 = b'2';
pub const CHAR_DEV: u8 = b'3';
pub const BLOCK_DEV: u8 = b'4';
pub const DIRECTORY: u8 = b'5';
pub const FIFO_NODE: u8 = b'6';
#[doc(hidden)] pub const RESERVED: u8 = b'7';
pub const PAX_SINGLE: u8 = b'x';
pub const PAX_GLOBAL: u8 = b'g';
}
pub(in crate::header) struct Checksum;
impl Checksum {
pub fn write(tar: &mut header::Header) {
Self::compute(*tar).into_octal_field(&mut tar.checksum)
.expect("We should always be able to octal-encode an `u32` into 8 bytes");
}
pub fn verify(tar: &header::Header) -> Result<(), BasicTarError> {
match Self::compute(*tar) == u64::from_octal_field(&tar.checksum)? {
true => Ok(()),
false => Err(BasicTarError::InvalidData("Invalid header checksum"))
}
}
fn compute(tar: header::Header) -> u64 {
let raw: header::Raw = tar.into();
raw[..148].iter().chain([b' '; 8].iter()).chain(raw[156..].iter())
.fold(0, |sum, byte| sum + (*byte as u64))
}
}
pub(in crate::header) trait U64Ext: Sized {
fn from_octal_field(field: &[u8]) -> Result<Self, BasicTarError>;
fn into_octal_field(self, field: &mut[u8]) -> Result<(), BasicTarError>;
}
impl U64Ext for Option<u64> {
fn from_octal_field(field: &[u8]) -> Result<Self, BasicTarError> {
let string = Option::<String>::from_terminated_field(field)?;
let octal = match string.as_ref().map(|s| s.trim_end()) {
Some(octal) if octal.len() > 0 => octal,
_ => return Ok(None)
};
let num = u64::from_str_radix(&octal, 8)
.map_err(|_| BasicTarError::InvalidData("Invalid octal number"))?;
Ok(Some(num))
}
fn into_octal_field(self, field: &mut[u8]) -> Result<(), BasicTarError> {
let num = self.map(|num| format!("{:o}", num)).unwrap_or_default();
let available = field.len().checked_sub(1).unwrap_or(0);
let pad = available.checked_sub(num.len()).unwrap_or(0);
let num: String = iter::repeat('0').take(pad).chain(num.chars()).collect();
num.into_terminated_field(field)
}
}
impl U64Ext for u64 {
fn from_octal_field(field: &[u8]) -> Result<Self, BasicTarError> {
Option::from_octal_field(field)?
.ok_or(BasicTarError::InvalidData("Required field is empty"))
}
fn into_octal_field(self, field: &mut[u8]) -> Result<(), BasicTarError> {
Some(self).into_octal_field(field)
}
}
pub(in crate::header) trait StringExt: Sized {
fn from_field(field: &[u8]) -> Result<Self, BasicTarError>;
fn into_field(self, field: &mut[u8]) -> Result<(), BasicTarError>;
fn from_terminated_field(field: &[u8]) -> Result<Self, BasicTarError>;
fn into_terminated_field(self, field: &mut[u8]) -> Result<(), BasicTarError>;
}
impl StringExt for Option<String> {
fn from_field(field: &[u8]) -> Result<Self, BasicTarError> {
let nul = field.iter().position(|b| *b == 0x00).unwrap_or(field.len());
let data = field[..nul].to_vec();
match data.is_empty() {
true => Ok(None),
false => {
let string = String::from_utf8(data.to_vec())
.map_err(|_| BasicTarError::Unsupported("Header field is not UTF-8"))?;
Ok(Some(string))
}
}
}
fn into_field(self, field: &mut[u8]) -> Result<(), BasicTarError> {
if field.len() < self.as_ref().map(|string| string.len()).unwrap_or(0) {
Err(BasicTarError::ApiMisuse("`field` is too small to hold the value"))?
}
let string = self.as_ref().map(|string| string.as_str()).unwrap_or_default();
let nul_terminated = string.bytes().chain(iter::repeat(0));
field.iter_mut().zip(nul_terminated).for_each(|(field, byte)| *field = byte);
Ok(())
}
fn from_terminated_field(field: &[u8]) -> Result<Self, BasicTarError> {
let space = field.iter().position(|b| *b == b' ').unwrap_or(field.len());
Self::from_field(&field[..space])
}
fn into_terminated_field(self, field: &mut[u8]) -> Result<(), BasicTarError> {
let last = field.len().checked_sub(1)
.ok_or(BasicTarError::ApiMisuse("`field` is too small to hold the value"))?;
self.into_field(&mut field[..last])?;
field[last] = 0;
Ok(())
}
}
impl StringExt for String {
fn from_field(field: &[u8]) -> Result<Self, BasicTarError> {
Option::<String>::from_field(field)?
.ok_or(BasicTarError::InvalidData("Required field is empty"))
}
fn into_field(self, field: &mut[u8]) -> Result<(), BasicTarError> {
Some(self).into_field(field)
}
fn from_terminated_field(field: &[u8]) -> Result<Self, BasicTarError> {
Option::<String>::from_terminated_field(field)?
.ok_or(BasicTarError::InvalidData("Required field is empty"))
}
fn into_terminated_field(self, field: &mut[u8]) -> Result<(), BasicTarError> {
Some(self).into_terminated_field(field)
}
}