use super::*;
use std::collections::HashMap;
use std::fs;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
struct IncomingContent {
path: PathBuf,
kind: Kind,
file_id: u32,
itempos: u64,
contentpos: u64,
size: u64,
}
#[derive(Clone, Debug, Default)]
pub struct Options {
file_size: bool,
metadata: bool,
}
impl Options {
pub fn new() -> Self {
Default::default()
}
pub fn file_size(mut self, value: bool) -> Self {
self.file_size = value;
self
}
pub fn metadata(mut self, value: bool) -> Self {
self.metadata = value;
self
}
}
pub struct Writer<W: Write + Seek> {
output: W,
options: Options,
prev_dir_id: u32,
directories: Vec<Entry>,
prev_file_id: u32,
files: HashMap<u32, Entry>,
current_pos: u64,
contents: Vec<IncomingContent>,
buffer: Option<Vec<u8>>,
manifest: Option<Vec<u8>>,
encryption: Encryption,
secret_key: Option<Vec<u8>>,
bytes_written: u64,
}
impl<W: Write + Seek> Writer<W> {
pub fn new(output: W) -> Result<Self, Error> {
Writer::with_options(output, Default::default())
}
pub fn with_options(mut output: W, options: Options) -> Result<Self, Error> {
write_archive_header(&mut output, None)?;
Ok(Self {
output,
options,
prev_dir_id: 0,
directories: vec![],
prev_file_id: 0,
files: HashMap::new(),
current_pos: 0,
contents: vec![],
buffer: None,
manifest: None,
encryption: Encryption::None,
secret_key: None,
bytes_written: 0,
})
}
pub fn enable_encryption(
&mut self,
kd: KeyDerivation,
ea: Encryption,
password: &str,
) -> Result<(), Error> {
if self.prev_dir_id > 0 || self.prev_file_id > 0 {
return Err(Error::InternalError("pack must be empty".into()));
}
self.encryption = ea;
let salt = generate_salt(&kd)?;
let params: KeyDerivationParams = Default::default();
self.secret_key = Some(derive_key(&kd, password, &salt, ¶ms)?);
self.output.seek(SeekFrom::Start(0))?;
let mut header = HeaderBuilder::new();
header.add_u8(TAG_ENC_ALGO, ea.into())?;
header.add_u8(TAG_KEY_DERIV, kd.into())?;
header.add_bytes(TAG_SALT, &salt)?;
write_archive_header(&mut self.output, Some(header))?;
Ok(())
}
pub fn add_dir_all<P: AsRef<Path>>(&mut self, basepath: P) -> Result<u64, Error> {
let mut file_count: u64 = 0;
let mut subdirs: Vec<(u32, PathBuf)> = Vec::new();
subdirs.push((0, basepath.as_ref().to_path_buf()));
while let Some((parent, currdir)) = subdirs.pop() {
let dir_id = self.add_directory(&currdir, parent)?;
let readdir = fs::read_dir(currdir)?;
for entry_result in readdir {
let entry = entry_result?;
let path = entry.path();
let metadata = entry.metadata()?;
if metadata.is_dir() {
subdirs.push((dir_id, path));
} else if metadata.is_file() {
self.add_file(&path, Some(dir_id))?;
file_count += 1;
} else if metadata.is_symlink() {
self.add_symlink(&path, Some(dir_id))?;
}
}
}
Ok(file_count)
}
pub fn bytes_written(&self) -> u64 {
self.bytes_written
}
pub fn finish(&mut self) -> Result<(), Error> {
if !self.contents.is_empty() {
self.process_contents()?;
}
Ok(())
}
fn process_contents(&mut self) -> Result<(), Error> {
self.insert_content()?;
self.contents.clear();
self.directories.clear();
self.current_pos = 0;
Ok(())
}
pub fn add_directory<P: AsRef<Path>>(&mut self, path: P, parent: u32) -> Result<u32, Error> {
self.prev_dir_id += 1;
let mut dir_entry = Entry::new(path);
dir_entry.dir_id = Some(self.prev_dir_id);
if parent > 0 {
dir_entry.parent = Some(parent);
}
self.directories.push(dir_entry);
Ok(self.prev_dir_id)
}
pub fn add_file<P: AsRef<Path>>(&mut self, path: P, parent: Option<u32>) -> Result<(), Error> {
self.prev_file_id += 1;
let mut file_entry = Entry::new(path.as_ref());
file_entry.parent = parent;
let md = fs::metadata(path.as_ref());
let file_len = match md.as_ref() {
Ok(attr) => attr.len(),
Err(_) => 0,
};
file_entry.size = Some(file_len);
self.files.insert(self.prev_file_id, file_entry);
let mut itempos: u64 = 0;
let mut size: u64 = file_len;
loop {
if self.current_pos + size > BUNDLE_SIZE {
let remainder = BUNDLE_SIZE - self.current_pos;
let content = IncomingContent {
path: path.as_ref().to_path_buf(),
kind: Kind::File,
file_id: self.prev_file_id,
itempos,
contentpos: self.current_pos,
size: remainder,
};
self.contents.push(content);
self.process_contents()?;
size -= remainder;
itempos += remainder;
} else {
let content = IncomingContent {
path: path.as_ref().to_path_buf(),
kind: Kind::File,
file_id: self.prev_file_id,
itempos,
contentpos: self.current_pos,
size,
};
self.contents.push(content);
self.current_pos += size;
break;
}
}
Ok(())
}
pub fn add_symlink<P: AsRef<Path>>(
&mut self,
path: P,
parent: Option<u32>,
) -> Result<(), Error> {
self.prev_file_id += 1;
let mut file_entry = Entry::new(path.as_ref());
file_entry.parent = parent;
let md = fs::symlink_metadata(path.as_ref());
let link_len = match md.as_ref() {
Ok(attr) => attr.len(),
Err(_) => 0,
};
file_entry.size = Some(link_len);
self.files.insert(self.prev_file_id, file_entry);
let content = IncomingContent {
path: path.as_ref().to_path_buf(),
kind: Kind::Link,
file_id: self.prev_file_id,
itempos: 0,
contentpos: self.current_pos,
size: link_len,
};
self.contents.push(content);
self.current_pos += link_len;
Ok(())
}
pub fn add_file_slice<P: AsRef<Path>, S: Into<String>>(
&mut self,
path: P,
name: S,
parent: Option<u32>,
offset: u64,
length: u32,
) -> Result<(), Error> {
self.prev_file_id += 1;
let mut file_entry = Entry::with_name(name);
file_entry.parent = parent;
file_entry.size = Some(length as u64);
self.files.insert(self.prev_file_id, file_entry);
let mut itempos: u64 = offset;
let mut size: u64 = length as u64;
loop {
if self.current_pos + size > BUNDLE_SIZE {
let remainder = BUNDLE_SIZE - self.current_pos;
let content = IncomingContent {
path: path.as_ref().to_path_buf(),
kind: Kind::Slice(offset),
file_id: self.prev_file_id,
itempos,
contentpos: self.current_pos,
size: remainder,
};
self.contents.push(content);
self.process_contents()?;
size -= remainder;
itempos += remainder;
} else {
let content = IncomingContent {
path: path.as_ref().to_path_buf(),
kind: Kind::Slice(offset),
file_id: self.prev_file_id,
itempos,
contentpos: self.current_pos,
size,
};
self.contents.push(content);
self.current_pos += size;
break;
}
}
Ok(())
}
fn insert_content(&mut self) -> Result<(), Error> {
let mut content: Vec<u8> = if let Some(mut buf) = self.buffer.take() {
buf.clear();
buf
} else {
Vec::with_capacity(BUNDLE_SIZE as usize)
};
let mut encoder = zstd::stream::write::Encoder::new(content, 0)?;
for item in self.contents.iter() {
match item.kind {
Kind::Link => {
let value = read_link(&item.path)?;
encoder.write_all(&value)?;
}
_ => {
let mut input = fs::File::open(&item.path)?;
input.seek(SeekFrom::Start(item.itempos))?;
let mut chunk = input.take(item.size);
io::copy(&mut chunk, &mut encoder)?;
}
}
}
content = encoder.finish()?;
let mut output: Vec<u8> = if let Some(mut buf) = self.manifest.take() {
buf.clear();
buf
} else {
Vec::with_capacity(content.len() / 2 * 3)
};
let num_entries = (self.directories.len() + self.contents.len()) as u32;
let header = make_manifest_header(num_entries, content.len())?;
header.write_header(&mut output)?;
for dir_entry in self.directories.iter() {
let mut header = HeaderBuilder::new();
add_directory_rows(dir_entry, &mut header)?;
if self.options.metadata {
add_metadata_rows(dir_entry, &mut header)?;
}
header.write_header(&mut output)?;
}
for content in self.contents.iter() {
let file_entry = self
.files
.get(&content.file_id)
.expect("internal error, missing file entry for item content");
let mut header = HeaderBuilder::new();
add_file_rows(file_entry, &mut header)?;
if content.itempos == 0 {
if self.options.file_size {
add_size_row(file_entry, &mut header)?;
}
if self.options.metadata {
add_metadata_rows(file_entry, &mut header)?;
}
} else if content.kind.is_slice() {
add_size_row(file_entry, &mut header)?;
}
add_content_rows(content, &mut header)?;
header.write_header(&mut output)?;
}
output.write_all(&content)?;
if let Some(ref secret) = self.secret_key {
let (cipher, nonce) = encrypt_data(&self.encryption, secret, output.as_slice())?;
let mut header = HeaderBuilder::new();
header.add_bytes(TAG_INIT_VECTOR, &nonce)?;
header.add_u32(TAG_ENCRYPTED_SIZE, cipher.len() as u32)?;
header.write_header(&mut self.output)?;
output = cipher;
}
let mut cursor = std::io::Cursor::new(&output);
io::copy(&mut cursor, &mut self.output)?;
self.bytes_written += output.len() as u64;
self.buffer = Some(content);
self.manifest = Some(output);
Ok(())
}
}
fn write_archive_header<W: Write>(mut output: W, rows: Option<HeaderBuilder>) -> Result<(), Error> {
let version = [b'E', b'X', b'A', b'F', 1, 1];
output.write_all(&version)?;
if let Some(header) = rows {
header.write_header(output)?;
} else {
output.write_all(&[0, 0])?;
}
Ok(())
}
struct HeaderBuilder {
buffer: Vec<u8>,
row_count: u16,
}
impl HeaderBuilder {
fn new() -> Self {
Self {
buffer: vec![],
row_count: 0,
}
}
fn add_u8(&mut self, tag: u16, value: u8) -> Result<(), Error> {
let tag_bytes = u16::to_be_bytes(tag);
self.buffer.write_all(&tag_bytes)?;
self.buffer.write_all(&[0, 1, value])?;
self.row_count += 1;
Ok(())
}
fn add_u16(&mut self, tag: u16, value: u16) -> Result<(), Error> {
if value < 256 {
self.add_u8(tag, value as u8)
} else {
let tag_bytes = u16::to_be_bytes(tag);
self.buffer.write_all(&tag_bytes)?;
self.buffer.write_all(&[0, 2])?;
let value_bytes = u16::to_be_bytes(value);
self.buffer.write_all(&value_bytes)?;
self.row_count += 1;
Ok(())
}
}
fn add_u32(&mut self, tag: u16, value: u32) -> Result<(), Error> {
if value < 65_536 {
self.add_u16(tag, value as u16)
} else {
let tag_bytes = u16::to_be_bytes(tag);
self.buffer.write_all(&tag_bytes)?;
self.buffer.write_all(&[0, 4])?;
let value_bytes = u32::to_be_bytes(value);
self.buffer.write_all(&value_bytes)?;
self.row_count += 1;
Ok(())
}
}
fn add_u64(&mut self, tag: u16, value: u64) -> Result<(), Error> {
if value < 4_294_967_296 {
self.add_u32(tag, value as u32)
} else {
let tag_bytes = u16::to_be_bytes(tag);
self.buffer.write_all(&tag_bytes)?;
self.buffer.write_all(&[0, 8])?;
let value_bytes = u64::to_be_bytes(value);
self.buffer.write_all(&value_bytes)?;
self.row_count += 1;
Ok(())
}
}
fn add_i32(&mut self, tag: u16, value: i32) -> Result<(), Error> {
let tag_bytes = u16::to_be_bytes(tag);
self.buffer.write_all(&tag_bytes)?;
self.buffer.write_all(&[0, 4])?;
let value_bytes = i32::to_be_bytes(value);
self.buffer.write_all(&value_bytes)?;
self.row_count += 1;
Ok(())
}
fn add_i64(&mut self, tag: u16, value: i64) -> Result<(), Error> {
if (-2_147_483_648..=2_147_483_647).contains(&value) {
self.add_i32(tag, value as i32)
} else {
let tag_bytes = u16::to_be_bytes(tag);
self.buffer.write_all(&tag_bytes)?;
self.buffer.write_all(&[0, 8])?;
let value_bytes = i64::to_be_bytes(value);
self.buffer.write_all(&value_bytes)?;
self.row_count += 1;
Ok(())
}
}
fn add_str(&mut self, tag: u16, value: &str) -> Result<(), Error> {
if value.len() > 65535 {
return Err(Error::InternalError("add_str value too long".into()));
}
let tag_bytes = u16::to_be_bytes(tag);
self.buffer.write_all(&tag_bytes)?;
let value_bytes = value.as_bytes();
let value_len = u16::to_be_bytes(value_bytes.len() as u16);
self.buffer.write_all(&value_len)?;
self.buffer.write_all(value_bytes)?;
self.row_count += 1;
Ok(())
}
fn add_bytes(&mut self, tag: u16, value: &[u8]) -> Result<(), Error> {
if value.len() > 65535 {
return Err(Error::InternalError("add_bytes value too long".into()));
}
let tag_bytes = u16::to_be_bytes(tag);
self.buffer.write_all(&tag_bytes)?;
let value_len = u16::to_be_bytes(value.len() as u16);
self.buffer.write_all(&value_len)?;
self.buffer.write_all(value)?;
self.row_count += 1;
Ok(())
}
fn write_header<W: Write>(&self, mut output: W) -> Result<(), Error> {
let row_count = u16::to_be_bytes(self.row_count);
output.write_all(&row_count)?;
output.write_all(&self.buffer)?;
Ok(())
}
}
fn make_manifest_header(num_entries: u32, block_size: usize) -> Result<HeaderBuilder, Error> {
let mut header = HeaderBuilder::new();
header.add_u32(TAG_NUM_ENTRIES, num_entries)?;
header.add_u8(TAG_COMP_ALGO, Compression::ZStandard.into())?;
header.add_u32(TAG_BLOCK_SIZE, block_size as u32)?;
Ok(header)
}
fn add_metadata_rows(entry: &Entry, header: &mut HeaderBuilder) -> Result<(), Error> {
if let Some(mode) = entry.mode {
header.add_u32(TAG_UNIX_MODE, mode)?;
}
if let Some(attrs) = entry.attrs {
header.add_u32(TAG_FILE_ATTRS, attrs)?;
}
if let Some(mt) = entry.mtime {
header.add_i64(TAG_MODIFY_TIME, mt.timestamp())?;
}
if let Some(ct) = entry.ctime {
header.add_i64(TAG_CREATE_TIME, ct.timestamp())?;
}
if let Some(at) = entry.atime {
header.add_i64(TAG_ACCESS_TIME, at.timestamp())?;
}
if let Some(ref username) = entry.user {
header.add_str(TAG_USER_NAME, username)?;
}
if let Some(ref groupname) = entry.group {
header.add_str(TAG_GROUP_NAME, groupname)?;
}
if let Some(uid) = entry.uid {
header.add_u32(TAG_USER_ID, uid)?;
}
if let Some(gid) = entry.gid {
header.add_u32(TAG_GROUP_ID, gid)?;
}
Ok(())
}
fn add_directory_rows(entry: &Entry, header: &mut HeaderBuilder) -> Result<(), Error> {
if let Some(dir_id) = entry.dir_id {
header.add_u32(TAG_DIRECTORY_ID, dir_id)?;
} else {
return Err(Error::InternalError("dir_id was missing".into()));
}
header.add_str(TAG_NAME, &entry.name)?;
if let Some(parent) = entry.parent {
header.add_u32(TAG_PARENT, parent)?;
}
Ok(())
}
fn add_file_rows(entry: &Entry, header: &mut HeaderBuilder) -> Result<(), Error> {
if entry.is_link {
header.add_str(TAG_SYM_LINK, &entry.name)?;
} else {
header.add_str(TAG_NAME, &entry.name)?;
}
if let Some(parent) = entry.parent {
header.add_u32(TAG_PARENT, parent)?;
}
Ok(())
}
fn add_size_row(entry: &Entry, header: &mut HeaderBuilder) -> Result<(), Error> {
if let Some(size) = entry.size {
header.add_u64(TAG_FILE_SIZE, size)?;
}
Ok(())
}
fn add_content_rows(
item_content: &IncomingContent,
header: &mut HeaderBuilder,
) -> Result<(), Error> {
match item_content.kind {
Kind::Slice(offset) => {
header.add_u64(TAG_ITEM_POS, item_content.itempos - offset)?;
}
_ => {
header.add_u64(TAG_ITEM_POS, item_content.itempos)?;
}
}
header.add_u32(TAG_CONTENT_POS, item_content.contentpos as u32)?;
header.add_u32(TAG_ITEM_SIZE, item_content.size as u32)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_header_builder_empty() -> Result<(), Error> {
let builder = HeaderBuilder::new();
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 2);
assert_eq!(output[..], [0, 0]);
Ok(())
}
#[test]
fn test_header_builder_down_i64() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_i64(0x1234, 101)?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 10);
assert_eq!(output[..], [0, 1, 0x12, 0x34, 0, 4, 0, 0, 0, 101]);
Ok(())
}
#[test]
fn test_header_builder_i64_full() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_i64(0x1234, i64::MAX)?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 14);
assert_eq!(output[..], [0, 1, 0x12, 0x34, 0, 8, 127, 255, 255, 255, 255, 255, 255, 255]);
let mut builder = HeaderBuilder::new();
builder.add_i64(0x1234, i64::MIN)?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 14);
assert_eq!(output[..], [0, 1, 0x12, 0x34, 0, 8, 128, 0, 0, 0, 0, 0, 0, 0]);
Ok(())
}
#[test]
fn test_header_builder_down_u64() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_u64(0x1234, 101)?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 7);
assert_eq!(output[..], [0, 1, 0x12, 0x34, 0, 1, 101]);
Ok(())
}
#[test]
fn test_header_builder_down_i32() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_i32(0x1234, 101)?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 10);
assert_eq!(output[..], [0, 1, 0x12, 0x34, 0, 4, 0, 0, 0, 101]);
Ok(())
}
#[test]
fn test_header_builder_u8() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_u8(0x1234, 255)?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 7);
assert_eq!(output[..], [0, 1, 0x12, 0x34, 0, 1, 255]);
Ok(())
}
#[test]
fn test_header_builder_u16() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_u16(0x1234, 65_535)?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 8);
assert_eq!(output[..], [0, 1, 0x12, 0x34, 0, 2, 255, 255]);
Ok(())
}
#[test]
fn test_header_builder_u32() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_u32(0x1234, 4_294_967_295)?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 10);
assert_eq!(output[..], [0, 1, 0x12, 0x34, 0, 4, 255, 255, 255, 255]);
Ok(())
}
#[test]
fn test_header_builder_u64() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_u64(0x1234, 4_294_967_297)?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 14);
assert_eq!(output[..], [0, 1, 0x12, 0x34, 0, 8, 0, 0, 0, 1, 0, 0, 0, 1]);
Ok(())
}
#[test]
fn test_header_builder_str() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_str(0x1234, "foobar")?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 12);
assert_eq!(
output[..],
[0, 1, 0x12, 0x34, 0, 6, b'f', b'o', b'o', b'b', b'a', b'r']
);
Ok(())
}
#[test]
fn test_header_builder_bytes() -> Result<(), Error> {
let mut builder = HeaderBuilder::new();
builder.add_bytes(0x1234, "foobar".as_bytes())?;
let mut output: Vec<u8> = vec![];
builder.write_header(&mut output)?;
assert_eq!(output.len(), 12);
assert_eq!(
output[..],
[0, 1, 0x12, 0x34, 0, 6, b'f', b'o', b'o', b'b', b'a', b'r']
);
Ok(())
}
pub fn blake3_from_file(infile: &Path) -> io::Result<String> {
let mut file = fs::File::open(infile)?;
let mut hasher = blake3::Hasher::new();
io::copy(&mut file, &mut hasher)?;
let digest = hasher.finalize();
Ok(format!("{}", digest).to_lowercase())
}
#[test]
fn test_create_archive_big_content() -> Result<(), Error> {
let outdir = tempdir()?;
let archive = outdir.path().join("archive.exa");
let output = std::fs::File::create(&archive)?;
let mut builder = super::writer::Writer::new(output)?;
builder.add_file("test/fixtures/IMG_0385.JPG", None)?;
assert_eq!(builder.bytes_written(), 19541);
builder.finish()?;
let mut reader = super::reader::from_file(&archive)?;
reader.extract_all(outdir.path())?;
let actual = blake3_from_file(outdir.path().join("IMG_0385.JPG").as_path())?;
assert_eq!(
actual,
"eb32e7014330a92a93dd81e3214605816b6a96533fca9f302f5fb1ca453130cd"
);
Ok(())
}
#[test]
fn test_create_archive_file_slice() -> Result<(), Error> {
let outdir = tempdir()?;
let archive = outdir.path().join("archive.exa");
let output = std::fs::File::create(&archive)?;
let options = Options::new().file_size(true);
let mut builder = super::writer::Writer::with_options(output, options)?;
builder.add_file_slice(
"test/fixtures/IMG_0385.JPG",
"5ba33678260abc495b6c77003ddab5cc613b9ba7",
None,
4096,
8192,
)?;
assert_eq!(builder.bytes_written(), 6431);
builder.finish()?;
let reader = super::reader::Entries::new(&archive)?;
let entries: Vec<u64> = reader
.filter_map(|e| e.ok())
.map(|e| e.size().unwrap_or(0))
.collect();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0], 8192);
let mut reader = super::reader::from_file(&archive)?;
reader.extract_all(outdir.path())?;
let actual = blake3_from_file(
outdir
.path()
.join("5ba33678260abc495b6c77003ddab5cc613b9ba7")
.as_path(),
)?;
assert_eq!(
actual,
"2a3ac65bbc905b1419430b8df173e8e68beda5be541f6c1de5797b29428d2d24"
);
Ok(())
}
}