Skip to main content

use_cpio/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! CPIO archive labels and entry kind metadata for `RustUse`.
5
6use core::fmt;
7
8/// Common CPIO file extension.
9pub const CPIO_EXTENSION: &str = "cpio";
10/// Common gzip-compressed CPIO extension.
11pub const CPIO_GZIP_EXTENSION: &str = "cpio.gz";
12/// Common xz-compressed CPIO extension.
13pub const CPIO_XZ_EXTENSION: &str = "cpio.xz";
14/// Common zstd-compressed CPIO extension.
15pub const CPIO_ZSTD_EXTENSION: &str = "cpio.zst";
16/// Common CPIO-related extensions.
17pub const CPIO_EXTENSIONS: &[&str] = &["cpio", "cpio.gz", "cpio.xz", "cpio.zst"];
18
19/// CPIO format variant labels.
20#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
21pub enum CpioFormat {
22    /// Binary CPIO format.
23    Binary,
24    /// Old ASCII CPIO format.
25    OldAscii,
26    /// New ASCII CPIO format.
27    NewAscii,
28    /// New ASCII CPIO format with CRC metadata.
29    CrcAscii,
30    /// Unknown or intentionally unspecified CPIO format.
31    #[default]
32    Unknown,
33}
34
35impl CpioFormat {
36    /// Returns a stable lowercase label.
37    #[must_use]
38    pub const fn as_str(self) -> &'static str {
39        match self {
40            Self::Binary => "binary",
41            Self::OldAscii => "old-ascii",
42            Self::NewAscii => "new-ascii",
43            Self::CrcAscii => "crc-ascii",
44            Self::Unknown => "unknown",
45        }
46    }
47}
48
49impl fmt::Display for CpioFormat {
50    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51        formatter.write_str(self.as_str())
52    }
53}
54
55/// CPIO entry kind labels.
56#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
57pub enum CpioEntryKind {
58    /// File entry.
59    File,
60    /// Directory entry.
61    Directory,
62    /// Symbolic link entry.
63    Symlink,
64    /// Device entry.
65    Device,
66    /// FIFO entry.
67    Fifo,
68    /// Socket entry.
69    Socket,
70    /// Unknown or unsupported CPIO entry kind.
71    #[default]
72    Unknown,
73}
74
75impl CpioEntryKind {
76    /// Returns a stable lowercase label.
77    #[must_use]
78    pub const fn as_str(self) -> &'static str {
79        match self {
80            Self::File => "file",
81            Self::Directory => "directory",
82            Self::Symlink => "symlink",
83            Self::Device => "device",
84            Self::Fifo => "fifo",
85            Self::Socket => "socket",
86            Self::Unknown => "unknown",
87        }
88    }
89}
90
91impl fmt::Display for CpioEntryKind {
92    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
93        formatter.write_str(self.as_str())
94    }
95}
96
97/// Returns whether `extension` is a known CPIO extension label.
98#[must_use]
99pub fn is_cpio_extension(extension: &str) -> bool {
100    matches!(
101        normalize_extension(extension).as_str(),
102        "cpio" | "cpio.gz" | "cpio.xz" | "cpio.zst" | "cpio.zstd"
103    )
104}
105
106/// Returns whether `name` has a known CPIO filename encoding.
107#[must_use]
108pub fn is_cpio_filename(name: &str) -> bool {
109    let parts = filename_parts(name);
110
111    match parts.as_slice() {
112        [.., last] if last == "cpio" => true,
113        [.., previous, last]
114            if previous == "cpio" && matches!(last.as_str(), "gz" | "xz" | "zst" | "zstd") =>
115        {
116            true
117        },
118        _ => false,
119    }
120}
121
122fn normalize_extension(extension: &str) -> String {
123    extension
124        .trim()
125        .trim_start_matches('.')
126        .to_ascii_lowercase()
127}
128
129fn filename_parts(name: &str) -> Vec<String> {
130    name.trim()
131        .to_ascii_lowercase()
132        .rsplit(['/', '\\'])
133        .next()
134        .unwrap_or_default()
135        .trim_start_matches('.')
136        .split('.')
137        .filter(|part| !part.is_empty())
138        .map(str::to_owned)
139        .collect()
140}
141
142#[cfg(test)]
143mod tests {
144    use super::{CPIO_EXTENSIONS, CpioEntryKind, CpioFormat, is_cpio_extension, is_cpio_filename};
145
146    #[test]
147    fn detects_cpio_extensions() {
148        assert!(is_cpio_extension(".cpio"));
149        assert!(is_cpio_extension("cpio.gz"));
150        assert!(is_cpio_extension("cpio.zst"));
151        assert_eq!(CPIO_EXTENSIONS[0], "cpio");
152    }
153
154    #[test]
155    fn detects_cpio_filenames() {
156        assert!(is_cpio_filename("initramfs.cpio"));
157        assert!(is_cpio_filename("initramfs.CPIO.XZ"));
158        assert!(!is_cpio_filename("bundle.tar"));
159    }
160
161    #[test]
162    fn exposes_default_and_unknown_labels() {
163        assert_eq!(CpioFormat::default(), CpioFormat::Unknown);
164        assert_eq!(CpioFormat::CrcAscii.as_str(), "crc-ascii");
165        assert_eq!(CpioEntryKind::default(), CpioEntryKind::Unknown);
166        assert_eq!(CpioEntryKind::Socket.as_str(), "socket");
167    }
168}