Commit 84937445 authored by SamwiseFilmore's avatar SamwiseFilmore
Browse files

Implement upgrade; Refactoring; Tests in Rust!

A naive basic upgrade implementation is done now. It iterates a lot and
is currently requesting the entries from the new package once more than
it needs to (Transaction::install is used directly after and calls
read_entries() again). However, it does appear to work, as...

I wrote some tests in rust! The API now has a test case running on it,
so I'll know if I break API or some such. I designed it to be
thread-safe, so parallel testing is possible (unique temp files for each
thread, cleaned up automatically on drop).

This commit also refactors the basic functions in the crate to use
AsRef<Path> instead of &str, and reorganizes the imports in the crate to
make everything look nicer in the rustdoc.
parent e78fded8
......@@ -24,6 +24,10 @@ version = "0.3.6"
default-features = false
features = ["rayon"]
[dev-dependencies]
copy_dir = "0.1.2"
tempfile = "3.1.0"
[features]
default = ["clap", "std"]
std = []
......
use std::ffi::OsStr;
use std::fs;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::os::unix::ffi::OsStrExt;
......@@ -103,10 +102,14 @@ fn folder_entries<P, Q>(base: P, path: Q, entries: &mut Vec<Entry>) -> io::Resul
Ok(())
}
pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(), Error> {
pub fn create(
secret_path: impl AsRef<Path>,
archive_path: impl AsRef<Path>,
folder: impl AsRef<Path>,
) -> Result<(), Error> {
let secret_key = pkgar_keys::get_skey(&secret_path.as_ref())?
.key()
.expect(&format!("{} was encrypted?", secret_path));
.expect(&format!("{} was encrypted?", secret_path.as_ref().display()));
//TODO: move functions to library
......@@ -114,19 +117,19 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
.write(true)
.create(true)
.truncate(true)
.open(archive_path)
.open(&archive_path)
.map_err(|e| Error::Io {
reason: "Write archive".to_string(),
file: PathBuf::from(archive_path),
file: archive_path.as_ref().to_path_buf(),
source: e,
})?;
// Create a list of entries
let mut entries = Vec::new();
folder_entries(folder, folder, &mut entries)
folder_entries(&folder, &folder, &mut entries)
.map_err(|e| Error::Io {
reason: "Recursing buildroot".to_string(),
file: PathBuf::from(folder),
file: folder.as_ref().to_path_buf(),
source: e,
})?;
......@@ -152,7 +155,7 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
archive_file.seek(SeekFrom::Start(data_offset as u64))
.map_err(|e| Error::Io {
reason: format!("Seek to {} (data offset)", data_offset),
file: PathBuf::from(archive_path),
file: archive_path.as_ref().to_path_buf(),
source: e,
})?;
//TODO: fallocate data_offset + data_size
......@@ -161,8 +164,8 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
let mut header_hasher = blake3::Hasher::new();
let mut buf = vec![0; 4 * 1024 * 1024];
for entry in &mut entries {
let relative = Path::new(OsStr::from_bytes(entry.path()));
let path = Path::new(folder).join(relative);
let relative = entry.check_path()?;
let path = folder.as_ref().join(relative);
let mode_kind = entry.mode & MODE_KIND;
let (total, hash) = match mode_kind {
......@@ -172,7 +175,7 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
.open(&path)
.map_err(|e| Error::Io {
reason: "Read source file".to_string(),
file: PathBuf::from(&path),
file: path.clone(),
source: e,
})?;
copy_and_hash(&mut entry_file, &mut archive_file, &mut buf)?
......@@ -181,7 +184,7 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
let destination = fs::read_link(&path)
.map_err(|e| Error::Io {
reason: "Read source link".to_string(),
file: PathBuf::from(&path),
file: path.clone(),
source: e,
})?;
let mut data = destination.as_os_str().as_bytes();
......@@ -189,14 +192,14 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
},
_ => {
return Err(Error::UnsupportedMode {
entry: PathBuf::from(relative),
entry: relative.to_path_buf(),
mode: entry.mode(),
});
}
};
if total != entry.size() {
return Err(Error::LengthMismatch {
entry: PathBuf::from(relative),
entry: relative.to_path_buf(),
actual: total,
expected: entry.size(),
});
......@@ -217,7 +220,7 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
archive_file.seek(SeekFrom::Start(0))
.map_err(|e| Error::Io {
reason: "Seek to start".to_string(),
file: PathBuf::from(archive_path),
file: archive_path.as_ref().to_path_buf(),
source: e,
})?;
archive_file.write_all(unsafe {
......@@ -225,7 +228,7 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
})
.map_err(|e| Error::Io {
reason: "Write header".to_string(),
file: PathBuf::from(archive_path),
file: archive_path.as_ref().to_path_buf(),
source: e,
})?;
......@@ -237,7 +240,7 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
})
.map_err(|e| Error::Io {
reason: format!("Write entry {}", checked_path.display()),
file: PathBuf::from(archive_path),
file: archive_path.as_ref().to_path_buf(),
source: e,
})?;
}
......@@ -245,7 +248,11 @@ pub fn create(secret_path: &str, archive_path: &str, folder: &str) -> Result<(),
Ok(())
}
pub fn extract(pkey_path: &str, archive_path: &str, base_dir: &str) -> Result<(), Error> {
pub fn extract(
pkey_path: impl AsRef<Path>,
archive_path: impl AsRef<Path>,
base_dir: impl AsRef<Path>,
) -> Result<(), Error> {
let pkey = PublicKeyFile::open(&pkey_path.as_ref())?.pkey;
let mut package = PackageFile::new(archive_path, &pkey)?;
......@@ -257,7 +264,11 @@ pub fn extract(pkey_path: &str, archive_path: &str, base_dir: &str) -> Result<()
Ok(())
}
pub fn remove(pkey_path: &str, archive_path: &str, base_dir: &str) -> Result<(), Error> {
pub fn remove(
pkey_path: impl AsRef<Path>,
archive_path: impl AsRef<Path>,
base_dir: impl AsRef<Path>,
) -> Result<(), Error> {
let pkey = PublicKeyFile::open(&pkey_path.as_ref())?.pkey;
let mut package = PackageFile::new(archive_path, &pkey)?;
......@@ -269,7 +280,10 @@ pub fn remove(pkey_path: &str, archive_path: &str, base_dir: &str) -> Result<(),
Ok(())
}
pub fn list(pkey_path: &str, archive_path: &str) -> Result<(), Error> {
pub fn list(
pkey_path: impl AsRef<Path>,
archive_path: impl AsRef<Path>,
) -> Result<(), Error> {
let pkey = PublicKeyFile::open(&pkey_path.as_ref())?.pkey;
let mut package = PackageFile::new(archive_path, &pkey)?;
......
pub mod bin;
mod bin;
pub mod ext;
pub mod package;
pub mod transaction;
mod package;
mod transaction;
pub use bin::*;
pub use package::*;
pub use transaction::*;
use std::io;
use std::path::PathBuf;
......
use std::process;
use clap::{App, AppSettings, Arg, crate_authors, crate_description, crate_name, crate_version, SubCommand};
use pkgar::bin::{
use pkgar::{
create,
extract,
remove,
......
......@@ -150,12 +150,29 @@ impl Transaction {
Ok(())
}
pub fn upgrade<Pkg, Pth>(&mut self, old: &mut Pkg, new: Pkg, base_dir: Pth) -> Result<(), Error>
pub fn upgrade<Pkg, Pth>(&mut self, old: &mut Pkg, new: &mut Pkg, base_dir: Pth) -> Result<(), Error>
where
Pkg: PackageSrc<Err = Error>,
Pth: AsRef<Path>,
{
unimplemented!();
let old_entries = old.read_entries()?;
let new_entries = new.read_entries()?;
// All the files that are present in old but not in new
let mut removes = old_entries.iter()
.filter(|old_e| new_entries.iter()
.find(|new_e| new_e.blake3() == old_e.blake3() )
.is_none())
.map(|e| {
let target_path = base_dir.as_ref()
.join(e.check_path()?);
Ok(Action::Remove(target_path))
})
.collect::<Result<Vec<Action>, Error>>()?;
self.actions.append(&mut removes);
//TODO: Don't force a re-read of all the entries for the new package
self.install(new, base_dir)
}
pub fn remove<Pkg, Pth>(&mut self, pkg: &mut Pkg, base_dir: Pth) -> Result<(), Error>
......
use std::env;
use std::error::Error;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use pkgar::{PackageFile, Transaction};
use pkgar_keys::SecretKeyFile;
struct TestDir {
tmpdir: tempfile::TempDir,
}
impl TestDir {
fn new() -> io::Result<TestDir> {
Ok(TestDir {
tmpdir: tempfile::tempdir()?,
})
}
fn dir(&self, path: impl AsRef<Path>) -> PathBuf {
self.tmpdir.path().join(path)
}
fn file(&self, path: impl AsRef<Path>) -> PathBuf {
self.tmpdir.path().join(path)
}
}
const MANIFEST_DIR: &'static str = env!("CARGO_MANIFEST_DIR");
#[test]
fn build_install_update_remove() -> Result<(), Box<dyn Error>> {
let tmp = TestDir::new()?;
fs::create_dir(tmp.dir("keys"))?;
let (pkey_file, skey_file) = SecretKeyFile::new();
pkey_file.save(&tmp.file("keys/public.toml"))?;
skey_file.save(&tmp.file("keys/private.toml"))?;
let pkgar_src = PathBuf::from(MANIFEST_DIR)
.join("src");
println!("Copying {:?} to buildroot", pkgar_src);
copy_dir::copy_dir(pkgar_src, tmp.dir("buildroot"))?;
println!("Create archive");
pkgar::create(
tmp.file("keys/private.toml"),
tmp.file("pkgar-src-1.pkgar"),
tmp.dir("buildroot"),
)?;
println!("Read pkgar-src-1.pkgar");
let mut src_pkg = PackageFile::new(tmp.file("pkgar-src-1.pkgar"), &pkey_file.pkey)?;
println!("Install archive");
let mut install = Transaction::new();
install.install(&mut src_pkg, tmp.dir("installroot"))?;
install.commit()?;
println!("Modify build");
fs::remove_file(tmp.file("buildroot/main.rs"))?;
pkgar::create(
tmp.file("keys/private.toml"),
tmp.file("pkgar-src-2.pkgar"),
tmp.file("buildroot"),
)?;
println!("Read pkgar-src-2.pkgar");
let mut src2_pkg = PackageFile::new(tmp.file("pkgar-src-2.pkgar"), &pkey_file.pkey)?;
println!("Upgrade archive");
let mut update = Transaction::new();
update.upgrade(&mut src_pkg, &mut src2_pkg, tmp.dir("installroot"))?;
update.commit()?;
println!("Uninstall archive");
let mut remove = Transaction::new();
remove.remove(&mut src2_pkg, tmp.dir("installroot"))?;
remove.commit()?;
assert_eq!(fs::read_dir(tmp.dir("installroot"))?.count(), 0);
Ok(())
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment