From 289ec0d474abe92bc219e3478b71e2ef067bf96f Mon Sep 17 00:00:00 2001 From: LI Qing Date: Wed, 22 Feb 2023 16:08:47 +0800 Subject: [PATCH] Add the cpio-decoder crate --- src/Cargo.lock | 24 ++ src/Cargo.toml | 1 + src/services/libs/cpio-decoder/Cargo.toml | 8 + src/services/libs/cpio-decoder/src/error.rs | 12 + src/services/libs/cpio-decoder/src/lib.rs | 355 ++++++++++++++++++++ src/services/libs/cpio-decoder/src/test.rs | 45 +++ 6 files changed, 445 insertions(+) create mode 100644 src/services/libs/cpio-decoder/Cargo.toml create mode 100644 src/services/libs/cpio-decoder/src/error.rs create mode 100644 src/services/libs/cpio-decoder/src/lib.rs create mode 100644 src/services/libs/cpio-decoder/src/test.rs diff --git a/src/Cargo.lock b/src/Cargo.lock index 07b88b8e4..261636372 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -99,6 +99,10 @@ dependencies = [ "syn", ] +[[package]] +name = "cpio-decoder" +version = "0.1.0" + [[package]] name = "either" version = "1.8.0" @@ -120,6 +124,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "intrusive-collections" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4f90afb01281fdeffb0f8e082d230cbe4f888f837cc90759696b858db1a700" +dependencies = [ + "memoffset", +] + [[package]] name = "itertools" version = "0.10.5" @@ -157,6 +170,7 @@ dependencies = [ "bootloader", "buddy_system_allocator", "font8x8", + "intrusive-collections", "lazy_static", "linked_list_allocator", "pod", @@ -196,6 +210,7 @@ dependencies = [ "ascii", "bitflags", "controlled", + "intrusive-collections", "jinux-frame", "jinux-pci", "jinux-rights-proc", @@ -303,6 +318,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.17.0" diff --git a/src/Cargo.toml b/src/Cargo.toml index 5c1fad321..b285c0bc8 100644 --- a/src/Cargo.toml +++ b/src/Cargo.toml @@ -22,6 +22,7 @@ members = [ "services/libs/typeflags", "services/libs/typeflags-util", "services/libs/jinux-util", + "services/libs/cpio-decoder", ] exclude = [ diff --git a/src/services/libs/cpio-decoder/Cargo.toml b/src/services/libs/cpio-decoder/Cargo.toml new file mode 100644 index 000000000..bf5c1c80c --- /dev/null +++ b/src/services/libs/cpio-decoder/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cpio-decoder" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/src/services/libs/cpio-decoder/src/error.rs b/src/services/libs/cpio-decoder/src/error.rs new file mode 100644 index 000000000..193f3afc9 --- /dev/null +++ b/src/services/libs/cpio-decoder/src/error.rs @@ -0,0 +1,12 @@ +pub type Result = core::result::Result; + +/// Errors of CPIO decoder. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Error { + MagicError, + Utf8Error, + ParseIntError, + FileTypeError, + FileNameError, + BufferShortError, +} diff --git a/src/services/libs/cpio-decoder/src/lib.rs b/src/services/libs/cpio-decoder/src/lib.rs new file mode 100644 index 000000000..51b9dd233 --- /dev/null +++ b/src/services/libs/cpio-decoder/src/lib.rs @@ -0,0 +1,355 @@ +//! A safe Rust CPIO (the newc format) decoder. +//! +//! # Example +//! +//! ```rust,should_panic +//! use cpio_decoder::CpioDecoder; +//! +//! let decoder = CpioDecoder::new(&[]); +//! for entry in decoder.entries() { +//! println!("{:?}", entry); +//! } +//! ``` + +#![cfg_attr(not(test), no_std)] +#![forbid(unsafe_code)] + +use crate::error::{Error, Result}; + +pub mod error; + +#[cfg(test)] +mod test; + +/// A CPIO (the newc format) decoder. +/// +/// "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<'a> { + buffer: &'a [u8], +} + +impl<'a> CpioDecoder<'a> { + /// create a decoder to decode the CPIO. + pub fn new(buffer: &'a [u8]) -> Self { + Self { buffer } + } + + /// Return an iterator for all entries in the CPIO. + /// + /// It will panic if fails to decode some entries. + pub fn entries(&'a self) -> CpioEntryIter<'a> { + CpioEntryIter::new(self) + } +} + +/// A file entry iterator. +pub struct CpioEntryIter<'a> { + buffer: &'a [u8], + offset: usize, +} + +impl<'a> CpioEntryIter<'a> { + fn new(decoder: &'a CpioDecoder) -> Self { + Self { + buffer: decoder.buffer, + offset: 0, + } + } +} + +impl<'a> Iterator for CpioEntryIter<'a> { + type Item = CpioEntry<'a>; + + fn next(&mut self) -> Option> { + let entry = CpioEntry::new(&self.buffer[self.offset..]).unwrap(); + if entry.is_trailer() { + return None; + } + self.offset += entry.archive_offset(); + Some(entry) + } +} + +/// A file entry in the CPIO. +#[derive(Debug)] +pub struct CpioEntry<'a> { + metadata: FileMetadata, + name: &'a str, + data: &'a [u8], +} + +impl<'a> CpioEntry<'a> { + fn new(bytes: &'a [u8]) -> Result { + let (metadata, name, data_offset) = { + let header = Header::new(bytes)?; + let name = { + let bytes_remain = &bytes[HEADER_LEN..]; + let name_size = read_hex_bytes_to_u32(header.name_size)? as usize; + if bytes_remain.len() < name_size { + return Err(Error::BufferShortError); + } + let name = core::ffi::CStr::from_bytes_with_nul(&bytes_remain[..name_size]) + .map_err(|_| Error::FileNameError)?; + name.to_str().map_err(|_| Error::Utf8Error)? + }; + let metadata = if name == TRAILER_NAME { + Default::default() + } else { + FileMetadata::new(header)? + }; + + (metadata, name, align_up(HEADER_LEN + name.len() + 1, 4)) + }; + let data_size = metadata.size as usize; + let bytes_remain = &bytes[data_offset..]; + if bytes_remain.len() < data_size { + return Err(Error::BufferShortError); + } + let data = &bytes_remain[..data_size]; + Ok(Self { + metadata, + name, + data, + }) + } + + /// The metadata of the file. + pub fn metadata(&self) -> &FileMetadata { + &self.metadata + } + + /// The name of the file. + pub fn name(&self) -> &str { + self.name + } + + /// The data of the file. + pub fn data(&self) -> &[u8] { + self.data + } + + fn is_trailer(&self) -> bool { + self.name == TRAILER_NAME + } + + fn archive_offset(&self) -> usize { + align_up(HEADER_LEN + self.name.len() + 1, 4) + align_up(self.metadata.size as usize, 4) + } +} + +/// 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 { + const MODE_MASK: u32 = 0o7777; + let raw_mode = read_hex_bytes_to_u32(&header.mode)?; + let metadata = Self { + ino: read_hex_bytes_to_u32(&header.ino)?, + type_: FileType::from_u32(raw_mode)?, + 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)] +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 FileType { + pub fn from_u32(bits: u32) -> Result { + const TYPE_MASK: u32 = 0o170000; + let bits = bits & TYPE_MASK; + let type_ = if bits == Self::FiFo as u32 { + Self::FiFo + } else if bits == Self::Char as u32 { + Self::Char + } else if bits == Self::Dir as u32 { + Self::Dir + } else if bits == Self::Block as u32 { + Self::Block + } else if bits == Self::File as u32 { + Self::File + } else if bits == Self::Link as u32 { + Self::Link + } else if bits == Self::Socket as u32 { + Self::Socket + } else { + return Err(Error::FileTypeError); + }; + Ok(type_) + } +} + +impl Default for FileType { + fn default() -> Self { + Self::File + } +} + +const HEADER_LEN: usize = 110; +const MAGIC: &[u8] = b"070701"; +const TRAILER_NAME: &str = "TRAILER!!!"; + +#[rustfmt::skip] +struct Header<'a> { + // magic: &'a [u8], // [u8; 6] + ino: &'a [u8], // [u8; 8] + mode: &'a [u8], // [u8; 8] + uid: &'a [u8], // [u8; 8] + gid: &'a [u8], // [u8; 8] + nlink: &'a [u8], // [u8; 8] + mtime: &'a [u8], // [u8; 8] + file_size: &'a [u8], // [u8; 8] + dev_maj: &'a [u8], // [u8; 8] + dev_min: &'a [u8], // [u8; 8] + rdev_maj: &'a [u8], // [u8; 8] + rdev_min: &'a [u8], // [u8; 8] + name_size: &'a [u8], // [u8; 8] + // chksum: &'a [u8], // [u8; 8] +} + +impl<'a> Header<'a> { + pub fn new(bytes: &'a [u8]) -> Result { + if bytes.len() < HEADER_LEN { + return Err(Error::BufferShortError); + } + let magic = &bytes[..6]; + if magic != MAGIC { + return Err(Error::MagicError); + } + Ok(Self { + ino: &bytes[6..14], + mode: &bytes[14..22], + uid: &bytes[22..30], + gid: &bytes[30..38], + nlink: &bytes[38..46], + mtime: &bytes[46..54], + file_size: &bytes[54..62], + dev_maj: &bytes[62..70], + dev_min: &bytes[70..78], + rdev_maj: &bytes[78..86], + rdev_min: &bytes[86..94], + name_size: &bytes[94..102], + }) + } +} + +fn read_hex_bytes_to_u32(bytes: &[u8]) -> Result { + 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(size: usize, align: usize) -> usize { + debug_assert!(align >= 2 && align.is_power_of_two()); + (size + align - 1) & !(align - 1) +} diff --git a/src/services/libs/cpio-decoder/src/test.rs b/src/services/libs/cpio-decoder/src/test.rs new file mode 100644 index 000000000..a9cd3286e --- /dev/null +++ b/src/services/libs/cpio-decoder/src/test.rs @@ -0,0 +1,45 @@ +use super::{CpioDecoder, FileType}; + +#[test] +fn test_decoder() { + use std::process::{Command, Stdio}; + + // Prepare the cpio buffer + let buffer = { + let mut find_process = Command::new("find") + .arg(".") + .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 decoder = CpioDecoder::new(&buffer); + assert!(decoder.entries().count() > 3); + for (idx, entry) in decoder.entries().enumerate() { + if idx == 0 { + assert!(entry.name() == "."); + assert!(entry.metadata().file_type() == FileType::Dir); + assert!(entry.metadata().ino() > 0); + } + if idx == 1 { + assert!(entry.name() == "src"); + assert!(entry.metadata().file_type() == FileType::Dir); + assert!(entry.metadata().ino() > 0); + } + if idx == 2 { + assert!(entry.name() == "src/lib.rs"); + assert!(entry.metadata().file_type() == FileType::File); + assert!(entry.metadata().ino() > 0); + } + } +}