cpio_archive/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5pub mod newc;
6pub use newc::{NewcHeader, NewcReader};
7pub mod odc;
8pub use odc::{OdcBuilder, OdcHeader, OdcReader};
9
10use {
11    chrono::{DateTime, Utc},
12    std::{
13        fmt::Debug,
14        io::{Chain, Cursor, Read},
15        path::PathBuf,
16    },
17};
18
19#[derive(Debug, thiserror::Error)]
20pub enum Error {
21    #[error("I/O error: {0}")]
22    Io(#[from] std::io::Error),
23
24    #[error("bad magic value encountered")]
25    BadMagic,
26
27    #[error("value in header is not an ASCII string")]
28    BadHeaderString,
29
30    #[error("string value in header is not in hex: {0}")]
31    BadHeaderHex(String),
32
33    #[error("filename could not be decoded")]
34    FilenameDecode,
35
36    #[error("Numeric value too large to be encoded")]
37    ValueTooLarge,
38
39    #[error("Size mismatch between header length and provided data")]
40    SizeMismatch,
41
42    #[error("path is not a file: {0}")]
43    NotAFile(PathBuf),
44}
45
46/// Result type for this crate.
47pub type CpioResult<T> = Result<T, Error>;
48
49/// Common behavior for a header/entry in a cpio archive.
50pub trait CpioHeader: Debug {
51    /// Device number.
52    fn device(&self) -> u32;
53
54    /// Inode number.
55    fn inode(&self) -> u32;
56
57    /// File mode.
58    fn mode(&self) -> u32;
59
60    /// User ID.
61    fn uid(&self) -> u32;
62
63    /// Group ID.
64    fn gid(&self) -> u32;
65
66    /// Number of links.
67    fn nlink(&self) -> u32;
68
69    /// Associated device number.
70    fn rdev(&self) -> u32;
71
72    /// Modified time as seconds since UNIX epoch.
73    fn mtime(&self) -> u32;
74
75    /// Modified time as a [DateTime].
76    fn modified_time(&self) -> DateTime<Utc> {
77        DateTime::from_timestamp(self.mtime() as _, 0)
78            .expect("out of range timestamp")
79            .to_utc()
80    }
81
82    /// File size in bytes.
83    fn file_size(&self) -> u64;
84
85    /// File name.
86    fn name(&self) -> &str;
87}
88
89/// Common interface for cpio archive reading.
90///
91/// In addition to the members of this trait, instances implement [Iterator] over
92/// the members of the archive and [Read] to obtain a reader for the current
93/// archive member.
94///
95/// Instances behave like a cursor over members of the archive. The cursor is
96/// advanced by calling [Self::read_next]. When the cursor is advanced, the
97/// [Read] trait will read data for this and only this archive member. The reader
98/// will hit EOF at the end of the current archive member.
99pub trait CpioReader<T>: Iterator<Item = CpioResult<Box<dyn CpioHeader>>> + Read
100where
101    T: Read + Sized,
102{
103    /// Construct a new instance from a reader.
104    fn new(reader: T) -> Self
105    where
106        Self: Sized;
107
108    /// Read the next header from the archive.
109    ///
110    /// `Some` on another file entry. `None` if at end of file.
111    ///
112    /// The special `TRAILER!!!` entry is not emitted.
113    fn read_next(&mut self) -> CpioResult<Option<Box<dyn CpioHeader>>>;
114
115    /// Finish reading the current member.
116    ///
117    /// This will advance the reader to the next archive member if the
118    /// current member hasn't been fully consumed.
119    fn finish(&mut self) -> CpioResult<()>;
120}
121
122pub type ChainedCpioReader<T> = dyn CpioReader<Chain<Cursor<Vec<u8>>, T>>;
123
124/// Construct a new cpio archive reader.
125///
126/// This will sniff the type of the cpio archive and return an appropriate
127/// instance.
128pub fn reader<T: 'static + Read + Sized>(mut reader: T) -> CpioResult<Box<ChainedCpioReader<T>>> {
129    let mut magic = vec![0u8; 6];
130    reader.read_exact(&mut magic)?;
131
132    match magic.as_ref() {
133        crate::newc::MAGIC => Ok(Box::new(NewcReader::new(Cursor::new(magic).chain(reader)))),
134        crate::odc::MAGIC => Ok(Box::new(OdcReader::new(Cursor::new(magic).chain(reader)))),
135        _ => Err(Error::BadMagic),
136    }
137}