mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-26 19:03:27 +00:00
Add the cpio-decoder crate
This commit is contained in:
24
src/Cargo.lock
generated
24
src/Cargo.lock
generated
@ -99,6 +99,10 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpio-decoder"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@ -120,6 +124,15 @@ dependencies = [
|
|||||||
"ahash",
|
"ahash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "intrusive-collections"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f4f90afb01281fdeffb0f8e082d230cbe4f888f837cc90759696b858db1a700"
|
||||||
|
dependencies = [
|
||||||
|
"memoffset",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@ -157,6 +170,7 @@ dependencies = [
|
|||||||
"bootloader",
|
"bootloader",
|
||||||
"buddy_system_allocator",
|
"buddy_system_allocator",
|
||||||
"font8x8",
|
"font8x8",
|
||||||
|
"intrusive-collections",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"linked_list_allocator",
|
"linked_list_allocator",
|
||||||
"pod",
|
"pod",
|
||||||
@ -196,6 +210,7 @@ dependencies = [
|
|||||||
"ascii",
|
"ascii",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"controlled",
|
"controlled",
|
||||||
|
"intrusive-collections",
|
||||||
"jinux-frame",
|
"jinux-frame",
|
||||||
"jinux-pci",
|
"jinux-pci",
|
||||||
"jinux-rights-proc",
|
"jinux-rights-proc",
|
||||||
@ -303,6 +318,15 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
|
@ -22,6 +22,7 @@ members = [
|
|||||||
"services/libs/typeflags",
|
"services/libs/typeflags",
|
||||||
"services/libs/typeflags-util",
|
"services/libs/typeflags-util",
|
||||||
"services/libs/jinux-util",
|
"services/libs/jinux-util",
|
||||||
|
"services/libs/cpio-decoder",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
|
8
src/services/libs/cpio-decoder/Cargo.toml
Normal file
8
src/services/libs/cpio-decoder/Cargo.toml
Normal file
@ -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]
|
12
src/services/libs/cpio-decoder/src/error.rs
Normal file
12
src/services/libs/cpio-decoder/src/error.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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,
|
||||||
|
}
|
355
src/services/libs/cpio-decoder/src/lib.rs
Normal file
355
src/services/libs/cpio-decoder/src/lib.rs
Normal file
@ -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<CpioEntry<'a>> {
|
||||||
|
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<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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<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(size: usize, align: usize) -> usize {
|
||||||
|
debug_assert!(align >= 2 && align.is_power_of_two());
|
||||||
|
(size + align - 1) & !(align - 1)
|
||||||
|
}
|
45
src/services/libs/cpio-decoder/src/test.rs
Normal file
45
src/services/libs/cpio-decoder/src/test.rs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user