pub mod file_tree;
#[cfg(feature = "std")]
pub mod from_fs;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use super::super::Write;
use super::header::{CpioMagic, HEADER_SIZE, RawNewcHeader, TRAILER_NAME};
use crate::error::{Error, Result};
use crate::mode::{self, FileType};
use file_tree::{FileNode, FileTree};
const PATH_MAX: usize = 4096;
#[derive(Debug, Clone, Copy, Default)]
pub struct CpioWriteOptions {
pub use_crc: bool,
}
impl CpioWriteOptions {
pub const fn crc(mut self, enabled: bool) -> Self {
self.use_crc = enabled;
self
}
}
struct ArchiveEncoder {
options: CpioWriteOptions,
}
pub struct CpioArchiveWriter<W> {
writer: W,
options: CpioWriteOptions,
}
io_transform! {
impl ArchiveEncoder {
fn new(options: CpioWriteOptions) -> Self {
Self { options }
}
async fn write<W: Write>(&self, writer: &mut W, tree: &FileTree) -> Result<()> {
let magic = if self.options.use_crc {
CpioMagic::NewcCrc
} else {
CpioMagic::Newc
};
let mut entries: Vec<(String, &FileNode)> = Vec::new();
for node in &tree.root {
flatten_tree(node, String::new(), &mut entries);
}
let mut next_ino: u32 = 1;
let mut path_to_ino: BTreeMap<String, u32> = BTreeMap::new();
let mut hard_links: BTreeMap<String, Vec<(String, u32)>> = BTreeMap::new();
let mut assigned_inos: Vec<u32> = Vec::with_capacity(entries.len());
for (path, node) in &entries {
match node {
FileNode::HardLink { link_target, .. } => {
let ino = *path_to_ino
.get(link_target.as_str())
.ok_or(Error::UnresolvedHardLink { ino: 0 })?;
assigned_inos.push(ino);
hard_links
.entry(link_target.clone())
.or_default()
.push((path.clone(), ino));
}
_ => {
let ino = next_ino;
next_ino += 1;
path_to_ino.insert(path.clone(), ino);
assigned_inos.push(ino);
}
}
}
for (i, (path, node)) in entries.iter().enumerate() {
let ino = assigned_inos[i];
self.write_entry(writer, magic, ino, path, node, &hard_links).await?;
}
self.write_trailer(writer, magic).await?;
Ok(())
}
async fn write_entry<W: Write>(
&self,
writer: &mut W,
magic: CpioMagic,
ino: u32,
path: &str,
node: &FileNode,
hard_links: &BTreeMap<String, Vec<(String, u32)>>,
) -> Result<()> {
let name_bytes = path.as_bytes();
if name_bytes.is_empty()
|| name_bytes.contains(&0)
|| name_bytes.len().saturating_add(1) > PATH_MAX
{
return Err(Error::FilenameTooLong);
}
let namesize =
u32::try_from(name_bytes.len() + 1).map_err(|_| Error::FilenameTooLong)?;
let (file_mode, uid, gid, mtime, filesize, data, nlink, devmajor, devminor, rdevmajor, rdevminor) =
match node {
FileNode::File {
contents,
permissions,
uid,
gid,
mtime,
..
} => {
let nlink = 1 + hard_links.get(path).map_or(0, |v| v.len() as u32);
let size =
u32::try_from(contents.len()).map_err(|_| Error::FileTooLarge)?;
(
mode::make_mode(FileType::Regular, *permissions),
*uid,
*gid,
*mtime,
size,
contents.as_slice(),
nlink,
0u32,
0u32,
0u32,
0u32,
)
}
FileNode::Directory {
permissions,
uid,
gid,
mtime,
..
} => (
mode::make_mode(FileType::Directory, *permissions),
*uid,
*gid,
*mtime,
0u32,
&[] as &[u8],
2u32,
0u32,
0u32,
0u32,
0u32,
),
FileNode::Symlink {
target,
permissions,
uid,
gid,
mtime,
..
} => {
let data = target.as_bytes();
if data.is_empty() {
return Err(Error::InvalidHeader {
reason: "symbolic-link c_filesize must not be zero",
});
}
(
mode::make_mode(FileType::Symlink, *permissions),
*uid,
*gid,
*mtime,
u32::try_from(data.len()).map_err(|_| Error::FileTooLarge)?,
data,
1u32,
0u32,
0u32,
0u32,
0u32,
)
}
FileNode::HardLink { link_target, .. } => {
(
mode::make_mode(FileType::Regular, 0o644),
0u32,
0u32,
0u32,
0u32,
&[] as &[u8],
1 + hard_links
.get(link_target)
.map_or(0, |links| links.len() as u32),
0u32,
0u32,
0u32,
0u32,
)
}
FileNode::DeviceNode {
device_type,
major,
minor,
permissions,
uid,
gid,
mtime,
..
} => (
mode::make_mode(*device_type, *permissions),
*uid,
*gid,
*mtime,
0u32,
&[] as &[u8],
1u32,
0u32,
0u32,
*major,
*minor,
),
FileNode::Fifo {
permissions,
uid,
gid,
mtime,
..
} => (
mode::make_mode(FileType::Fifo, *permissions),
*uid,
*gid,
*mtime,
0u32,
&[] as &[u8],
1u32,
0u32,
0u32,
0u32,
0u32,
),
};
let check = if magic == CpioMagic::NewcCrc && filesize > 0 {
compute_crc(data)
} else {
0
};
let raw = RawNewcHeader::build(
magic, ino, file_mode, uid, gid, nlink, mtime, filesize, devmajor, devminor,
rdevmajor, rdevminor, namesize, check,
);
raw.write(writer).await?;
writer.write_all(name_bytes).await?;
writer.write_all(&[0]).await?;
let header_plus_name = HEADER_SIZE as u64 + namesize as u64;
let pad = align4_padding(header_plus_name);
if pad > 0 {
writer.write_all(&[0u8; 3][..pad as usize]).await?;
}
if filesize > 0 {
writer.write_all(data).await?;
let data_pad = align4_padding(filesize as u64);
if data_pad > 0 {
writer.write_all(&[0u8; 3][..data_pad as usize]).await?;
}
}
Ok(())
}
async fn write_trailer<W: Write>(&self, writer: &mut W, magic: CpioMagic) -> Result<()> {
let namesize = (TRAILER_NAME.len() + 1) as u32;
let raw = RawNewcHeader::build(
magic, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, namesize, 0,
);
raw.write(writer).await?;
writer.write_all(TRAILER_NAME).await?;
writer.write_all(&[0]).await?;
let header_plus_name = HEADER_SIZE as u64 + namesize as u64;
let pad = align4_padding(header_plus_name);
if pad > 0 {
writer.write_all(&[0u8; 3][..pad as usize]).await?;
}
Ok(())
}
}
impl<W: Write> CpioArchiveWriter<W> {
pub fn new(writer: W, options: CpioWriteOptions) -> Self {
Self { writer, options }
}
pub fn into_inner(self) -> W {
self.writer
}
pub async fn finish(mut self, tree: &FileTree) -> Result<W> {
ArchiveEncoder::new(self.options)
.write(&mut self.writer, tree)
.await?;
Ok(self.writer)
}
pub async fn create(writer: W, tree: &FileTree, options: CpioWriteOptions) -> Result<W> {
Self::new(writer, options).finish(tree).await
}
}
}
fn flatten_tree<'a>(node: &'a FileNode, prefix: String, out: &mut Vec<(String, &'a FileNode)>) {
let name = node.node_name();
let path = if prefix.is_empty() {
String::from(name)
} else {
let mut p = prefix;
p.push('/');
p.push_str(name);
p
};
match node {
FileNode::Directory { children, .. } => {
let dir_path = path.clone();
out.push((dir_path.clone(), node));
for child in children {
flatten_tree(child, dir_path.clone(), out);
}
}
_ => {
out.push((path, node));
}
}
}
fn compute_crc(data: &[u8]) -> u32 {
let mut sum: u32 = 0;
for &b in data {
sum = sum.wrapping_add(b as u32);
}
sum
}
fn align4_padding(offset: u64) -> u64 {
(4 - (offset % 4)) % 4
}