use crate::header::TarHeader;
use std::fs;
use std::fs::File;
use std::io::{self, Write};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
pub struct Builder<W: Write> {
writer: W,
finished: bool,
}
impl<W: Write> Builder<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
finished: false,
}
}
pub fn append_path<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
let path = path.as_ref();
let path_bytes = path.as_os_str().as_bytes();
let clean_bytes = if !path_bytes.is_empty() && path_bytes[0] == b'/' {
&path_bytes[1..]
} else {
path_bytes
};
self.append_path_as_bytes(path, clean_bytes)
}
pub fn append_dir_all<P: AsRef<Path>>(
&mut self,
prefix_in_tar: &str,
path: P,
) -> io::Result<()> {
let path = path.as_ref();
let prefix_bytes = prefix_in_tar.as_bytes();
for entry in fs::read_dir(path)? {
let entry = entry?;
let entry_path = entry.path();
let file_name = entry.file_name();
let file_name_bytes = file_name.as_bytes();
let mut internal_name =
Vec::with_capacity(prefix_bytes.len() + 1 + file_name_bytes.len());
internal_name.extend_from_slice(prefix_bytes);
internal_name.push(b'/');
internal_name.extend_from_slice(file_name_bytes);
if entry_path.is_dir() {
let next_prefix = String::from_utf8_lossy(&internal_name);
self.append_dir_all(&next_prefix, &entry_path)?;
} else {
self.append_path_as_bytes(&entry_path, &internal_name)?;
}
}
Ok(())
}
pub fn append_path_as<P: AsRef<Path>>(
&mut self,
source: P,
name_in_tar: &str,
) -> io::Result<()> {
self.append_path_as_bytes(source.as_ref(), name_in_tar.as_bytes())
}
fn append_path_as_bytes(&mut self, source: &Path, name_in_tar: &[u8]) -> io::Result<()> {
let mut file = File::open(source)?;
let metadata = file.metadata()?;
let mut header = TarHeader::new();
let clean_name = if !name_in_tar.is_empty() && name_in_tar[0] == b'/' {
&name_in_tar[1..]
} else {
name_in_tar
};
let name_len = clean_name.len().min(100);
header.name[..name_len].copy_from_slice(&clean_name[..name_len]);
TarHeader::set_octal(&mut header.mode, metadata.mode() as u64);
TarHeader::set_octal(&mut header.uid, metadata.uid() as u64);
TarHeader::set_octal(&mut header.gid, metadata.gid() as u64);
TarHeader::set_octal(&mut header.size, metadata.len());
TarHeader::set_octal(&mut header.mtime, metadata.mtime() as u64);
header.typeflag = b'0';
let cksum = header.calculate_checksum();
TarHeader::set_octal(&mut header.checksum, cksum as u64);
let header_ptr = &header as *const _ as *const u8;
let header_slice = unsafe { std::slice::from_raw_parts(header_ptr, 512) };
self.writer.write_all(header_slice)?;
let n = io::copy(&mut file, &mut self.writer)?;
let remainder = n % 512;
if remainder > 0 {
let padding = [0u8; 512];
self.writer
.write_all(&padding[..(512 - remainder as usize)])?;
}
Ok(())
}
pub fn finish(&mut self) -> io::Result<()> {
if !self.finished {
self.writer.write_all(&[0u8; 1024])?;
self.writer.flush()?;
self.finished = true;
}
Ok(())
}
}