Commit c0ff5505 authored by SamwiseFilmore's avatar SamwiseFilmore
Browse files

Use bitflags! for mode defines

I think this cleans up the code for matching on modes a little bit. At
least most of it is now type checked so it'll be harder to break stuff
with the mode defines.
parent 802dc824
......@@ -8,6 +8,7 @@ repository = "https://gitlab.redox-os.org/redox-os/pkgar"
edition = "2018"
[dependencies]
bitflags = "1.2.1"
blake3 = { version = "0.3.6", default_features = false, features = ["rayon"] }
plain = "0.2.3"
sodiumoxide = { version = "0.2.6", default_features = false }
......
......@@ -2,6 +2,8 @@
use blake3::Hash;
use plain::Plain;
use crate::{Error, Mode};
#[derive(Clone, Copy, Debug)]
#[repr(packed)]
pub struct Entry {
......@@ -30,8 +32,9 @@ impl Entry {
self.size
}
pub fn mode(&self) -> u32 {
self.mode
pub fn mode(&self) -> Result<Mode, Error> {
Mode::from_bits(self.mode)
.ok_or(Error::InvalidMode(self.mode))
}
/// Retrieve the path, ending at the first NUL
......
......@@ -4,9 +4,10 @@ use core::fmt::{Display, Formatter, Result};
#[derive(Debug)]
pub enum Error {
InvalidBlake3,
InvalidData,
InvalidKey,
InvalidBlake3,
InvalidMode(u32),
InvalidSignature,
Plain(plain::Error),
Overflow,
......@@ -19,10 +20,11 @@ impl Display for Error {
use Error::*;
let msg = match self {
InvalidData => "DataInvalid".to_string(),
InvalidKey => "KeyInvalid".to_string(),
InvalidBlake3 => "InvalidBlake3".to_string(),
InvalidSignature => "InvalidSignature".to_string(),
InvalidBlake3 => "Invalid Blake3".to_string(),
InvalidData => "Data Invalid".to_string(),
InvalidKey => "Key Invalid".to_string(),
InvalidMode(mode) => format!("Invalid Mode: {:o}", mode),
InvalidSignature => "Invalid Signature".to_string(),
Plain(err) => format!("Plain: {:?}", err),
Overflow => "Overflow".to_string(),
TryFromInt(err) => format!("TryFromInt: {}", err),
......
......@@ -4,6 +4,8 @@ extern crate alloc;
use core::mem;
use bitflags::bitflags;
pub use crate::entry::Entry;
pub use crate::error::Error;
pub use crate::header::Header;
......@@ -17,6 +19,28 @@ mod package;
pub const HEADER_SIZE: usize = mem::size_of::<Header>();
pub const ENTRY_SIZE: usize = mem::size_of::<Entry>();
bitflags! {
/// Ensures that all platforms use the same mode defines.
pub struct Mode: u32 {
const PERM = 0o007777;
const KIND = 0o170000;
const FILE = 0o100000;
const SYMLINK = 0o120000;
}
}
impl Mode {
/// Only any kind bits
pub fn kind(self) -> Mode {
self & Mode::KIND
}
/// Only any permissions bits
pub fn perm(self) -> Mode {
self & Mode::PERM
}
}
#[cfg(test)]
mod tests {
use core::mem;
......
......@@ -4,11 +4,11 @@ use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use pkgar_core::{Entry, Header, PackageSrc};
use pkgar_core::{Entry, Header, Mode, PackageSrc};
use pkgar_keys::PublicKeyFile;
use sodiumoxide::crypto::sign;
use crate::{Error, ErrorKind, MODE_PERM, MODE_KIND, MODE_FILE, MODE_SYMLINK};
use crate::{Error, ErrorKind};
use crate::ext::{copy_and_hash, EntryExt};
use crate::package::PackageFile;
use crate::transaction::Transaction;
......@@ -50,11 +50,15 @@ 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;
let file_mode = metadata.permissions().mode();
//TODO: Use pkgar_core::Mode for all ops. This is waiting on error
// handling.
let mut mode = file_mode & Mode::PERM.bits();
if file_type.is_file() {
mode |= MODE_FILE;
mode |= Mode::FILE.bits();
} else if file_type.is_symlink() {
mode |= MODE_SYMLINK;
mode |= Mode::SYMLINK.bits();
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
......@@ -134,20 +138,25 @@ pub fn create(
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 {
MODE_FILE => {
let mode = entry.mode()
.map_err(|e| Error::from(e)
.entry(*entry)
)?;
let (total, hash) = match mode.kind() {
Mode::FILE => {
let mut entry_file = fs::OpenOptions::new()
.read(true)
.open(&path)
.map_err(|e| Error::from(e).path(&path) )?;
copy_and_hash(&mut entry_file, &mut archive_file, &mut buf)
.map_err(|e| Error::from(e)
.reason(format!("Writing entry to archive: '{}'", relative.display()))
.path(&path)
)?
},
MODE_SYMLINK => {
Mode::SYMLINK => {
let destination = fs::read_link(&path)
.map_err(|e| Error::from(e).path(&path) )?;
......@@ -158,19 +167,19 @@ pub fn create(
.path(&path)
)?
},
_ => {
return Err(ErrorKind::UnsupportedMode {
entry: relative.to_path_buf(),
mode: entry.mode(),
}.into());
}
_ => return Err(Error::from(
pkgar_core::Error::InvalidMode(mode.bits())
)
.entry(*entry)),
};
if total != entry.size() {
return Err(ErrorKind::LengthMismatch {
entry: relative.to_path_buf(),
actual: total,
expected: entry.size(),
}.into());
actual: total,
expected: entry.size(),
}
.as_error()
.entry(*entry)
);
}
entry.blake3.copy_from_slice(hash.as_bytes());
......
......@@ -7,46 +7,46 @@ use std::path::{Component, Path};
use blake3::{Hash, Hasher};
use pkgar_core::{Entry, PackageSrc};
use crate::ErrorKind;
use crate::{Error, ErrorKind};
/// Handy associated functions for `pkgar_core::Entry` that depend on std
pub trait EntryExt {
fn check_path(&self) -> Result<&Path, ErrorKind>;
fn check_path(&self) -> Result<&Path, Error>;
fn verify(&self, blake3: Hash, size: u64) -> Result<(), ErrorKind>;
fn verify(&self, blake3: Hash, size: u64) -> Result<(), Error>;
}
impl EntryExt for Entry {
/// Iterate the components of the path and ensure that there are no
/// non-normal components.
fn check_path(&self) -> Result<&Path, ErrorKind> {
fn check_path(&self) -> Result<&Path, Error> {
let path = Path::new(OsStr::from_bytes(self.path_bytes()));
for component in path.components() {
match component {
Component::Normal(_) => {},
invalid => {
let bad_component: &Path = invalid.as_ref();
return Err(ErrorKind::InvalidPath {
entry: path.to_path_buf(),
component: bad_component.to_path_buf(),
});
return Err(ErrorKind::InvalidPathComponent(bad_component.to_path_buf())
.as_error()
.entry(*self)
);
},
}
}
Ok(&path)
}
fn verify(&self, blake3: Hash, size: u64) -> Result<(), ErrorKind> {
let path = self.check_path()?;
fn verify(&self, blake3: Hash, size: u64) -> Result<(), Error> {
if size != self.size() {
Err(ErrorKind::LengthMismatch {
entry: path.to_path_buf(),
actual: size,
expected: self.size(),
})
actual: size,
expected: self.size(),
}
.as_error()
.entry(*self)
)
} else if blake3 != self.blake3() {
Err(ErrorKind::Core(pkgar_core::Error::InvalidBlake3))
Err(pkgar_core::Error::InvalidBlake3.into())
} else {
Ok(())
}
......
......@@ -10,15 +10,10 @@ pub use transaction::*;
use std::io;
use std::path::{Path, PathBuf};
use pkgar_core::{Entry, Mode};
use thiserror::Error;
use user_error::UFE;
// This ensures that all platforms use the same mode defines
pub(crate) const MODE_PERM: u32 = 0o007777;
pub(crate) const MODE_KIND: u32 = 0o170000;
pub(crate) const MODE_FILE: u32 = 0o100000;
pub(crate) const MODE_SYMLINK: u32 = 0o120000;
/// This mimics the way std::io::Error works, to manage adding context in an
/// adequate manner, without too much boilderplate.
#[derive(Debug, Error)]
......@@ -41,21 +36,29 @@ enum Repr {
Complex {
path: Option<PathBuf>,
reason: Option<String>,
entry: Option<Entry>,
src: ErrorKind,
}
}
impl Repr {
fn as_complex(self, new_path: Option<PathBuf>, new_reason: Option<String>) -> Repr {
fn as_complex(
self,
new_path: Option<PathBuf>,
new_reason: Option<String>,
new_entry: Option<Entry>,
) -> Repr {
match self {
Repr::Kind(src) => Repr::Complex {
path: new_path,
reason: new_reason,
entry: new_entry,
src,
},
Repr::Complex { path, reason, src } => Repr::Complex {
Repr::Complex { path, reason, entry, src } => Repr::Complex {
path: new_path.or(path),
reason: new_reason.or(reason),
entry: new_entry.or(entry),
src,
},
_ => self,
......@@ -80,7 +83,7 @@ impl Error {
/// unlikely that consumers of the library will need to use this function.
pub fn path(mut self, path: impl AsRef<Path>) -> Error {
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, None);
self
}
......@@ -91,7 +94,12 @@ impl Error {
/// unlikely that consumers of the library will need to use this function.
pub fn reason(mut self, reason: impl ToString) -> Error {
let reason = Some(reason.to_string());
self.repr = self.repr.as_complex(None, reason);
self.repr = self.repr.as_complex(None, reason, None);
self
}
pub fn entry(mut self, entry: Entry) -> Error {
self.repr = self.repr.as_complex(None, None, Some(entry));
self
}
}
......@@ -131,24 +139,23 @@ pub enum ErrorKind {
#[error("Package: {0}")]
Core(pkgar_core::Error),
#[error("Invalid component in entry path '{entry}': {component}")]
InvalidPath {
entry: PathBuf,
component: PathBuf,
},
#[error("Invalid path component: {0}")]
InvalidPathComponent(PathBuf),
#[error("Entry size mismatch for '{entry}', expected {expected}, got {actual}")]
#[error("Entry size mismatch: expected {expected}, got {actual}")]
LengthMismatch {
entry: PathBuf,
actual: u64,
expected: u64,
},
#[error("Unsupported mode for entry {entry}: {mode:#o}")]
UnsupportedMode {
entry: PathBuf,
mode: u32,
},
#[error("Invalid Mode Kind: {0:#o}")]
InvalidModeKind(Mode),
}
impl ErrorKind {
pub fn as_error(self) -> Error {
Error::from(self)
}
}
impl From<pkgar_core::Error> for ErrorKind {
......
......@@ -6,9 +6,9 @@ use std::os::unix::fs::{symlink, OpenOptionsExt};
use std::path::{Path, PathBuf};
use blake3::Hash;
use pkgar_core::PackageSrc;
use pkgar_core::{Mode, PackageSrc};
use crate::{Error, ErrorKind, MODE_FILE, MODE_KIND, MODE_PERM, MODE_SYMLINK};
use crate::{Error, ErrorKind};
use crate::ext::{copy_and_hash, EntryExt, PackageSrcExt};
const READ_WRITE_HASH_BUF_SIZE: usize = 4 * 1024 * 1024;
......@@ -22,10 +22,7 @@ fn temp_path(target_path: impl AsRef<Path>, entry_hash: Hash) -> Result<PathBuf,
};
let parent = target_path.as_ref().parent()
.ok_or(ErrorKind::InvalidPath {
entry: PathBuf::from(target_path.as_ref()),
component: PathBuf::from("/"),
})?;
.ok_or(ErrorKind::InvalidPathComponent(PathBuf::from("/")))?;
fs::create_dir_all(&parent)
.map_err(|e| Error::from(e).path(parent) )?;
Ok(parent.join(tmp_name))
......@@ -75,7 +72,8 @@ impl Transaction {
self.actions.reserve(entries.len());
for entry in entries {
let relative_path = entry.check_path()?;
let relative_path = entry.check_path()
.map_err(|e| e.path(src.path()) )?;
let target_path = self.basedir.join(relative_path);
//HELP: Under what circumstances could this ever fail?
......@@ -83,14 +81,20 @@ impl Transaction {
let tmp_path = temp_path(&target_path, entry.blake3())?;
let (entry_data_size, entry_data_hash) = match entry.mode() & MODE_KIND {
MODE_FILE => {
let mode = entry.mode()
.map_err(|e| Error::from(e)
.path(src.path())
.entry(entry)
)?;
let (entry_data_size, entry_data_hash) = match mode.kind() {
Mode::FILE => {
//TODO: decide what to do when temp files are left over
let mut tmp_file = fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(entry.mode & MODE_PERM)
.mode(mode.perm().bits())
.open(&tmp_path)
.map_err(|e| Error::from(e).path(&tmp_path) )?;
......@@ -100,7 +104,7 @@ impl Transaction {
.path(&tmp_path)
)?
},
MODE_SYMLINK => {
Mode::SYMLINK => {
let mut data = Vec::new();
let (size, hash) = copy_and_hash(src.entry_reader(entry), &mut data, &mut buf)
.map_err(|e| Error::from(e)
......@@ -117,18 +121,15 @@ impl Transaction {
},
_ => {
return Err(Error::from(
ErrorKind::UnsupportedMode {
entry: PathBuf::from(relative_path),
mode: entry.mode(),
})
pkgar_core::Error::InvalidMode(mode.bits())
)
.entry(entry)
.path(src.path()));
}
};
entry.verify(entry_data_hash, entry_data_size)
.map_err(|e| Error::from(e)
.path(src.path())
)?;
.map_err(|e| e.path(src.path()))?;
self.actions.push(Action::Rename(tmp_path, target_path))
}
......
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