diff --git a/src/bin/pkgar.rs b/src/bin/pkgar.rs index 728d4e696c565810712a40b0780ff04b4f143b29..1f4f7b642e4f0e72b6ad6fee295d5a885322f713 100644 --- a/src/bin/pkgar.rs +++ b/src/bin/pkgar.rs @@ -7,9 +7,15 @@ use std::fs; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::mem; use std::os::unix::ffi::OsStrExt; -use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; +use std::os::unix::fs::{symlink, OpenOptionsExt, PermissionsExt}; use std::path::{Component, Path}; +// This ensures that all platforms use the same mode defines +const MODE_PERM: u32 = 0o7777; +const MODE_KIND: u32 = 0o170000; +const MODE_FILE: u32 = 0o100000; +const MODE_SYMLINK: u32 = 0o120000; + fn folder_entries<P, Q>(base: P, path: Q, entries: &mut Vec<Entry>) -> io::Result<()> where P: AsRef<Path>, Q: AsRef<Path> { @@ -46,11 +52,23 @@ fn folder_entries<P, Q>(base: P, path: Q, entries: &mut Vec<Entry>) -> io::Resul } path_bytes[..relative_bytes.len()].copy_from_slice(relative_bytes); + let file_type = metadata.file_type(); + let mut mode = metadata.permissions().mode() & MODE_PERM; + if file_type.is_file() { + mode |= MODE_FILE; + } else if file_type.is_symlink() { + mode |= MODE_SYMLINK; + } else { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Unsupported entry at {:?}: {:?}", relative, metadata), + )); + } entries.push(Entry { blake3: [0; 32], offset: 0, size: metadata.len(), - mode: metadata.permissions().mode(), + mode, path: path_bytes, }); } @@ -100,7 +118,7 @@ fn create(secret_path: &str, archive_path: &str, folder: &str) { for entry in &mut entries { entry.offset = data_size; data_size = data_size.checked_add(entry.size) - .expect("overflow when calculating entry offset"); + .expect("overflow when calculating data size"); println!("{}: {:?}", { entry.offset }, ::std::str::from_utf8(entry.path())); } @@ -118,22 +136,44 @@ fn create(secret_path: &str, archive_path: &str, folder: &str) { for entry in &mut entries { let relative = Path::new(OsStr::from_bytes(entry.path())); let path = Path::new(folder).join(relative); - let mut entry_file = fs::OpenOptions::new() - .read(true) - .open(path) - .expect("failed to open entry file"); let mut hasher = blake3::Hasher::new(); - loop { - let count = entry_file.read(&mut buf) - .expect("failed to read entry file"); - if count == 0 { - break; + let mode_kind = entry.mode & MODE_KIND; + match mode_kind { + MODE_FILE => { + let mut entry_file = fs::OpenOptions::new() + .read(true) + .open(path) + .expect("failed to open entry file"); + + let mut total = 0; + loop { + let count = entry_file.read(&mut buf) + .expect("failed to read entry file"); + if count == 0 { + break; + } + total += count as u64; + //TODO: Progress + archive_file.write_all(&buf[..count]) + .expect("failed to write entry data"); + hasher.update_with_join::<blake3::join::RayonJoin>(&buf[..count]); + } + assert_eq!(total, { entry.size }); + }, + MODE_SYMLINK => { + let destination = fs::read_link(path) + .expect("failed to read entry link"); + let data = destination.as_os_str().as_bytes(); + assert_eq!(data.len() as u64, { entry.size }); + + archive_file.write_all(&data) + .expect("failed to write entry data"); + hasher.update_with_join::<blake3::join::RayonJoin>(&data); + }, + _ => { + panic!("Unsupported mode {:#o}", { entry.mode }); } - //TODO: Progress - archive_file.write_all(&buf[..count]) - .expect("failed to write entry data"); - hasher.update_with_join::<blake3::join::RayonJoin>(&buf[..count]); } entry.blake3.copy_from_slice(hasher.finalize().as_bytes()); @@ -143,6 +183,8 @@ fn create(secret_path: &str, archive_path: &str, folder: &str) { } header.blake3.copy_from_slice(header_hasher.finalize().as_bytes()); + //TODO: ensure file size matches + // Calculate signature let unsigned = header.clone(); sodalite::sign_attached( @@ -246,15 +288,29 @@ fn extract(public_path: &str, archive_path: &str, folder: &str) { .expect("failed to create entry parent directory"); } - fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .mode(entry.mode) - .open(entry_path) - .expect("failed to create entry file") - .write_all(&data) - .expect("failed to write entry file"); + let mode_kind = entry.mode & MODE_KIND; + let mode_perm = entry.mode & MODE_PERM; + match mode_kind { + MODE_FILE => { + fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .mode(mode_perm) + .open(entry_path) + .expect("failed to create entry file") + .write_all(&data) + .expect("failed to write entry file"); + }, + MODE_SYMLINK => { + let os_str: &OsStr = OsStrExt::from_bytes(data.as_slice()); + symlink(os_str, entry_path) + .expect("failed to create entry link"); + }, + _ => { + panic!("Unsupported mode {:#o}", { entry.mode }); + } + } } }