use std::collections::HashMap;
use std::fs::read_link;
use std::fs::File;
use std::io::Error;
use std::io::ErrorKind;
use std::io::Read;
use std::io::Write;
use std::path::Path;
use crate::constants::*;
use crate::io::*;
use crate::CpioPath;
use crate::CrcWriter;
use crate::Format;
use crate::Metadata;
use crate::MetadataId;
use crate::Walk;
pub trait EditMetadata {
fn edit_metadata(&mut self, metadata: &mut Metadata) -> Result<(), Error>;
}
pub struct DoNotEditMetadata;
impl EditMetadata for DoNotEditMetadata {
fn edit_metadata(&mut self, _: &mut Metadata) -> Result<(), Error> {
Ok(())
}
}
pub struct Builder<W: Write, E: EditMetadata> {
writer: W,
max_inode: u32,
max_dev: u16,
format: Format,
inodes: HashMap<MetadataId, (u32, u32)>,
devices: HashMap<u64, u16>,
metadata_editor: E,
#[cfg(not(unix))]
inode_counter: u64,
}
impl<W: Write> Builder<W, DoNotEditMetadata> {
pub fn new(writer: W) -> Self {
Self::with_metadata_editor(writer, DoNotEditMetadata)
}
}
impl<W: Write, E: EditMetadata> Builder<W, E> {
pub fn with_metadata_editor(writer: W, metadata_editor: E) -> Self {
Self {
writer,
max_inode: 0,
max_dev: 0,
format: Format::Newc,
inodes: Default::default(),
devices: Default::default(),
metadata_editor,
#[cfg(not(unix))]
inode_counter: 0,
}
}
pub fn set_format(&mut self, format: Format) {
self.format = format;
}
pub fn format(&self) -> Format {
self.format
}
pub fn append_entry<R: Read>(
&mut self,
mut metadata: Metadata,
inner_path: impl TryInto<CpioPath>,
mut data: R,
) -> Result<Metadata, Error> {
let inner_path: CpioPath = inner_path
.try_into()
.map_err(|_| Error::other("Invalid path"))?;
let is_hard_link = self.fix_header(&mut metadata, &inner_path)?;
let is_crc = matches!(self.format, Format::Crc) && metadata.is_file() && !is_hard_link;
let file_contents = if is_crc {
let mut crc_writer = CrcWriter::new(Vec::new());
std::io::copy(&mut data, &mut crc_writer)?;
metadata.check = crc_writer.sum();
if let Some(entry) = self.inodes.get_mut(&metadata.id()) {
entry.1 = metadata.check;
}
crc_writer.into_inner()
} else {
Vec::new()
};
self.metadata_editor.edit_metadata(&mut metadata)?;
metadata.write(self.writer.by_ref(), self.format)?;
inner_path.write(self.writer.by_ref(), self.format)?;
if metadata.file_size != 0 {
let n = if is_crc {
self.writer.write_all(&file_contents)?;
file_contents.len() as u64
} else {
std::io::copy(&mut data, self.writer.by_ref())?
};
if n != metadata.file_size {
return Err(ErrorKind::InvalidData.into());
}
write_file_padding(self.writer.by_ref(), n, self.format)?;
}
Ok(metadata)
}
pub fn append_path<P: AsRef<Path>>(
&mut self,
path: P,
inner_path: impl TryInto<CpioPath>,
) -> Result<(Metadata, std::fs::Metadata), Error> {
let path = path.as_ref();
let fs_metadata = path.symlink_metadata()?;
let mut cpio_metadata: Metadata = (&fs_metadata).try_into()?;
#[cfg(not(unix))]
{
cpio_metadata.ino = self.inode_counter;
self.inode_counter += 1;
}
let cpio_metadata = if fs_metadata.is_symlink() {
let target = read_link(path)?;
let target = CpioPath::try_from(target)?;
cpio_metadata.file_size = target.as_bytes_with_nul().len() as u64;
self.append_entry(cpio_metadata, inner_path, target.as_bytes_with_nul())?
} else if fs_metadata.is_file() {
self.append_entry(cpio_metadata, inner_path, File::open(path)?)?
} else {
cpio_metadata.file_size = 0;
self.append_entry(cpio_metadata, inner_path, std::io::empty())?
};
Ok((cpio_metadata, fs_metadata))
}
pub fn append_dir_all<P: AsRef<Path>>(&mut self, directory: P) -> Result<(), Error> {
let directory = directory.as_ref();
for entry in directory.walk()? {
let entry = entry?;
let outer_path = entry.path();
let inner_path = outer_path.strip_prefix(directory).map_err(Error::other)?;
if inner_path == Path::new("") {
continue;
}
self.append_path(&outer_path, inner_path)?;
}
Ok(())
}
pub fn pack<P: AsRef<Path>>(writer: W, metadata_editor: E, directory: P) -> Result<W, Error> {
let mut builder = Self::with_metadata_editor(writer, metadata_editor);
builder.append_dir_all(directory)?;
builder.finish()
}
pub fn get_mut(&mut self) -> &mut W {
self.writer.by_ref()
}
pub fn get_ref(&self) -> &W {
&self.writer
}
pub fn finish(mut self) -> Result<W, Error> {
self.write_trailer()?;
Ok(self.writer)
}
fn write_trailer(&mut self) -> Result<(), Error> {
let len = TRAILER.to_bytes_with_nul().len();
let metadata = Metadata {
dev: 0,
ino: 0,
mode: 0,
uid: 0,
gid: 0,
nlink: 0,
rdev: 0,
mtime: 0,
name_len: len as u32,
file_size: 0,
check: 0,
};
metadata.write(self.writer.by_ref(), self.format)?;
write_path_c_str(self.writer.by_ref(), TRAILER, self.format)?;
Ok(())
}
fn fix_header(&mut self, metadata: &mut Metadata, name: &CpioPath) -> Result<bool, Error> {
self.remap_device_id(metadata);
let is_hard_link = self.remap_inode(metadata);
let name_len = name.as_bytes_with_nul().len();
let max = match self.format {
Format::Newc | Format::Crc => MAX_8,
Format::Odc => MAX_6,
Format::Bin(..) => u16::MAX as u32,
};
if name_len > max as usize {
return Err(ErrorKind::InvalidData.into());
}
metadata.name_len = name_len as u32;
Ok(is_hard_link)
}
fn remap_device_id(&mut self, metadata: &mut Metadata) {
use std::collections::hash_map::Entry::*;
match self.format {
Format::Odc | Format::Bin(..) => {
let dev = match self.devices.entry(metadata.dev) {
Vacant(v) => {
let dev = self.max_dev;
self.max_dev += 1;
v.insert(dev);
dev
}
Occupied(o) => *o.get(),
};
metadata.dev = dev as u64;
}
Format::Newc | Format::Crc => {
}
};
}
fn remap_inode(&mut self, metadata: &mut Metadata) -> bool {
use std::collections::hash_map::Entry::*;
let mut is_hard_link = false;
let inode = match self.inodes.entry(metadata.id()) {
Vacant(v) => {
let inode = self.max_inode;
self.max_inode += 1;
v.insert((inode, 0));
inode
}
Occupied(o) => {
let (inode, check) = *o.get();
if matches!(self.format, Format::Newc | Format::Crc) {
metadata.file_size = 0;
metadata.check = check;
is_hard_link = true;
}
inode
}
};
metadata.ino = inode as u64;
is_hard_link
}
}