Refactor project structure

This commit is contained in:
Zhang Junyang
2024-02-27 16:40:16 +08:00
committed by Tate, Hongliang Tian
parent bd878dd1c9
commit e3c227ae06
474 changed files with 77 additions and 77 deletions

View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: MPL-2.0
pub type Result<T> = core::result::Result<T, self::Error>;
/// Errors of CPIO decoder.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Error {
MagicError,
Utf8Error,
ParseIntError,
FileTypeError,
FileNameError,
BufferShortError,
IoError,
}
impl From<core2::io::Error> for Error {
#[inline]
fn from(err: core2::io::Error) -> Self {
use core2::io::ErrorKind;
match err.kind() {
ErrorKind::UnexpectedEof => Self::BufferShortError,
_ => Self::IoError,
}
}
}

View File

@ -0,0 +1,378 @@
// SPDX-License-Identifier: MPL-2.0
//! A safe Rust CPIO (the newc format) decoder.
//!
//! # Example
//!
//! ```rust
//! use cpio_decoder::CpioDecoder;
//! use lending_iterator::LendingIterator;
//!
//! let short_buffer: Vec<u8> = Vec::new();
//! let mut decoder = CpioDecoder::new(short_buffer.as_slice());
//! if let Some(entry_result) = decoder.next() {
//! println!("The entry_result is: {:?}", entry_result);
//! }
//! ```
#![cfg_attr(not(test), no_std)]
#![forbid(unsafe_code)]
#![allow(dead_code)]
extern crate alloc;
use alloc::{
string::{String, ToString},
vec,
};
use core::cmp::min;
use core2::io::{Read, Write};
use int_to_c_enum::TryFromInt;
use lending_iterator::prelude::*;
use crate::error::{Error, Result};
pub mod error;
#[cfg(test)]
mod test;
/// A CPIO (the newc format) decoder to iterator over the results of CPIO entries.
///
/// "newc" is the new portable format and CRC format.
///
/// Each file has a 110 byte header, a variable length NULL-terminated filename,
/// and variable length file data.
/// A header for a filename "TRAILER!!!" indicates the end of the archive.
///
/// All the fields in the header are ISO 646 (approximately ASCII) strings
/// of hexadecimal numbers, left padded, not NULL terminated.
pub struct CpioDecoder<R> {
reader: R,
is_error: bool,
}
impl<R> CpioDecoder<R>
where
R: Read,
{
/// Create a decoder.
pub fn new(reader: R) -> Self {
Self {
reader,
is_error: false,
}
}
}
#[gat]
impl<R> LendingIterator for CpioDecoder<R>
where
R: Read,
{
type Item<'a> = Result<CpioEntry<'a, R>>;
/// Stops if reaches to the trailer entry or encounters an error.
fn next(&mut self) -> Option<Self::Item<'_>> {
// Stop to iterate entries if encounters an error.
if self.is_error {
return None;
}
let entry_result = CpioEntry::new(&mut self.reader);
match &entry_result {
Ok(entry) => {
// A correct CPIO buffer must end with a trailer.
if entry.is_trailer() {
return None;
}
}
Err(_) => {
self.is_error = true;
}
}
Some(entry_result)
}
}
/// A file entry in the CPIO.
#[derive(Debug)]
pub struct CpioEntry<'a, R> {
metadata: FileMetadata,
name: String,
reader: &'a mut R,
data_padding_len: usize,
}
impl<'a, R> CpioEntry<'a, R>
where
R: Read,
{
fn new(reader: &'a mut R) -> Result<Self> {
let (metadata, name, data_padding_len) = {
let header = Header::new(reader)?;
let name = {
let name_size = read_hex_bytes_to_u32(&header.name_size)? as usize;
let mut name_bytes = vec![0u8; name_size];
reader.read_exact(&mut name_bytes)?;
let name = core::ffi::CStr::from_bytes_with_nul(&name_bytes)
.map_err(|_| Error::FileNameError)?;
name.to_str().map_err(|_| Error::Utf8Error)?.to_string()
};
let metadata = if name == TRAILER_NAME {
Default::default()
} else {
FileMetadata::new(&header)?
};
let data_padding_len = {
let header_padding_len = align_up_pad(header.len() + name.len() + 1, 4);
if header_padding_len > 0 {
let mut pad_buf = vec![0u8; header_padding_len];
reader.read_exact(&mut pad_buf)?;
}
align_up_pad(metadata.size() as usize, 4)
};
(metadata, name, data_padding_len)
};
Ok(Self {
metadata,
name,
reader,
data_padding_len,
})
}
/// The metadata of the file.
pub fn metadata(&self) -> &FileMetadata {
&self.metadata
}
/// The name of the file.
pub fn name(&self) -> &str {
&self.name
}
/// Read all data to the writer.
pub fn read_all<W>(&mut self, mut writer: W) -> Result<()>
where
W: Write,
{
let data_len = self.metadata().size() as usize;
let mut send_len = 0;
let mut buffer = vec![0u8; 0x1000];
while send_len < data_len {
let len = min(buffer.len(), data_len - send_len);
self.reader.read_exact(&mut buffer[..len])?;
writer.write_all(&buffer[..len])?;
send_len += len;
}
if self.data_padding_len > 0 {
self.reader
.read_exact(&mut buffer[..self.data_padding_len])?;
}
Ok(())
}
pub fn is_trailer(&self) -> bool {
self.name == TRAILER_NAME
}
}
/// The metadata of the file.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FileMetadata {
ino: u32,
type_: FileType,
mode: u16,
uid: u32,
gid: u32,
nlink: u32,
mtime: u32,
size: u32,
dev_maj: u32,
dev_min: u32,
rdev_maj: u32,
rdev_min: u32,
}
impl FileMetadata {
fn new(header: &Header) -> Result<Self> {
const MODE_MASK: u32 = 0o7777;
const TYPE_MASK: u32 = 0o170000;
let raw_mode = read_hex_bytes_to_u32(&header.mode)?;
let metadata = Self {
ino: read_hex_bytes_to_u32(&header.ino)?,
type_: FileType::try_from(raw_mode & TYPE_MASK).map_err(|_| Error::FileTypeError)?,
mode: (raw_mode & MODE_MASK) as u16,
uid: read_hex_bytes_to_u32(&header.uid)?,
gid: read_hex_bytes_to_u32(&header.gid)?,
nlink: read_hex_bytes_to_u32(&header.nlink)?,
mtime: read_hex_bytes_to_u32(&header.mtime)?,
size: read_hex_bytes_to_u32(&header.file_size)?,
dev_maj: read_hex_bytes_to_u32(&header.dev_maj)?,
dev_min: read_hex_bytes_to_u32(&header.dev_min)?,
rdev_maj: read_hex_bytes_to_u32(&header.rdev_maj)?,
rdev_min: read_hex_bytes_to_u32(&header.rdev_min)?,
};
Ok(metadata)
}
/// The inode number.
pub fn ino(&self) -> u32 {
self.ino
}
/// The file type.
pub fn file_type(&self) -> FileType {
self.type_
}
/// The file permission mode, e.g., 0o0755.
pub fn permission_mode(&self) -> u16 {
self.mode
}
/// The user ID of the file owner.
pub fn uid(&self) -> u32 {
self.uid
}
/// The group ID of the file owner.
pub fn gid(&self) -> u32 {
self.gid
}
/// The number of hard links.
pub fn nlink(&self) -> u32 {
self.nlink
}
/// The last modification time.
pub fn mtime(&self) -> u32 {
self.mtime
}
/// The size of the file in bytes.
pub fn size(&self) -> u32 {
self.size
}
/// The device major ID on which the file resides.
pub fn dev_maj(&self) -> u32 {
self.dev_maj
}
/// The device minor ID on which the file resides.
pub fn dev_min(&self) -> u32 {
self.dev_min
}
/// The device major ID that the file represents. Only relevant for special file.
pub fn rdev_maj(&self) -> u32 {
self.rdev_maj
}
/// The device minor ID that the file represents. Only relevant for special file.
pub fn rdev_min(&self) -> u32 {
self.rdev_min
}
}
/// The type of the file.
#[repr(u32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromInt)]
pub enum FileType {
/// FIFO special file
FiFo = 0o010000,
/// Character device
Char = 0o020000,
/// Directory
Dir = 0o040000,
/// Block device
Block = 0o060000,
/// Regular file
File = 0o100000,
/// Symbolic link
Link = 0o120000,
/// Socket
Socket = 0o140000,
}
impl Default for FileType {
fn default() -> Self {
Self::File
}
}
const MAGIC: &[u8] = b"070701";
const TRAILER_NAME: &str = "TRAILER!!!";
struct Header {
magic: [u8; 6],
ino: [u8; 8],
mode: [u8; 8],
uid: [u8; 8],
gid: [u8; 8],
nlink: [u8; 8],
mtime: [u8; 8],
file_size: [u8; 8],
dev_maj: [u8; 8],
dev_min: [u8; 8],
rdev_maj: [u8; 8],
rdev_min: [u8; 8],
name_size: [u8; 8],
chksum: [u8; 8],
}
impl Header {
pub fn new<R>(reader: &mut R) -> Result<Self>
where
R: Read,
{
let mut buf = vec![0u8; core::mem::size_of::<Self>()];
reader.read_exact(&mut buf)?;
let header = Self {
magic: <[u8; 6]>::try_from(&buf[0..6]).unwrap(),
ino: <[u8; 8]>::try_from(&buf[6..14]).unwrap(),
mode: <[u8; 8]>::try_from(&buf[14..22]).unwrap(),
uid: <[u8; 8]>::try_from(&buf[22..30]).unwrap(),
gid: <[u8; 8]>::try_from(&buf[30..38]).unwrap(),
nlink: <[u8; 8]>::try_from(&buf[38..46]).unwrap(),
mtime: <[u8; 8]>::try_from(&buf[46..54]).unwrap(),
file_size: <[u8; 8]>::try_from(&buf[54..62]).unwrap(),
dev_maj: <[u8; 8]>::try_from(&buf[62..70]).unwrap(),
dev_min: <[u8; 8]>::try_from(&buf[70..78]).unwrap(),
rdev_maj: <[u8; 8]>::try_from(&buf[78..86]).unwrap(),
rdev_min: <[u8; 8]>::try_from(&buf[86..94]).unwrap(),
name_size: <[u8; 8]>::try_from(&buf[94..102]).unwrap(),
chksum: <[u8; 8]>::try_from(&buf[102..110]).unwrap(),
};
if header.magic != MAGIC {
return Err(Error::MagicError);
}
Ok(header)
}
fn len(&self) -> usize {
core::mem::size_of::<Self>()
}
}
fn read_hex_bytes_to_u32(bytes: &[u8]) -> Result<u32> {
debug_assert!(bytes.len() == 8);
let string = core::str::from_utf8(bytes).map_err(|_| Error::Utf8Error)?;
let num = u32::from_str_radix(string, 16).map_err(|_| Error::ParseIntError)?;
Ok(num)
}
fn align_up_pad(size: usize, align: usize) -> usize {
align_up(size, align) - size
}
fn align_up(size: usize, align: usize) -> usize {
debug_assert!(align >= 2 && align.is_power_of_two());
(size + align - 1) & !(align - 1)
}

