Code cleanup.

This commit is contained in:
gugz0r
2024-12-19 13:58:05 +01:00
parent e96eb6dac6
commit ff66c10a27
2 changed files with 14 additions and 244 deletions
+14 -165
View File
@@ -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,
}
-79
View File
@@ -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");
}
}