Code cleanup.
This commit is contained in:
+14
-165
@@ -1,16 +1,8 @@
|
||||
//! tar.h implementation for Redox, following POSIX.1-1990 specification
|
||||
//! and https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/tar.h.html
|
||||
|
||||
//!
|
||||
//! This module provides functionality for working with tar archives, including
|
||||
//! header creation, validation, and manipulation. It implements the POSIX.1-1990
|
||||
//! ustar format.
|
||||
|
||||
use core::slice;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Block size for tar archives (512 bytes).
|
||||
pub const BLOCKSIZE: usize = 512;
|
||||
|
||||
@@ -71,11 +63,11 @@ pub const XHDRTYPE: u8 = b'x'; // Extended header referring to the next file in
|
||||
pub const XGLTYPE: u8 = b'g'; // Global extended header
|
||||
|
||||
/// Reserved values for GNU tar extensions
|
||||
pub const GNUTYPE_DUMPDIR: u8 = b'D'; // Directory dump
|
||||
pub const GNUTYPE_MULTIVOL: u8 = b'M'; // Multi-volume file
|
||||
pub const GNUTYPE_LONGNAME: u8 = b'L'; // Long file name
|
||||
pub const GNUTYPE_LONGLINK: u8 = b'K'; // Long link name
|
||||
pub const GNUTYPE_SPARSE: u8 = b'S'; // Sparse file
|
||||
// pub const GNUTYPE_DUMPDIR: u8 = b'D'; // Directory dump
|
||||
// pub const GNUTYPE_MULTIVOL: u8 = b'M'; // Multi-volume file
|
||||
// pub const GNUTYPE_LONGNAME: u8 = b'L'; // Long file name
|
||||
// pub const GNUTYPE_LONGLINK: u8 = b'K'; // Long link name
|
||||
// pub const GNUTYPE_SPARSE: u8 = b'S'; // Sparse file
|
||||
|
||||
/// Represents a tar archive header following the POSIX ustar format.
|
||||
///
|
||||
@@ -105,39 +97,6 @@ pub struct TarHeader {
|
||||
pub padding: [u8; 12], // 500 - Padding to make 512 bytes
|
||||
}
|
||||
|
||||
pub struct TarHeaderBuilder {
|
||||
header: TarHeader,
|
||||
}
|
||||
|
||||
impl TarHeaderBuilder {
|
||||
/// Creates a new `TarHeaderBuilder` with default values.
|
||||
pub fn new() -> Self {
|
||||
let mut header = TarHeader::default();
|
||||
header.magic.copy_from_slice(TMAGIC.as_bytes());
|
||||
header.version.copy_from_slice(TVERSION.as_bytes());
|
||||
Self { header }
|
||||
}
|
||||
|
||||
/// Sets the name field in the tar header.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - A string slice that holds the name of the file.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `TarError::FieldTooLong` if the name exceeds the maximum allowed length.
|
||||
pub fn name(mut self, name: &str) -> Result<Self, TarError> {
|
||||
self.header.name = TarHeader::to_null_terminated_field::<NAME_SIZE>(name)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Builds and returns the `TarHeader`.
|
||||
pub fn build(self) -> TarHeader {
|
||||
self.header
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TarHeader {
|
||||
fn default() -> Self {
|
||||
let mut header = Self {
|
||||
@@ -148,10 +107,10 @@ impl Default for TarHeader {
|
||||
size: [0; SIZE_SIZE],
|
||||
mtime: [0; MTIME_SIZE],
|
||||
chksum: [0; CHKSUM_SIZE],
|
||||
typeflag: REGTYPE, // Default to regular file type
|
||||
typeflag: AREGTYPE,
|
||||
linkname: [0; LINKNAME_SIZE],
|
||||
magic: *b"ustar\0", // UStar magic string
|
||||
version: *b"00", // UStar version
|
||||
magic: [0; MAGIC_SIZE],
|
||||
version: [0; VERSION_SIZE],
|
||||
uname: [0; UNAME_SIZE],
|
||||
gname: [0; GNAME_SIZE],
|
||||
devmajor: [0; DEVMAJOR_SIZE],
|
||||
@@ -160,17 +119,14 @@ impl Default for TarHeader {
|
||||
padding: [0; 12],
|
||||
};
|
||||
|
||||
// Set default magic and version
|
||||
// Set default magic ("ustar") and version ("00")
|
||||
let magic_bytes = TMAGIC.as_bytes(); // "ustar"
|
||||
header.magic[..magic_bytes.len()].copy_from_slice(magic_bytes);
|
||||
|
||||
// POSIX ustar magic expects a trailing null
|
||||
// TMAGIC = "ustar" (5 chars) + '\0' = 6 chars total
|
||||
if TMAGLEN == 6 {
|
||||
header.magic[5] = 0;
|
||||
// tar specification often expects "ustar\0"
|
||||
if MAGIC_SIZE >= 6 && TMAGIC.len() < MAGIC_SIZE {
|
||||
header.magic[TMAGIC.len()] = 0;
|
||||
}
|
||||
|
||||
// Set the version field to "00"
|
||||
let version_bytes = TVERSION.as_bytes(); // "00"
|
||||
header.version[..version_bytes.len()].copy_from_slice(version_bytes);
|
||||
|
||||
@@ -179,120 +135,13 @@ impl Default for TarHeader {
|
||||
}
|
||||
|
||||
impl TarHeader {
|
||||
/// Converts a byte array field to a UTF-8 string, trimming null bytes.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Some(&str)` - If the field contains valid UTF-8 data
|
||||
/// * `None` - If the field contains invalid UTF-8 data
|
||||
pub fn to_str(field: &[u8]) -> Option<&str> {
|
||||
let end = field.iter().position(|&b| b == 0).unwrap_or(field.len());
|
||||
core::str::from_utf8(&field[..end]).ok()
|
||||
}
|
||||
|
||||
/// Converts a string to a null-terminated byte array suitable for a tar header field.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `s` - The string to convert
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok([u8; N])` - The null-terminated byte array
|
||||
/// * `Err(TarError)` - If the string is too long for the field
|
||||
pub fn to_null_terminated_field<const N: usize>(s: &str) -> Result<[u8; N], TarError> {
|
||||
let mut field = [0u8; N];
|
||||
let bytes = s.as_bytes();
|
||||
|
||||
// Reserve one byte for the null terminator
|
||||
if bytes.len() + 1 > N {
|
||||
return Err(TarError::FieldTooLong);
|
||||
}
|
||||
|
||||
field[..bytes.len()].copy_from_slice(bytes);
|
||||
field[bytes.len()] = 0; // Null terminator
|
||||
|
||||
Ok(field)
|
||||
}
|
||||
|
||||
/// Calculates the checksum of the header according to the POSIX specification.
|
||||
///
|
||||
/// The checksum is calculated by summing all bytes in the header, with the
|
||||
/// checksum field itself treated as eight spaces (ASCII 32).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The calculated checksum value
|
||||
/// Calculates the checksum of the tar header as required by the specification.
|
||||
/// Before computing, the checksum field is treated as if it contained all spaces (0x20).
|
||||
pub fn calculate_checksum(&self) -> usize {
|
||||
let mut header_copy = *self;
|
||||
// Replace chksum field with spaces
|
||||
header_copy.chksum.fill(b' ');
|
||||
|
||||
let bytes =
|
||||
unsafe { slice::from_raw_parts(&header_copy as *const _ as *const u8, HEADER_SIZE) };
|
||||
|
||||
bytes.iter().map(|&b| b as usize).sum()
|
||||
}
|
||||
|
||||
/// Validates the header's stored checksum against a calculated checksum.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(())` - If the checksum is valid
|
||||
/// * `Err(TarError)` - If the checksum is invalid or malformed
|
||||
pub fn validate(&self) -> Result<(), TarError> {
|
||||
let recorded_str = match TarHeader::to_str(&self.chksum) {
|
||||
Some(cs) => cs.trim_end(),
|
||||
None => return Err(TarError::InvalidChecksum),
|
||||
};
|
||||
|
||||
let recorded_checksum =
|
||||
usize::from_str_radix(recorded_str, 8).map_err(|_| TarError::InvalidChecksum)?;
|
||||
|
||||
let actual_checksum = self.calculate_checksum();
|
||||
if actual_checksum != recorded_checksum {
|
||||
return Err(TarError::InvalidChecksum);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a new TarHeaderBuilder for constructing a TarHeader.
|
||||
pub fn builder() -> TarHeaderBuilder {
|
||||
TarHeaderBuilder::new()
|
||||
}
|
||||
|
||||
/// Returns a human-readable description of the file type.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A string slice describing the type of file represented by this header.
|
||||
pub fn file_type(&self) -> &'static str {
|
||||
match self.typeflag {
|
||||
REGTYPE | AREGTYPE => "Regular File",
|
||||
DIRTYPE => "Directory",
|
||||
SYMTYPE => "Symbolic Link",
|
||||
LNKTYPE => "Hard Link",
|
||||
CHRTYPE => "Character Device",
|
||||
BLKTYPE => "Block Device",
|
||||
FIFOTYPE => "FIFO",
|
||||
CONTTYPE => "Contiguous File",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TarError {
|
||||
/// The checksum in the tar header is invalid.
|
||||
InvalidChecksum,
|
||||
/// An I/O error occurred.
|
||||
IoError,
|
||||
/// The tar header is invalid.
|
||||
InvalidHeader,
|
||||
/// The type flag in the tar header is unknown.
|
||||
UnknownTypeFlag(u8),
|
||||
/// An error occurred during UTF-8 conversion.
|
||||
InvalidEncoding,
|
||||
/// The input string exceeds the maximum allowed field size.
|
||||
FieldTooLong,
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::header::tar::{TarError, TarHeader, DIRTYPE, NAME_SIZE, SYMTYPE};
|
||||
|
||||
#[test]
|
||||
fn test_header_checksum() {
|
||||
let mut header = TarHeader::default();
|
||||
|
||||
// Set fields to valid octal strings for mode, size, mtime
|
||||
// mode: 0000644 (octal), commonly used in tar archives
|
||||
header.mode = *b"0000644\0";
|
||||
|
||||
// size: 12 chars total, octal "000000000123" fits exactly
|
||||
// (no null terminator needed if fully used)
|
||||
header.size = *b"000000000123";
|
||||
|
||||
// mtime: 12 chars total, octal time "00000001234\0"
|
||||
// Counting chars: '0'(1)'0'(2)'0'(3)'0'(4)'0'(5)'0'(6)'0'(7)'1'(8)'2'(9)'3'(10)'4'(11)'\0'(12)
|
||||
header.mtime = *b"00000001234\0";
|
||||
|
||||
// Calculate the checksum
|
||||
let checksum = header.calculate_checksum();
|
||||
|
||||
// According to tar specification, the checksum field should contains
|
||||
// an octal number followed by a null and a space if possible.
|
||||
// For an 8-byte field: 6 octal digits, a null, and a space :
|
||||
//
|
||||
// Example: "000000\0 " (sum in octal)
|
||||
let checksum_str = format!("{:06o}\0 ", checksum);
|
||||
header.chksum.copy_from_slice(checksum_str.as_bytes());
|
||||
|
||||
assert!(header.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_builder() {
|
||||
let header = TarHeader::builder().name("test/file.txt").unwrap().build();
|
||||
|
||||
// The builder sets a null terminator after "test/file.txt"
|
||||
let expected = {
|
||||
let mut arr = [0u8; NAME_SIZE];
|
||||
arr[.."test/file.txt".len()].copy_from_slice(b"test/file.txt");
|
||||
arr["test/file.txt".len()] = 0;
|
||||
arr
|
||||
};
|
||||
|
||||
assert_eq!(header.name, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_field_too_long() {
|
||||
// NAME_SIZE = 100, so a string longer than 99 chars fails
|
||||
let long_string = "a".repeat(200);
|
||||
let result = TarHeader::to_null_terminated_field::<NAME_SIZE>(&long_string);
|
||||
|
||||
assert!(matches!(result, Err(TarError::FieldTooLong)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_checksum() {
|
||||
let mut header = TarHeader::default();
|
||||
// Set chksum to something invalid
|
||||
header.chksum = *b"99999999"; // Not a valid octal, likely fails
|
||||
assert!(header.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_type() {
|
||||
let mut header = TarHeader::default();
|
||||
assert_eq!(header.file_type(), "Regular File");
|
||||
|
||||
header.typeflag = DIRTYPE;
|
||||
assert_eq!(header.file_type(), "Directory");
|
||||
|
||||
header.typeflag = SYMTYPE;
|
||||
assert_eq!(header.file_type(), "Symbolic Link");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user