Commit 802dc824 authored by SamwiseFilmore's avatar SamwiseFilmore
Browse files

Refactoring Transaction; Docs

Trying to leverage the type system a bit more for Entry and Header
attributes and refactor transaction code to make it more readable and
concise. More coming here.
parent 53990857
//! The packed structs represent the on-disk format of pkgar //! The packed structs represent the on-disk format of pkgar
use blake3::Hash;
use plain::Plain; use plain::Plain;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
...@@ -17,8 +18,8 @@ pub struct Entry { ...@@ -17,8 +18,8 @@ pub struct Entry {
} }
impl Entry { impl Entry {
pub fn blake3(&self) -> [u8; 32] { pub fn blake3(&self) -> Hash {
self.blake3 Hash::from(self.blake3)
} }
pub fn offset(&self) -> u64 { pub fn offset(&self) -> u64 {
...@@ -34,7 +35,7 @@ impl Entry { ...@@ -34,7 +35,7 @@ impl Entry {
} }
/// Retrieve the path, ending at the first NUL /// Retrieve the path, ending at the first NUL
pub fn path(&self) -> &[u8] { pub fn path_bytes(&self) -> &[u8] {
let mut i = 0; let mut i = 0;
while i < self.path.len() { while i < self.path.len() {
if self.path[i] == 0 { if self.path[i] == 0 {
......
...@@ -217,8 +217,8 @@ pub fn extract( ...@@ -217,8 +217,8 @@ pub fn extract(
let mut package = PackageFile::new(archive_path, &pkey)?; let mut package = PackageFile::new(archive_path, &pkey)?;
let mut transaction = Transaction::new(); let mut transaction = Transaction::new(base_dir);
transaction.install(&mut package, base_dir)?; transaction.install(&mut package)?;
transaction.commit()?; transaction.commit()?;
Ok(()) Ok(())
...@@ -233,8 +233,8 @@ pub fn remove( ...@@ -233,8 +233,8 @@ pub fn remove(
let mut package = PackageFile::new(archive_path, &pkey)?; let mut package = PackageFile::new(archive_path, &pkey)?;
let mut transaction = Transaction::new(); let mut transaction = Transaction::new(base_dir);
transaction.remove(&mut package, base_dir)?; transaction.remove(&mut package)?;
transaction.commit()?; transaction.commit()?;
Ok(()) Ok(())
......
...@@ -12,13 +12,15 @@ use crate::ErrorKind; ...@@ -12,13 +12,15 @@ use crate::ErrorKind;
/// Handy associated functions for `pkgar_core::Entry` that depend on std /// Handy associated functions for `pkgar_core::Entry` that depend on std
pub trait EntryExt { pub trait EntryExt {
fn check_path(&self) -> Result<&Path, ErrorKind>; fn check_path(&self) -> Result<&Path, ErrorKind>;
fn verify(&self, blake3: Hash, size: u64) -> Result<(), ErrorKind>;
} }
impl EntryExt for Entry { impl EntryExt for Entry {
/// Iterate the components of the path and ensure that there are no /// Iterate the components of the path and ensure that there are no
/// non-normal components. /// non-normal components.
fn check_path(&self) -> Result<&Path, ErrorKind> { fn check_path(&self) -> Result<&Path, ErrorKind> {
let path = Path::new(OsStr::from_bytes(self.path())); let path = Path::new(OsStr::from_bytes(self.path_bytes()));
for component in path.components() { for component in path.components() {
match component { match component {
Component::Normal(_) => {}, Component::Normal(_) => {},
...@@ -33,6 +35,22 @@ impl EntryExt for Entry { ...@@ -33,6 +35,22 @@ impl EntryExt for Entry {
} }
Ok(&path) Ok(&path)
} }
fn verify(&self, blake3: Hash, size: u64) -> Result<(), ErrorKind> {
let path = self.check_path()?;
if size != self.size() {
Err(ErrorKind::LengthMismatch {
entry: path.to_path_buf(),
actual: size,
expected: self.size(),
})
} else if blake3 != self.blake3() {
Err(ErrorKind::Core(pkgar_core::Error::InvalidBlake3))
} else {
Ok(())
}
}
} }
pub trait PackageSrcExt pub trait PackageSrcExt
......
...@@ -63,6 +63,8 @@ impl Repr { ...@@ -63,6 +63,8 @@ impl Repr {
} }
} }
/// Primary error type for pkgar. Provides optional path and reason context
/// for the underlying [`ErrorKind`](enum.ErrorKind.html).
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[error(transparent)] #[error(transparent)]
pub struct Error { pub struct Error {
...@@ -71,12 +73,22 @@ pub struct Error { ...@@ -71,12 +73,22 @@ pub struct Error {
} }
impl Error { impl Error {
/// Set the path associated with this error. Calling `path()` multiple
/// times will only store the most recently added path.
///
/// Most pkgar APIs will have already called `path()`; therefore, it's
/// unlikely that consumers of the library will need to use this function.
pub fn path(mut self, path: impl AsRef<Path>) -> Error { pub fn path(mut self, path: impl AsRef<Path>) -> Error {
let path = Some(path.as_ref().to_path_buf()); let path = Some(path.as_ref().to_path_buf());
self.repr = self.repr.as_complex(path, None); self.repr = self.repr.as_complex(path, None);
self self
} }
/// Set the reason associated with this error. Calling `reason()` multiple
/// times will only store the most recently added reason.
///
/// Most pkgar APIs will have already called `reason()`; therefore, it's
/// unlikely that consumers of the library will need to use this function.
pub fn reason(mut self, reason: impl ToString) -> Error { pub fn reason(mut self, reason: impl ToString) -> Error {
let reason = Some(reason.to_string()); let reason = Some(reason.to_string());
self.repr = self.repr.as_complex(None, reason); self.repr = self.repr.as_complex(None, reason);
...@@ -100,7 +112,10 @@ impl From<pkgar_keys::Error> for Error { ...@@ -100,7 +112,10 @@ impl From<pkgar_keys::Error> for Error {
impl UFE for Error {} impl UFE for Error {}
/// Primary enumeration of error types producible by the pkgar crate. Most
/// interaction with this type will be via [`Error`](struct.Error.html).
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[non_exhaustive]
pub enum ErrorKind { pub enum ErrorKind {
#[error("Io")] #[error("Io")]
Io(#[from] io::Error), Io(#[from] io::Error),
......
...@@ -55,42 +55,42 @@ impl Action { ...@@ -55,42 +55,42 @@ impl Action {
pub struct Transaction { pub struct Transaction {
actions: Vec<Action>, actions: Vec<Action>,
basedir: PathBuf,
} }
impl Transaction { impl Transaction {
pub fn new() -> Transaction { pub fn new(basedir: impl AsRef<Path>) -> Transaction {
Transaction { Transaction {
actions: Vec::new(), actions: Vec::new(),
basedir: basedir.as_ref().to_path_buf(),
} }
} }
pub fn install<Pkg, Pth>(&mut self, src: &mut Pkg, basedir: Pth) -> Result<(), Error> pub fn install<Pkg>(&mut self, src: &mut Pkg) -> Result<(), Error>
where where Pkg: PackageSrc<Err = Error> + PackageSrcExt,
Pkg: PackageSrc<Err = Error> + PackageSrcExt,
Pth: AsRef<Path>,
{ {
let mut buf = vec![0; READ_WRITE_HASH_BUF_SIZE]; let mut buf = vec![0; READ_WRITE_HASH_BUF_SIZE];
for entry in src.read_entries()? { let entries = src.read_entries()?;
self.actions.reserve(entries.len());
for entry in entries {
let relative_path = entry.check_path()?; let relative_path = entry.check_path()?;
let target_path = basedir.as_ref().join(relative_path); let target_path = self.basedir.join(relative_path);
//HELP: Under what circumstances could this ever fail? //HELP: Under what circumstances could this ever fail?
assert!(target_path.starts_with(&basedir), "target path was not in the base path"); assert!(target_path.starts_with(&self.basedir), "target path was not in the base path");
let entry_hash = Hash::from(entry.blake3()); let tmp_path = temp_path(&target_path, entry.blake3())?;
let tmp_path = temp_path(&target_path, entry_hash)?;
let mode_kind = entry.mode() & MODE_KIND; let (entry_data_size, entry_data_hash) = match entry.mode() & MODE_KIND {
let mode_perm = entry.mode() & MODE_PERM;
let (entry_data_size, entry_data_hash) = match mode_kind {
MODE_FILE => { MODE_FILE => {
//TODO: decide what to do when temp files are left over //TODO: decide what to do when temp files are left over
let mut tmp_file = fs::OpenOptions::new() let mut tmp_file = fs::OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.truncate(true) .truncate(true)
.mode(mode_perm) .mode(entry.mode & MODE_PERM)
.open(&tmp_path) .open(&tmp_path)
.map_err(|e| Error::from(e).path(&tmp_path) )?; .map_err(|e| Error::from(e).path(&tmp_path) )?;
...@@ -125,27 +125,18 @@ impl Transaction { ...@@ -125,27 +125,18 @@ impl Transaction {
} }
}; };
if entry_data_size != entry.size() { entry.verify(entry_data_hash, entry_data_size)
Err(Error::from( .map_err(|e| Error::from(e)
ErrorKind::LengthMismatch { .path(src.path())
entry: PathBuf::from(relative_path), )?;
actual: entry_data_size,
expected: entry.size(),
})
.path(src.path()))?
} else if entry_data_hash != entry_hash {
Err(pkgar_core::Error::InvalidBlake3)?
};
self.actions.push(Action::Rename(tmp_path, target_path)) self.actions.push(Action::Rename(tmp_path, target_path))
} }
Ok(()) Ok(())
} }
pub fn replace<Pkg, Pth>(&mut self, old: &mut Pkg, new: &mut Pkg, base_dir: Pth) -> Result<(), Error> pub fn replace<Pkg>(&mut self, old: &mut Pkg, new: &mut Pkg) -> Result<(), Error>
where where Pkg: PackageSrc<Err = Error> + PackageSrcExt,
Pkg: PackageSrc<Err = Error> + PackageSrcExt,
Pth: AsRef<Path>,
{ {
let old_entries = old.read_entries()?; let old_entries = old.read_entries()?;
let new_entries = new.read_entries()?; let new_entries = new.read_entries()?;
...@@ -156,7 +147,7 @@ impl Transaction { ...@@ -156,7 +147,7 @@ impl Transaction {
.find(|new_e| new_e.blake3() == old_e.blake3() ) .find(|new_e| new_e.blake3() == old_e.blake3() )
.is_none()) .is_none())
.map(|e| { .map(|e| {
let target_path = base_dir.as_ref() let target_path = self.basedir
.join(e.check_path()?); .join(e.check_path()?);
Ok(Action::Remove(target_path)) Ok(Action::Remove(target_path))
}) })
...@@ -164,22 +155,23 @@ impl Transaction { ...@@ -164,22 +155,23 @@ impl Transaction {
self.actions.append(&mut removes); self.actions.append(&mut removes);
//TODO: Don't force a re-read of all the entries for the new package //TODO: Don't force a re-read of all the entries for the new package
self.install(new, base_dir) self.install(new)
} }
pub fn remove<Pkg, Pth>(&mut self, pkg: &mut Pkg, base_dir: Pth) -> Result<(), Error> pub fn remove<Pkg>(&mut self, pkg: &mut Pkg) -> Result<(), Error>
where where Pkg: PackageSrc<Err = Error>,
Pkg: PackageSrc<Err = Error>,
Pth: AsRef<Path>,
{ {
let mut buf = vec![0; READ_WRITE_HASH_BUF_SIZE]; let mut buf = vec![0; READ_WRITE_HASH_BUF_SIZE];
for entry in pkg.read_entries()? { let entries = pkg.read_entries()?;
self.actions.reserve(entries.len());
for entry in entries {
let relative_path = entry.check_path()?; let relative_path = entry.check_path()?;
let target_path = base_dir.as_ref().join(relative_path); let target_path = self.basedir.join(relative_path);
// Under what circumstances could this ever fail? // Under what circumstances could this ever fail?
assert!(target_path.starts_with(&base_dir), "target path was not in the base path"); assert!(target_path.starts_with(&self.basedir), "target path was not in the base path");
let candidate = File::open(&target_path) let candidate = File::open(&target_path)
.map_err(|e| Error::from(e).path(&target_path) )?; .map_err(|e| Error::from(e).path(&target_path) )?;
......
...@@ -54,8 +54,8 @@ fn build_install_update_remove() -> Result<(), Box<dyn Error>> { ...@@ -54,8 +54,8 @@ fn build_install_update_remove() -> Result<(), Box<dyn Error>> {
let mut src_pkg = PackageFile::new(tmp.file("pkgar-src-1.pkgar"), &pkey_file.pkey)?; let mut src_pkg = PackageFile::new(tmp.file("pkgar-src-1.pkgar"), &pkey_file.pkey)?;
println!("Install archive"); println!("Install archive");
let mut install = Transaction::new(); let mut install = Transaction::new(tmp.dir("installroot"));
install.install(&mut src_pkg, tmp.dir("installroot"))?; install.install(&mut src_pkg)?;
install.commit()?; install.commit()?;
println!("Modify build"); println!("Modify build");
...@@ -70,13 +70,13 @@ fn build_install_update_remove() -> Result<(), Box<dyn Error>> { ...@@ -70,13 +70,13 @@ fn build_install_update_remove() -> Result<(), Box<dyn Error>> {
let mut src2_pkg = PackageFile::new(tmp.file("pkgar-src-2.pkgar"), &pkey_file.pkey)?; let mut src2_pkg = PackageFile::new(tmp.file("pkgar-src-2.pkgar"), &pkey_file.pkey)?;
println!("Upgrade archive"); println!("Upgrade archive");
let mut update = Transaction::new(); let mut update = Transaction::new(tmp.dir("installroot"));
update.replace(&mut src_pkg, &mut src2_pkg, tmp.dir("installroot"))?; update.replace(&mut src_pkg, &mut src2_pkg)?;
update.commit()?; update.commit()?;
println!("Uninstall archive"); println!("Uninstall archive");
let mut remove = Transaction::new(); let mut remove = Transaction::new(tmp.dir("installroot"));
remove.remove(&mut src2_pkg, tmp.dir("installroot"))?; remove.remove(&mut src2_pkg)?;
remove.commit()?; remove.commit()?;
assert_eq!(fs::read_dir(tmp.dir("installroot"))?.count(), 0); assert_eq!(fs::read_dir(tmp.dir("installroot"))?.count(), 0);
......
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