View File

@ -0,0 +1,81 @@
// SPDX-License-Identifier: MPL-2.0
use lending_iterator::LendingIterator;
use super::{error::*, CpioDecoder, FileType};
#[test]
fn test_decoder() {
use std::process::{Command, Stdio};
let manifest_path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let manifest_path = std::path::Path::new(manifest_path.as_str());
// Prepare the cpio buffer
let buffer = {
let mut find_process = Command::new("find")
.arg(manifest_path.as_os_str())
.stdout(Stdio::piped())
.spawn()
.expect("find command is not started");
let ecode = find_process.wait().expect("failed to execute find");
assert!(ecode.success());
let find_stdout = find_process.stdout.take().unwrap();
let output = Command::new("cpio")
.stdin(find_stdout)
.args(["-o", "-H", "newc"])
.output()
.expect("failed to execute cpio");
assert!(output.status.success());
output.stdout
};
let mut decoder = CpioDecoder::new(buffer.as_slice());
// 1st entry must be the root entry
let entry = {
let entry_result = decoder.next().unwrap();
entry_result.unwrap()
};
assert_eq!(entry.name(), manifest_path.as_os_str());
assert!(entry.metadata().file_type() == FileType::Dir);
assert!(entry.metadata().ino() > 0);
// Other entries
while let Some(decode_result) = decoder.next() {
let mut entry = decode_result.unwrap();
assert!(entry.metadata().ino() > 0);
if entry.name() == manifest_path.join("src").as_os_str() {
assert!(entry.metadata().file_type() == FileType::Dir);
assert!(entry.metadata().ino() > 0);
} else if entry.name() == manifest_path.join("src").join("lib.rs").as_os_str()
|| entry.name() == manifest_path.join("src").join("test.rs").as_os_str()
|| entry.name() == manifest_path.join("src").join("error.rs").as_os_str()
|| entry.name() == manifest_path.join("Cargo.toml").as_os_str()
{
assert!(entry.metadata().file_type() == FileType::File);
assert!(entry.metadata().size() > 0);
let mut buffer: Vec<u8> = Vec::new();
assert!(entry.read_all(&mut buffer).is_ok());
} else {
panic!("unexpected entry: {:?}", entry.name());
}
}
}
#[test]
fn test_short_buffer() {
let short_buffer: Vec<u8> = Vec::new();
let mut decoder = CpioDecoder::new(short_buffer.as_slice());
let entry_result = decoder.next().unwrap();
assert!(entry_result.is_err());
assert!(entry_result.err() == Some(Error::BufferShortError));
}
#[test]
fn test_invalid_buffer() {
let buffer: &[u8] = b"invalidmagic.invalidmagic.invalidmagic.invalidmagic.invalidmagic.invalidmagic.invalidmagic.invalidmagic.invalidmagic.invalidmagic";
let mut decoder = CpioDecoder::new(buffer);
let entry_result = decoder.next().unwrap();
assert!(entry_result.is_err());
assert!(entry_result.err() == Some(Error::MagicError));
}