From 1e7d73bc28ec53e72bbe4fb357b171367fef158c Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Sun, 2 Apr 2023 18:51:12 -0400 Subject: [PATCH 01/17] Split from hpk crate --- .gitignore | 3 + Cargo.toml | 30 +++ src/creator/mod.rs | 143 ++++++++++++ src/item/mod.rs | 91 ++++++++ src/lib.rs | 14 ++ src/package/dependency.rs | 28 +++ src/package/mod.rs | 122 +++++++++++ src/package/specs.rs | 31 +++ src/plist/mod.rs | 91 ++++++++ src/tar/README.md | 8 + src/tar/error.rs | 22 ++ src/tar/header.rs | 441 ++++++++++++++++++++++++++++++++++++++ src/tar/mod.rs | 134 ++++++++++++ src/tar/node.rs | 137 ++++++++++++ src/version/gitrev.rs | 79 +++++++ src/version/mod.rs | 130 +++++++++++ src/version/mod.rs.bak | 206 ++++++++++++++++++ src/version/rapid.rs | 168 +++++++++++++++ src/version/semver.rs | 191 +++++++++++++++++ 19 files changed, 2069 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/creator/mod.rs create mode 100644 src/item/mod.rs create mode 100644 src/lib.rs create mode 100644 src/package/dependency.rs create mode 100644 src/package/mod.rs create mode 100644 src/package/specs.rs create mode 100644 src/plist/mod.rs create mode 100644 src/tar/README.md create mode 100644 src/tar/error.rs create mode 100644 src/tar/header.rs create mode 100644 src/tar/mod.rs create mode 100644 src/tar/node.rs create mode 100644 src/version/gitrev.rs create mode 100644 src/version/mod.rs create mode 100644 src/version/mod.rs.bak create mode 100644 src/version/rapid.rs create mode 100644 src/version/semver.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83a0302 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +test diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..19b8704 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "hpk-package" +version = "0.1.0" +edition = "2021" +license = "GPL-3.0-only" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +deku = "0.16" +rayon = "1.7" +ron = "0.8" +sha2 = "0.10" +walkdir = "2.3" +zstd = "0.12" +thiserror = "1.0" +libc = "0.2" + +[dependencies.chrono] +version = "0.4" +features = ["serde"] + +[dependencies.serde] +version = "1.0" +features = ["derive"] + +[profile.release] +codegen-units = 1 +lto = true +strip = true diff --git a/src/creator/mod.rs b/src/creator/mod.rs new file mode 100644 index 0000000..e143601 --- /dev/null +++ b/src/creator/mod.rs @@ -0,0 +1,143 @@ +use { + crate::{Entry, Item, Package, Plist, Specs}, + rayon::prelude::{IntoParallelRefIterator, ParallelIterator}, + std::{ + borrow::BorrowMut, + env, + error::Error, + fs::{self, File}, + io::{self, Write}, + path::{Path, PathBuf}, + sync::{ + atomic::{AtomicUsize, Ordering}, + mpsc::Sender, + Mutex, + }, + }, + walkdir::WalkDir, + zstd::Encoder, +}; + +pub enum Message { + MemberAdded(String), + Success(String), + Failure(String), +} + +pub struct Creator { + path: PathBuf, + entries: Vec, + specs: Specs, +} + +impl Creator { + pub fn new(path: &Path, specs: Specs) -> Result { + let d = env::current_dir()?; + env::set_current_dir(path)?; + let path = path.to_path_buf(); + let entries = WalkDir::new(".") + .into_iter() + .filter(Result::is_ok) + .map(|x| x.unwrap().path().to_path_buf()) + .collect::>(); + env::set_current_dir(d)?; + Ok(Self { + path, + entries, + specs, + }) + } + + pub fn from_list(list: &[&str], specs: Specs) -> Result { + let entries = list.iter().map(|x| Path::new(x).to_path_buf()).collect(); + Ok(Self { + path: env::current_dir().unwrap_or(Path::new("/").to_path_buf()), + entries, + specs, + }) + } + + pub fn len(&self) -> usize { + self.entries.len() + } + + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub fn create(self, outdir: &Path, sender: Sender) -> Result<(), Box> { + let d = env::current_dir()?; + let plist = Mutex::new(Plist::default()); + let totalsize: AtomicUsize = 0.into(); + let fullname = format!( + "{}-{}_{}", + &self.specs.name, self.specs.version, self.specs.release + ); + if !outdir.exists() { + fs::create_dir_all(outdir)?; + } + let outdir = outdir.canonicalize()?; + let mut archive = outdir.clone(); + archive.push(&fullname); + archive.set_extension("tar.zst"); + let fd = File::create(&archive)?; + let writer = Mutex::new(Encoder::new(fd, 0)?); + let sender = Mutex::new(sender); + env::set_current_dir(&self.path)?; + self.entries + .par_iter() + .filter(|x| x.as_path() != Path::new(".")) + .for_each(|x| { + let sender = sender.lock().unwrap().clone(); + if let Ok(item) = Item::try_create(x.as_path()) { + if let Entry::File { + path: _, + sha256sum: _, + mode: _, + size, + } = &item.entry + { + totalsize.fetch_add(*size, Ordering::Release); + } + let path = match item.entry.clone() { + Entry::File { + path, + sha256sum: _, + mode: _, + size: _, + } + | Entry::Link { path, target: _ } + | Entry::Directory { path, mode: _ } => path, + }; + plist.lock().unwrap().borrow_mut().entries.push(item.entry); + match writer.lock().unwrap().borrow_mut().write_all(&item.data) { + Ok(_) => sender + .send(Message::MemberAdded(format!("{}", path.display()))) + .expect("couldn't send message"), + Err(e) => sender + .send(Message::Failure(format!("{e}"))) + .expect("couldn't send message"), + } + } else { + sender + .send(Message::Failure(format!( + "Could not process DirEntry for {}", + x.display() + ))) + .expect("could not send message"); + } + }); + let mut package: Package = self.specs.into(); + package.size = totalsize.into_inner(); + let plist = plist.into_inner()?; + package.plist = plist; + let node = package.save_ron_and_create_tar_node(&outdir)?; + let mut writer = writer.into_inner()?; + writer.write_all(&node.to_vec()?)?; + let _fd = writer.finish()?; + let sender = sender.into_inner()?; + sender.send(Message::Success(format!("{} saved", archive.display())))?; + env::set_current_dir(d)?; + Ok(()) + } +} diff --git a/src/item/mod.rs b/src/item/mod.rs new file mode 100644 index 0000000..6e2c60c --- /dev/null +++ b/src/item/mod.rs @@ -0,0 +1,91 @@ +use crate::Entry; +use sha2::{Digest, Sha256}; +use std::{ + error::Error, + ffi::OsStr, + fmt::Write, + fs, + io::Read, + os::unix::fs::MetadataExt, + path::{Path, PathBuf}, +}; +use crate::tar::{Node, Owner}; + +#[derive(Clone, Debug)] +pub struct Item { + pub entry: Entry, + pub data: Vec, +} + +impl Item { + pub fn try_create(path: &Path) -> Result> { + let path = fix_path(path); + let meta = fs::metadata(&path)?; + let filename = format!("{}", path.display()); + let owner = Some(Owner::default()); + if meta.is_file() { + let mut data = vec![]; + let mut fd = fs::File::open(path)?; + let path = PathBuf::from(&filename); + let size = fd.read_to_end(&mut data)?; + let mut sha256sum = String::new(); + let mut hasher = Sha256::new(); + hasher.update(&data); + let res = hasher.finalize(); + for c in res { + write!(sha256sum, "{c:02x}")?; + } + let mode = meta.mode(); + Ok(Self { + entry: Entry::File { + path, + sha256sum, + mode, + size, + }, + data: Node::read_data_to_tar(&data, &filename, &meta, owner)?.to_vec()?, + }) + } else if meta.is_dir() { + let mode = meta.mode(); + let path = PathBuf::from(&filename); + Ok(Self { + entry: Entry::Directory { path, mode }, + data: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?.to_vec()?, + }) + } else if meta.is_symlink() { + let target = fs::read_link(path)?; + let path = PathBuf::from(&filename); + Ok(Self { + entry: Entry::Link { path, target }, + data: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?.to_vec()?, + }) + } else { + unreachable!(); + } + } +} + +fn fix_path(path: &Path) -> PathBuf { + let path = if let Ok(p) = path.strip_prefix("./") { + p + } else { + path + }; + if path.is_file() || path.is_symlink() { + match path.ancestors().last().and_then(Path::to_str) { + Some("etc" | "var") => { + let ext = if let Some(x) = path.extension().and_then(OsStr::to_str) { + format!("{x}.new") + } else { + "new".to_string() + }; + let mut path = path.to_path_buf(); + path.set_extension(ext); + path + } + _ => path.to_path_buf(), + } + } else { + path.to_path_buf() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ea6d0ff --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +pub mod tar; +mod creator; +mod item; +mod package; +mod plist; +mod version; + +pub use { + creator::{Creator, Message}, + item::Item, + package::{Dependency, Package, Specs}, + plist::*, + version::*, +}; diff --git a/src/package/dependency.rs b/src/package/dependency.rs new file mode 100644 index 0000000..64cbc29 --- /dev/null +++ b/src/package/dependency.rs @@ -0,0 +1,28 @@ +use { + super::Package, + crate::Version, + serde::{Deserialize, Serialize}, +}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Dependency { + pub name: String, + pub version: (Option, Option), +} + +impl Dependency { + #[allow(clippy::must_use_candidate)] + pub fn satisfied(&self, package: &Package) -> bool { + if self.name.as_str() == package.name.as_str() { + match &self.version { + (Some(low), Some(high)) => &package.version >= low && &package.version < high, + (Some(low), None) => &package.version >= low, + (None, Some(high)) => &package.version < high, + // no version requirements + _ => true, + } + } else { + false + } + } +} diff --git a/src/package/mod.rs b/src/package/mod.rs new file mode 100644 index 0000000..13bde79 --- /dev/null +++ b/src/package/mod.rs @@ -0,0 +1,122 @@ +mod dependency; +mod specs; + +use { + crate::{Plist, Version}, + ron::ser::{to_string_pretty, PrettyConfig}, + serde::{Deserialize, Serialize}, + std::{ + error::Error, + fs, + fs::File, + io::{BufWriter, Write}, + path::Path, + }, + crate::tar::{Node, Owner}, +}; + +pub use {dependency::Dependency, specs::Specs}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct User { + pub name: String, + pub uid: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Group { + pub name: String, + pub gid: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +/// the metadata associated with a package +pub struct Package { + /// The name of the package minus all version information + pub name: String, + /// The `Version` of the package + pub version: Version, + /// The release number for this package + pub release: u8, + /// a single line description of the package + pub description: String, + /// a more verbose description of the package + pub long_description: String, + /// an optional link to an + /// [AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/) + /// metadata file + pub appstream_data: Option, + /// a listing of all files, directories and symlinks which are a part of + /// this package + pub plist: Plist, + /// the total size of this package + pub size: usize, + /// all of this package's runtime dependencies + pub dependencies: Vec, + /// an optional list of users to be created upon installation + pub users: Option>, + /// an optional list of groups to be created upon installation + pub groups: Option>, + /// an optional post installation shell script to be run + pub post_install: Option, +} + +impl From for Package { + fn from(value: Specs) -> Self { + Package { + name: value.name, + version: value.version, + release: value.release, + description: value.description, + long_description: value.long_description, + appstream_data: value.appstream_data, + dependencies: value.dependencies, + users: value.users, + groups: value.groups, + post_install: value.post_install, + ..Default::default() + } + } +} + +impl Package { + fn as_ron(&self) -> Result { + let cfg = PrettyConfig::new().struct_names(true); + to_string_pretty(self, cfg) + } + + pub(crate) fn save_ron_and_create_tar_node( + &self, + outdir: &Path, + ) -> Result> { + if !outdir.exists() { + fs::create_dir_all(outdir)?; + } + let mut outfile = outdir.to_path_buf(); + outfile.push("package.ron"); + let fd = File::create(&outfile)?; + let s = self.as_ron()?; + let mut writer = BufWriter::new(&fd); + writer.write_all(s.as_bytes())?; + writer.flush()?; + let node = Node::read_data_to_tar( + s.as_bytes(), + "package.ron", + &outfile.metadata()?, + Some(Owner::default()), + )?; + Ok(node) + } + + /// Returns the formatted full package name including version and release strings + pub fn fullname(&self) -> String { + format!("{}-{}_{}", self.name, self.version, self.release) + } + + /// Tests whether this package is an update for another + pub fn is_upgrade(&self, other: &Self) -> bool { + self.name == other.name + && (self.version > other.version + || (self.version == other.version && self.release > other.release)) + } +} diff --git a/src/package/specs.rs b/src/package/specs.rs new file mode 100644 index 0000000..1cfcc83 --- /dev/null +++ b/src/package/specs.rs @@ -0,0 +1,31 @@ +use { + super::{Group, User}, + crate::{Dependency, Version}, + serde::{Deserialize, Serialize}, +}; + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct Specs { + /// The name of the package minus all version information + pub name: String, + /// The `Version` of the package + pub version: Version, + /// The release number for this package + pub release: u8, + /// a single line description of the package + pub description: String, + /// a more verbose description of the package + pub long_description: String, + /// an optional link to an + /// [AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/) + /// metadata file + pub appstream_data: Option, + /// all of this package's runtime dependencies + pub dependencies: Vec, + /// an optional list of users to be created upon installation + pub users: Option>, + /// an optional list of groups to be created upon installation + pub groups: Option>, + /// an optional post installation shell script to be run + pub post_install: Option, +} diff --git a/src/plist/mod.rs b/src/plist/mod.rs new file mode 100644 index 0000000..3551aca --- /dev/null +++ b/src/plist/mod.rs @@ -0,0 +1,91 @@ +use { + rayon::prelude::*, + serde::{Deserialize, Serialize}, + sha2::{digest::Digest, Sha256}, + std::{ + error::Error, + fmt::Write, + fs, + io::Read, + os::unix::fs::MetadataExt, + path::{Path, PathBuf}, + }, + walkdir::WalkDir, +}; + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct Plist { + pub entries: Vec, +} + +impl TryFrom<&Path> for Plist { + type Error = Box; + + fn try_from(value: &Path) -> Result { + let entries = WalkDir::new(value) + .into_iter() + .collect::>() + .par_iter() + .filter(|x| x.is_ok()) + .filter_map(|x| { + Entry::try_from(x.as_ref().unwrap().path().to_path_buf().as_path()).ok() + }) + .collect(); + Ok(Self { entries }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum Entry { + File { + path: PathBuf, + sha256sum: String, + mode: u32, + size: usize, + }, + Directory { + path: PathBuf, + mode: u32, + }, + Link { + path: PathBuf, + target: PathBuf, + }, +} + +impl TryFrom<&Path> for Entry { + type Error = Box; + + fn try_from(value: &Path) -> Result { + let mut path = PathBuf::from("/"); + path.push(value); + let meta = fs::metadata(value)?; + if meta.is_file() { + let mut buf = vec![]; + let mut fd = fs::File::open(value)?; + let size = fd.read_to_end(&mut buf)?; + let mut sha256sum = String::new(); + let mut hasher = Sha256::new(); + hasher.update(&buf); + let res = hasher.finalize(); + for c in res { + write!(sha256sum, "{c:02x}")?; + } + let mode = meta.mode(); + Ok(Self::File { + path, + sha256sum, + mode, + size, + }) + } else if meta.is_dir() { + let mode = meta.mode(); + Ok(Self::Directory { path, mode }) + } else if meta.is_symlink() { + let target = fs::read_link(value)?; + Ok(Self::Link { path, target }) + } else { + unreachable!(); + } + } +} diff --git a/src/tar/README.md b/src/tar/README.md new file mode 100644 index 0000000..062958e --- /dev/null +++ b/src/tar/README.md @@ -0,0 +1,8 @@ +## About +Derived originally from [minitar](https://github.com/genonullfree/minitar), this +crate implements basic functionality for creating and extracting tar archives. It +has been adapted to allow creation of a Node (Tar header + 512byte blocks of data) +from the raw data plus file metadata. This allows for better efficiency when it +is embedded into another application (such as a package manager), as the raw data +and metadata about each file can be extracted once and reused for purposes such +as generating checksums, getting file sizes and creating packing lists. diff --git a/src/tar/error.rs b/src/tar/error.rs new file mode 100644 index 0000000..52f9f3a --- /dev/null +++ b/src/tar/error.rs @@ -0,0 +1,22 @@ +use std::{fmt, io, num::ParseIntError, str::Utf8Error}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("DekuError: {0}")] + Deku(#[from] deku::DekuError), + #[error("IoError: {0}")] + Io(#[from] io::Error), + #[error("Error in conversion of oct_to_dev")] + Utf8Error(#[from] Utf8Error), + #[error("Error in conversion of oct_to_dev")] + ParseIntError(#[from] ParseIntError), + #[error("End of tar")] + EndOfTar, + #[error("Invalid magic")] + InvalidMagic, + #[error("Invalid Checksum")] + InvalidChecksum, + #[error("Parse int failed")] + Parse(#[from] fmt::Error), +} diff --git a/src/tar/header.rs b/src/tar/header.rs new file mode 100644 index 0000000..6e22b78 --- /dev/null +++ b/src/tar/header.rs @@ -0,0 +1,441 @@ +use crate::tar::Error; +use deku::prelude::*; +use std::{ + env, + ffi::CStr, + fmt::{self, Write}, + fs::{self, Metadata}, + io, + ops::Deref, + os::{linux::fs::MetadataExt, unix::fs::FileTypeExt}, + path::PathBuf, +}; + +#[repr(u8)] +pub enum FileType { + Normal = 0x30, + Hardlink = 0x31, + Symlink = 0x32, + Char = 0x33, + Block = 0x34, + Dir = 0x35, + FIFO = 0x36, + Unknown = 0x00, +} + +impl From for FileType +where + T: Deref, +{ + fn from(meta: T) -> Self { + if meta.is_dir() { + return FileType::Dir; + } + + let file_type = meta.file_type(); + if file_type.is_fifo() { + return FileType::FIFO; + } else if file_type.is_char_device() { + return FileType::Char; + } else if file_type.is_block_device() { + return FileType::Block; + } else if file_type.is_fifo() { + return FileType::FIFO; + } else if file_type.is_symlink() { + return FileType::Symlink; + } else if file_type.is_file() { + return FileType::Normal; + } + + FileType::Unknown + } +} + +#[derive(Clone)] +pub struct Owner { + pub uid: u32, + pub gid: u32, + pub username: String, + pub groupname: String, +} + +impl Default for Owner { + fn default() -> Self { + Self { + uid: 0, + gid: 0, + username: "root".into(), + groupname: "root".into(), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, DekuRead, DekuWrite)] +#[deku(endian = "little")] +pub struct Header { + pub(crate) fname: [u8; 100], + pub(crate) mode: [u8; 8], + pub(crate) uid: [u8; 8], + pub(crate) gid: [u8; 8], + pub(crate) size: [u8; 12], + pub(crate) mtime: [u8; 12], + pub(crate) header_checksum: [u8; 8], + pub(crate) link_indicator: [u8; 1], + pub(crate) link_name: [u8; 100], + pub(crate) ustar_magic: [u8; 6], + pub(crate) ustar_version: [u8; 2], + pub(crate) username: [u8; 32], + pub(crate) groupname: [u8; 32], + pub(crate) device_major: [u8; 8], + pub(crate) device_minor: [u8; 8], + pub(crate) file_prefix: [u8; 155], + pub(crate) reserved: [u8; 12], +} + +impl Default for Header { + fn default() -> Self { + Self { + fname: [0; 100], + mode: [0; 8], + uid: [0; 8], + gid: [0; 8], + size: [0; 12], + mtime: [0; 12], + header_checksum: [0x20; 8], + link_indicator: [0; 1], + link_name: [0; 100], + ustar_magic: [0x75, 0x73, 0x74, 0x61, 0x72, 0x20], + ustar_version: [0x20, 0x00], + username: [0; 32], + groupname: [0; 32], + device_major: [0; 8], + device_minor: [0; 8], + file_prefix: [0; 155], + reserved: [0; 12], + } + } +} + +impl Header { + pub fn filename(&self) -> Result { + let mut s = String::new(); + for c in self.fname { + if c != b'\0' { + write!(s, "{c}")?; + } else { + break; + } + } + Ok(s) + } + + pub fn mode(&self) -> Result { + let mut s = String::new(); + for c in self.mode { + if c != b'\0' { + write!(s, "{c}")?; + } else { + break; + } + } + let mode = u32::from_str_radix(&s, 8)?; + Ok(mode) + } + + fn uid(&self) -> Result { + let mut s = String::new(); + for c in self.mode { + if c != b'\0' { + write!(s, "{c}")?; + } else { + break; + } + } + let uid = u32::from_str_radix(&s, 8)?; + Ok(uid) + } + + fn gid(&self) -> Result { + let mut s = String::new(); + for c in self.mode { + if c != b'\0' { + write!(s, "{c}")?; + } else { + break; + } + } + let gid = u32::from_str_radix(&s, 8)?; + Ok(gid) + } + + fn username(&self) -> Result { + let mut s = String::new(); + for c in self.username { + if c != b'\0' { + write!(s, "{c}")?; + } else { + break; + } + } + Ok(s) + } + + fn groupname(&self) -> Result { + let mut s = String::new(); + for c in self.groupname { + if c != b'\0' { + write!(s, "{c}")?; + } else { + break; + } + } + Ok(s) + } + + pub fn owner(&self) -> Result { + let uid = self.uid()?; + let gid = self.gid()?; + let username = self.username()?; + let groupname = self.groupname()?; + Ok(Owner { + uid, + gid, + username, + groupname, + }) + } + + pub fn prefix(&self) -> Result { + let mut s = String::new(); + for c in self.file_prefix { + if c != b'\0' { + write!(s, "{c}")?; + } else { + break; + } + } + Ok(s) + } + + pub fn new(filename: &str) -> Result { + let mut header = Header::default(); + let meta = fs::symlink_metadata(filename)?; + let (filename, prefix) = if filename.len() < 100 { + (filename.to_string(), None) + } else { + // Deal with file names longer than 100 bytes + let path = PathBuf::from(&filename); + let name = match path.file_name().and_then(|n| n.to_str()) { + Some(n) => n.to_string(), + None => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "Cannot get file name", + ))) + } + }; + let dir = match path.parent() { + Some(d) => d, + None => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "Cannot get path prefix", + ))) + } + }; + (name, Some(format!("{}", dir.display()))) + }; + + /* Fill in metadata */ + header.fname[..filename.len()].copy_from_slice(filename.as_bytes()); + let mode = format!("{:07o}", (meta.st_mode() & 0o777)); + header.mode[..mode.len()].copy_from_slice(mode.as_bytes()); + let user = format!("{:07o}", meta.st_uid()); + header.uid[..user.len()].copy_from_slice(user.as_bytes()); + let group = format!("{:07o}", meta.st_gid()); + header.gid[..group.len()].copy_from_slice(group.as_bytes()); + let size = format!("{:011o}", meta.st_size()); + header.size[..size.len()].copy_from_slice(size.as_bytes()); + let mtime = format!("{:011o}", meta.st_mtime()); + header.mtime[..mtime.len()].copy_from_slice(mtime.as_bytes()); + if let Some(prefix) = prefix { + header.file_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes()); + } + + /* Get the file type and conditional metadata */ + header.link_indicator[0] = FileType::from(&meta) as u8; + if header.link_indicator[0] == FileType::Symlink as u8 { + let link = fs::read_link(filename)?.to_str().unwrap().to_string(); + header.link_name[..link.len()].copy_from_slice(link.as_bytes()); + } else if header.link_indicator[0] == FileType::Block as u8 { + let major = format!("{:07o}", meta.st_dev()); + header.device_major[..major.len()].copy_from_slice(major.as_bytes()); + let minor = format!("{:07o}", meta.st_rdev()); + header.device_minor[..minor.len()].copy_from_slice(minor.as_bytes()); + } + + /* TODO: Find better way to get username */ + let key = "USER"; + if let Ok(val) = env::var(key) { + header.username[..val.len()].copy_from_slice(val.as_bytes()) + } + /* TODO: Find way to get groupname */ + + /* Update the header checksum value */ + header.update_checksum()?; + + Ok(header) + } + + pub fn new_from_meta( + filename: &str, + meta: &Metadata, + owner: Option, + ) -> Result { + let mut header = Header::default(); + let (filename, prefix) = if filename.len() < 100 { + (filename.to_string(), None) + } else { + // Deal with file names longer than 100 bytes + let path = PathBuf::from(&filename); + let name = match path.file_name().and_then(|n| n.to_str()) { + Some(n) => n.to_string(), + None => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "Cannot get file name", + ))) + } + }; + let dir = match path.parent() { + Some(d) => d, + None => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "Cannot get path prefix", + ))) + } + }; + (name, Some(format!("{}", dir.display()))) + }; + header.fname[..filename.len()].copy_from_slice(filename.as_bytes()); + let mode = format!("{:07o}", meta.st_mode()); + header.mode[..mode.len()].copy_from_slice(mode.as_bytes()); + let owner = match owner { + Some(o) => o, + None => Owner { + uid: meta.st_uid(), + gid: meta.st_gid(), + username: get_username_for_uid(meta.st_uid())?.into(), + groupname: get_groupname_for_gid(meta.st_gid())?.into(), + }, + }; + let uid = format!("{:07o}", owner.uid); + header.uid[..uid.len()].copy_from_slice(uid.as_bytes()); + let gid = format!("{:07o}", owner.gid); + header.gid[..gid.len()].copy_from_slice(gid.as_bytes()); + let size = format!("{:011o}", meta.len()); + header.size[..size.len()].copy_from_slice(size.as_bytes()); + let mtime = format!("{:011o}", meta.st_mtime()); + header.mtime[..mtime.len()].copy_from_slice(mtime.as_bytes()); + if let Some(prefix) = prefix { + header.file_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes()); + } + header.link_indicator[0] = FileType::from(meta) as u8; + if header.link_indicator[0] == FileType::Symlink as u8 { + let link = fs::read_link(filename)?.to_str().unwrap().to_string(); + header.link_name[..link.len()].copy_from_slice(link.as_bytes()); + } else if header.link_indicator[0] == FileType::Block as u8 { + let major = format!("{:07o}", meta.st_dev()); + header.device_major[..major.len()].copy_from_slice(major.as_bytes()); + let minor = format!("{:07o}", meta.st_rdev()); + header.device_minor[..minor.len()].copy_from_slice(minor.as_bytes()); + } + header.username[..owner.username.len()].copy_from_slice(owner.username.as_bytes()); + header.groupname[..owner.groupname.len()].copy_from_slice(owner.groupname.as_bytes()); + header.update_checksum()?; + Ok(header) + } + + /// Validates that the magic value received matches the magic value required in the Tar specification. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Header; + /// let header = Header::default(); + /// if !header.validate_magic() { + /// println!("Magic value is invalid"); + /// } + /// ``` + pub fn validate_magic(self) -> bool { + self.ustar_magic == "ustar ".as_bytes() + } + + /// Validates the header checksum computes to the expected value. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Header; + /// let header = Header::default(); + /// if header.validate_checksum().unwrap() { + /// println!("Checksum is valid"); + /// } + /// ``` + pub fn validate_checksum(self) -> Result { + let mut test = self; + let mut new = [0x20u8; 8]; + test.header_checksum.copy_from_slice(&[0x20; 8]); + + let tmp = format!("{:06o}\x00", test.calc_checksum()?); + new[..tmp.len()].copy_from_slice(tmp.as_bytes()); + + Ok(self.header_checksum == new) + } + + /// Updates the header checksum value. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Header; + /// let mut header = Header::default(); + /// + /// /* Fill in header information */ + /// + /// header.update_checksum(); + /// ``` + pub fn update_checksum(&mut self) -> Result<(), Error> { + let checksum = format!("{:06o}\x00", self.calc_checksum()?); + self.header_checksum[..checksum.len()].copy_from_slice(checksum.as_bytes()); + Ok(()) + } + + fn calc_checksum(self) -> Result { + let out = self.to_bytes()?; + let mut checksum = 0; + for i in out { + checksum += i as usize; + } + Ok(checksum) + } +} + +fn get_username_for_uid<'a>(uid: u32) -> Result<&'a str, std::str::Utf8Error> { + let user = unsafe { + let pw = libc::getpwuid(uid); + let name = (*pw).pw_name; + CStr::from_ptr(name) + }; + user.to_str() +} + +pub fn get_groupname_for_gid<'a>(gid: u32) -> Result<&'a str, std::str::Utf8Error> { + let group = unsafe { + let gr = libc::getgrgid(gid); + let name = (*gr).gr_name; + CStr::from_ptr(name) + }; + group.to_str() +} diff --git a/src/tar/mod.rs b/src/tar/mod.rs new file mode 100644 index 0000000..c85010c --- /dev/null +++ b/src/tar/mod.rs @@ -0,0 +1,134 @@ +use std::{ + fs::File, + io::{self, BufReader, Write}, +}; + +mod error; +mod header; +mod node; +pub use { + error::Error, + header::{FileType, Header, Owner}, + node::Node, +}; + +#[derive(Default)] +pub struct Archive { + pub nodes: Vec, +} + +impl Archive { + pub fn to_vec(self) -> Result, Error> { + let mut buf = vec![]; + for node in self.nodes { + buf.extend(node.to_vec()?); + } + buf.write_all(&[0; 9216])?; + Ok(buf) + } + + /// Write out a vector of `TarNodes` to a file or something that implements ``std::io::Write`` and ``std::io::Copy``. + /// + /// # Example + /// + /// ``` + /// use std::fs::File; + /// use hpk_package::tar::Archive; + /// + /// let data = Archive::new("test/1.txt").unwrap(); + /// + /// let out = File::create("test/2.tar".to_string()).unwrap(); + /// data.write(&out).unwrap(); + /// ``` + pub fn write(self, mut input: T) -> Result { + let mut written = 0; + for f in self.nodes.clone() { + written += f.write(input)?; + } + + /* Complete the write with 18 blocks of 512 ``0x00`` bytes per the specification */ + if !self.nodes.is_empty() { + input.write_all(&[0; 9216])?; + written += 9216; + } + + Ok(written) + } + + /// Create a new `TarFile` struct and initialize it with a `filename` file. This will read in the file to + /// the `TarFile` struct as a `TarNode`. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Archive; + /// + /// let data = Archive::new("test/1.txt").unwrap(); + /// ``` + pub fn new(filename: &str) -> Result { + Ok(Self { + nodes: vec![Node::read_file_to_tar(filename)?], + }) + } + + /// Append another file to the `TarFile.file` vector. This adds a file to the internal representation of the tar file. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Archive; + /// + /// let mut data = Archive::new("test/1.txt").unwrap(); + /// data.append("test/1.txt").unwrap(); + /// ``` + pub fn append(&mut self, filename: &str) -> Result<(), Error> { + self.nodes.push(Node::read_file_to_tar(filename)?); + + Ok(()) + } + + /// Open and load an external tar file into the internal `TarFile` struct. This parses and loads up all the files + /// contained within the external tar file. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Archive; + /// + /// Archive::open("test/1.tar".to_string()).unwrap(); + /// ``` + pub fn open(filename: String) -> Result { + let file = File::open(&filename)?; + let mut reader = BufReader::new(file); + let mut out = Self { + nodes: Vec::::new(), + }; + + while let Ok(t) = Node::read(&mut reader) { + out.nodes.push(t); + } + + Ok(out) + } + + /// Remove the first file from the Tar that matches the filename and path. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Archive; + /// + /// let mut data = Archive::new("test/1.tar").unwrap(); + /// data.remove("test/1.tar".to_string()).unwrap(); + /// ``` + pub fn remove(&mut self, filename: String) -> Result { + let mut name = [0u8; 100]; + name[..filename.len()].copy_from_slice(filename.as_bytes()); + if let Some(i) = &self.nodes.iter().position(|x| x.header.fname == name) { + self.nodes.remove(*i); + return Ok(true); + } + + Ok(false) + } +} diff --git a/src/tar/node.rs b/src/tar/node.rs new file mode 100644 index 0000000..eac092c --- /dev/null +++ b/src/tar/node.rs @@ -0,0 +1,137 @@ +use crate::tar::{header::Owner, Error, FileType, Header}; +use deku::prelude::*; +use std::{ + fs::{File, Metadata}, + io::{self, BufReader}, + str, +}; + +#[derive(Clone, Debug, Default)] +pub struct Node { + pub header: Header, + pub data: Vec<[u8; 512]>, +} + +impl Node { + pub fn to_vec(self) -> Result, DekuError> { + let mut buf = self.header.to_bytes()?; + for block in self.data { + buf.extend(block.to_vec()); + } + Ok(buf) + } + + /// Write out a single file within the tar to a file or something with a ``std::io::Write`` trait. + pub fn write(self, mut input: T) -> Result { + input.write_all(&self.header.to_bytes()?)?; + let mut written = 512; + for d in self.data { + input.write_all(&d)?; + written += d.len(); + } + + Ok(written) + } + + /// Read a TarNode in from a file or something with a ``std::io::Read`` trait. + pub fn read(mut input: T) -> Result { + let mut h = vec![0u8; 512]; + input.read_exact(&mut h)?; + + let (_, header) = Header::from_bytes((&h, 0))?; + if header == Header::default() { + return Err(Error::EndOfTar); + } + if !header.validate_magic() { + return Err(Error::InvalidMagic); + } + if !header.validate_checksum()? { + return Err(Error::InvalidChecksum); + } + + let chunks = (oct_to_dec(&header.size)? / 512) + 1; + Ok(Node { + header, + data: Node::chunk_file(&mut input, Some(chunks))?, + }) + } + + /// Open and read a file from the ``filename`` argument to a TarNode. + pub fn read_file_to_tar(filename: &str) -> Result { + let header = Header::new(filename)?; + if header.link_indicator[0] != FileType::Normal as u8 { + return Ok(Node { + header, + data: Vec::<[u8; 512]>::new(), + }); + } + + let file = File::open(filename)?; + let mut reader = BufReader::new(file); + Ok(Node { + header, + data: Node::chunk_file(&mut reader, None)?, + }) + } + + /// Create a Node from in memory data, given the filename and metadata + pub fn read_data_to_tar( + data: &[u8], + filename: &str, + meta: &Metadata, + owner: Option, + ) -> Result { + let header = Header::new_from_meta(filename, meta, owner)?; + if header.link_indicator[0] != FileType::Normal as u8 { + return Ok(Node { + header, + data: Vec::<[u8; 512]>::new(), + }); + } + let mut reader = BufReader::new(data); + Ok(Node { + header, + data: Node::chunk_file(&mut reader, None)?, + }) + } + + /// Read in and split a file into ``512`` byte chunks. + fn chunk_file( + file: &mut T, + max_chunks: Option, + ) -> Result, Error> { + /* Extract the file data from the tar file */ + let mut out = Vec::<[u8; 512]>::new(); + let mut n = if let Some(max) = max_chunks { + max + } else { + usize::MAX + }; + + /* Carve out 512 bytes at a time */ + let mut buf: [u8; 512] = [0; 512]; + loop { + let len = file.read(&mut buf)?; + + n -= 1; + + /* If read len == 0, we've hit the EOF */ + if len == 0 || n == 0 { + break; + } + + /* Save this chunk */ + out.push(buf); + } + Ok(out) + } +} + +fn oct_to_dec(input: &[u8]) -> Result { + /* Convert the &[u8] to string and remove the null byte */ + let mut s = str::from_utf8(input)?.to_string(); + s.pop(); + + /* Convert to usize from octal */ + Ok(usize::from_str_radix(&s, 8)?) +} diff --git a/src/version/gitrev.rs b/src/version/gitrev.rs new file mode 100644 index 0000000..4b6ef38 --- /dev/null +++ b/src/version/gitrev.rs @@ -0,0 +1,79 @@ +use { + crate::Version, + chrono::{offset::Utc, DateTime}, + serde::{Deserialize, Serialize}, + std::{cmp::Ordering, error::Error, fmt, str::FromStr}, +}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct GitRev { + /// the short revision hash + pub hash: String, + /// the time of the revision commit + pub datetime: DateTime, +} + +impl fmt::Display for GitRev { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "git_{}.{}", self.hash, self.datetime.format("%Y%m%d")) + } +} + +impl PartialOrd for GitRev { + fn partial_cmp(&self, other: &Self) -> Option { + self.datetime.partial_cmp(&other.datetime) + } +} + +impl Ord for GitRev { + fn cmp(&self, other: &Self) -> Ordering { + self.datetime.cmp(&other.datetime) + } +} + +#[derive(Debug)] +pub struct ParseGitRevError; + +impl fmt::Display for ParseGitRevError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error parsing git version") + } +} + +impl Error for ParseGitRevError {} + +impl From for ParseGitRevError { + fn from(_value: chrono::ParseError) -> Self { + Self + } +} + +impl FromStr for GitRev { + type Err = ParseGitRevError; + + fn from_str(s: &str) -> Result { + if let Some(gitrev) = s.strip_prefix("git_") { + if let Some((hash, date)) = gitrev.split_once('_') { + if hash.len() == 7 { + let datetime = DateTime::parse_from_str(date, "%Y%m%d")?; + return Ok(Self { + hash: hash.to_string(), + datetime: datetime.into(), + }); + } + } + } + Err(ParseGitRevError) + } +} + +impl TryFrom for GitRev { + type Error = ParseGitRevError; + + fn try_from(value: Version) -> Result { + match value { + Version::Git(g) => Ok(g), + _ => Err(ParseGitRevError), + } + } +} diff --git a/src/version/mod.rs b/src/version/mod.rs new file mode 100644 index 0000000..06b8057 --- /dev/null +++ b/src/version/mod.rs @@ -0,0 +1,130 @@ +use { + serde::{Deserialize, Serialize}, + std::{error::Error, fmt, str::FromStr}, +}; + +mod gitrev; +mod rapid; +mod semver; + +pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum Version { + Number(u32), + Rapid(Rapid), + SemVer(SemVer), + Git(GitRev), +} + +impl Default for Version { + fn default() -> Self { + Self::SemVer(SemVer { + major: 0, + minor: 1, + patch: 0, + }) + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Number(n) => write!(f, "{n}"), + Self::SemVer(s) => write!(f, "{s}"), + Self::Rapid(r) => write!(f, "{r}"), + Self::Git(g) => write!(f, "{g}"), + } + } +} + +impl From for Version { + fn from(value: SemVer) -> Self { + Self::SemVer(value) + } +} + +impl From for Version { + fn from(value: Rapid) -> Self { + Self::Rapid(value) + } +} + +impl From for Version { + fn from(value: GitRev) -> Self { + Self::Git(value) + } +} + +impl From for Version { + fn from(value: u32) -> Self { + Self::Number(value) + } +} + +impl PartialOrd for Version { + #[allow(clippy::many_single_char_names)] + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Self::Number(s), Self::Number(o)) => s.partial_cmp(o), + (Self::Number(s), Self::Rapid(o)) => s.partial_cmp(o), + (Self::Number(s), Self::SemVer(o)) => s.partial_cmp(o), + (Self::Rapid(s), Self::Number(o)) => s.partial_cmp(o), + (Self::Rapid(s), Self::Rapid(o)) => s.partial_cmp(o), + (Self::Rapid(s), Self::SemVer(o)) => s.partial_cmp(o), + (Self::SemVer(s), Self::Number(o)) => s.partial_cmp(o), + (Self::SemVer(s), Self::Rapid(o)) => s.partial_cmp(o), + (Self::SemVer(s), Self::SemVer(o)) => s.partial_cmp(o), + (Self::Git(s), Self::Git(o)) => s.partial_cmp(o), + _ => None, + } + } +} + +#[derive(Debug)] +pub struct ParseVersionError; + +impl fmt::Display for ParseVersionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "error parsing version") + } +} + +impl Error for ParseVersionError {} + +impl FromStr for Version { + type Err = ParseVersionError; + + fn from_str(s: &str) -> Result { + if let Ok(v) = s.parse::() { + Ok(v.into()) + } else if let Ok(v) = s.parse::() { + Ok(v.into()) + } else if let Ok(v) = s.parse::() { + Ok(v.into()) + } else if let Ok(v) = s.parse::() { + Ok(v.into()) + } else { + Err(ParseVersionError) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn cmp_semver_rapid_gt() { + let sem = "1.42.1".parse::().unwrap(); + let rpd = "1.42".parse::().unwrap(); + assert!(sem > rpd); + } + + #[test] + fn cmp_semver_rapid_eq() { + let sem = "1.42.0".parse::().unwrap(); + let rpd = "1.42".parse::().unwrap(); + assert!(sem == rpd); + } +} diff --git a/src/version/mod.rs.bak b/src/version/mod.rs.bak new file mode 100644 index 0000000..9208624 --- /dev/null +++ b/src/version/mod.rs.bak @@ -0,0 +1,206 @@ +use { + chrono::{offset::Utc, DateTime}, + serde::{Deserialize, Serialize}, + std::{error::Error, fmt, str::FromStr}, +}; + +mod gitrev; +mod rapid; +mod semver; + +pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum Version { + Number(u32), + Rapid { + major: u32, + minor: u32, + }, + SemVer { + major: u32, + minor: u32, + patch: u32, + }, + Git { + hash: String, + datetime: DateTime, + }, +} + +impl Default for Version { + fn default() -> Self { + Self::SemVer { + major: 0, + minor: 1, + patch: 0, + } + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Number(n) => write!(f, "{n}"), + Self::SemVer { + major, + minor, + patch, + } => { + let v = SemVer { + major: *major, + minor: *minor, + patch: *patch, + }; + write!(f, "{v}") + } + Self::Rapid { major, minor } => { + let v = Rapid { + major: *major, + minor: *minor, + }; + write!(f, "{v}") + } + Self::Git { hash, datetime } => { + let v = GitRev { + hash: hash.clone(), + datetime: *datetime, + }; + write!(f, "{v}") + } + } + } +} + +impl From for Version { + fn from(value: SemVer) -> Self { + Self::SemVer { + major: value.major, + minor: value.minor, + patch: value.patch, + } + } +} + +impl From for Version { + fn from(value: Rapid) -> Self { + Self::Rapid { + major: value.major, + minor: value.minor, + } + } +} + +impl From for Version { + fn from(value: GitRev) -> Self { + Self::Git { + hash: value.hash, + datetime: value.datetime, + } + } +} + +impl From for Version { + fn from(value: u32) -> Self { + Self::Number(value) + } +} + +impl PartialOrd for Version { + #[allow(clippy::many_single_char_names)] + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Self::Number(s), Self::Number(o)) => s.partial_cmp(o), + ( + Self::SemVer { + major, + minor, + patch, + }, + Self::SemVer { + major: a, + minor: b, + patch: c, + }, + ) => (major, minor, patch).partial_cmp(&(a, b, c)), + (Self::Rapid { major, minor }, Self::Rapid { major: a, minor: b }) => { + (major, minor).partial_cmp(&(a, b)) + } + ( + Self::Git { + hash: _a, + datetime: b, + }, + Self::Git { + hash: _c, + datetime: d, + }, + ) => b.partial_cmp(&d), + ( + Self::SemVer { + major, + minor, + patch, + }, + Self::Rapid { major: a, minor: b }, + ) => SemVer { + major: *major, + minor: *minor, + patch: *patch, + } + .partial_cmp(&Rapid { + major: *a, + minor: *b, + }), + _ => None, + } + } +} + +#[derive(Debug)] +pub struct ParseVersionError; + +impl fmt::Display for ParseVersionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "error parsing version") + } +} + +impl Error for ParseVersionError {} + +impl FromStr for Version { + type Err = ParseVersionError; + + fn from_str(s: &str) -> Result { + if let Ok(v) = s.parse::() { + Ok(v.into()) + } else if let Ok(v) = s.parse::() { + Ok(v.into()) + } else if let Ok(v) = s.parse::() { + Ok(v.into()) + } else if let Ok(v) = s.parse::() { + Ok(v.into()) + } else { + Err(ParseVersionError) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn cmp_semver_rapid_gt() { + let sem = "1.42.1".parse::().unwrap(); + let rpd = "1.42".parse::().unwrap(); + assert!(sem > rpd); + } + + #[test] + fn cmp_semver_rapid_eq() { + let sem = "1.42.0".parse::().unwrap(); + let rpd = "1.42".parse::().unwrap(); + assert!(sem == rpd); + } +} diff --git a/src/version/rapid.rs b/src/version/rapid.rs new file mode 100644 index 0000000..0be2946 --- /dev/null +++ b/src/version/rapid.rs @@ -0,0 +1,168 @@ +use crate::SemVer; + +use { + crate::Version, + serde::{Deserialize, Serialize}, + std::{cmp::Ordering, error::Error, fmt, num::ParseIntError, str::FromStr}, +}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct Rapid { + pub major: u32, + pub minor: u32, +} + +impl fmt::Display for Rapid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}", self.major, self.minor) + } +} + +impl PartialOrd for Rapid { + fn partial_cmp(&self, other: &Self) -> Option { + match self.major.partial_cmp(&other.major) { + Some(Ordering::Greater) => Some(Ordering::Greater), + Some(Ordering::Less) => Some(Ordering::Less), + Some(Ordering::Equal) => self.minor.partial_cmp(&other.minor), + None => None, + } + } +} + +impl Ord for Rapid { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match self.major.cmp(&other.major) { + Ordering::Greater => Ordering::Greater, + Ordering::Less => Ordering::Less, + Ordering::Equal => self.minor.cmp(&other.minor), + } + } +} + +impl PartialEq for Rapid { + fn eq(&self, other: &u32) -> bool { + self.major == *other && self.minor == 0 + } +} + +impl PartialOrd for Rapid { + fn partial_cmp(&self, other: &u32) -> Option { + match self.major.partial_cmp(other) { + Some(Ordering::Greater) => Some(Ordering::Greater), + Some(Ordering::Less) => Some(Ordering::Less), + None => None, + Some(Ordering::Equal) => { + if self.minor == 0 { + Some(Ordering::Equal) + } else { + Some(Ordering::Greater) + } + } + } + } +} + +impl PartialEq for Rapid { + fn eq(&self, other: &SemVer) -> bool { + other.eq(self) + } +} + +impl PartialOrd for Rapid { + fn partial_cmp(&self, other: &SemVer) -> Option { + match other.partial_cmp(self) { + Some(Ordering::Less) => Some(Ordering::Greater), + Some(Ordering::Greater) => Some(Ordering::Less), + Some(Ordering::Equal) => Some(Ordering::Equal), + None => None, + } + } +} + +impl PartialEq for u32 { + fn eq(&self, other: &Rapid) -> bool { + other.eq(self) + } +} + +impl PartialOrd for u32 { + fn partial_cmp(&self, other: &Rapid) -> Option { + match other.partial_cmp(self) { + Some(Ordering::Equal) => Some(Ordering::Equal), + Some(Ordering::Less) => Some(Ordering::Greater), + Some(Ordering::Greater) => Some(Ordering::Less), + None => None, + } + } +} + +#[derive(Debug, PartialEq)] +pub struct ParseRapidError; + +impl fmt::Display for ParseRapidError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error parsing Rapid version") + } +} + +impl Error for ParseRapidError {} + +impl From for ParseRapidError { + fn from(_value: ParseIntError) -> Self { + Self + } +} + +impl FromStr for Rapid { + type Err = ParseRapidError; + + fn from_str(s: &str) -> Result { + let split = s.split('.').collect::>(); + match split.len() { + 2 => { + let major = split.first().unwrap().parse::()?; + let minor = split.get(1).unwrap().parse::()?; + Ok(Self { major, minor }) + } + _ => Err(ParseRapidError), + } + } +} + +impl TryFrom for Rapid { + type Error = ParseRapidError; + + fn try_from(value: Version) -> Result { + match value { + Version::SemVer(s) => { + if s.patch == 0 { + Ok(Self { + major: s.major, + minor: s.minor, + }) + } else { + Err(ParseRapidError) + } + } + Version::Rapid(s) => Ok(s), + Version::Number(major) => Ok(Self { major, minor: 0 }), + Version::Git(_) => Err(ParseRapidError), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_semver() { + assert_eq!( + "93.0".parse::(), + Ok(Rapid { + major: 93, + minor: 0, + }) + ); + } +} diff --git a/src/version/semver.rs b/src/version/semver.rs new file mode 100644 index 0000000..cc9d3da --- /dev/null +++ b/src/version/semver.rs @@ -0,0 +1,191 @@ +use { + crate::{Rapid, Version}, + serde::{Deserialize, Serialize}, + std::{cmp::Ordering, error::Error, fmt, num::ParseIntError, str::FromStr}, +}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct SemVer { + pub major: u32, + pub minor: u32, + pub patch: u32, +} + +impl fmt::Display for SemVer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} + +impl PartialOrd for SemVer { + fn partial_cmp(&self, other: &Self) -> Option { + match self.major.partial_cmp(&other.major) { + Some(Ordering::Greater) => Some(Ordering::Greater), + Some(Ordering::Less) => Some(Ordering::Less), + None => None, + Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) { + Some(Ordering::Greater) => Some(Ordering::Greater), + Some(Ordering::Less) => Some(Ordering::Less), + None => None, + Some(Ordering::Equal) => self.patch.partial_cmp(&other.patch), + }, + } + } +} + +impl Ord for SemVer { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match self.major.cmp(&other.major) { + Ordering::Greater => Ordering::Greater, + Ordering::Less => Ordering::Less, + Ordering::Equal => match self.minor.cmp(&other.minor) { + Ordering::Greater => Ordering::Greater, + Ordering::Less => Ordering::Less, + Ordering::Equal => self.patch.cmp(&other.patch), + }, + } + } +} + +impl PartialEq for SemVer { + fn eq(&self, other: &Rapid) -> bool { + self.major == other.major && self.minor == other.minor && self.patch == 0 + } +} + +impl PartialOrd for SemVer { + fn partial_cmp(&self, other: &Rapid) -> Option { + match self.major.partial_cmp(&other.major) { + Some(Ordering::Greater) => Some(Ordering::Greater), + Some(Ordering::Less) => Some(Ordering::Less), + None => None, + Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) { + Some(Ordering::Greater) => Some(Ordering::Greater), + Some(Ordering::Less) => Some(Ordering::Less), + None => None, + Some(Ordering::Equal) => match self.patch { + 0 => Some(Ordering::Equal), + _ => Some(Ordering::Greater), + }, + }, + } + } +} + +impl PartialEq for SemVer { + fn eq(&self, other: &u32) -> bool { + self.major == *other && self.minor == 0 && self.patch == 0 + } +} + +impl PartialOrd for SemVer { + fn partial_cmp(&self, other: &u32) -> Option { + match self.major.cmp(other) { + Ordering::Greater => Some(Ordering::Greater), + Ordering::Less => Some(Ordering::Less), + Ordering::Equal => { + if self.minor == 0 && self.patch == 0 { + Some(Ordering::Equal) + } else { + Some(Ordering::Greater) + } + } + } + } +} + +impl PartialEq for u32 { + fn eq(&self, other: &SemVer) -> bool { + other.eq(self) + } +} + +impl PartialOrd for u32 { + fn partial_cmp(&self, other: &SemVer) -> Option { + match other.partial_cmp(self) { + Some(Ordering::Less) => Some(Ordering::Greater), + Some(Ordering::Greater) => Some(Ordering::Less), + Some(Ordering::Equal) => Some(Ordering::Equal), + None => None, + } + } +} + +#[derive(Debug, PartialEq)] +pub struct ParseSemVerError; + +impl fmt::Display for ParseSemVerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error parsing SemVer") + } +} + +impl Error for ParseSemVerError {} + +impl From for ParseSemVerError { + fn from(_value: ParseIntError) -> Self { + Self + } +} + +impl FromStr for SemVer { + type Err = ParseSemVerError; + + fn from_str(s: &str) -> Result { + let split = s.split('.').collect::>(); + match split.len() { + 3 => { + let major = split.first().unwrap().parse::()?; + let minor = split.get(1).unwrap().parse::()?; + let patch = split.get(2).unwrap().parse::()?; + Ok(Self { + major, + minor, + patch, + }) + } + _ => Err(ParseSemVerError), + } + } +} + +impl TryFrom for SemVer { + type Error = ParseSemVerError; + fn try_from(value: Version) -> Result { + match value { + Version::SemVer(s) => Ok(SemVer { + major: s.major, + minor: s.minor, + patch: s.patch, + }), + Version::Rapid(r) => Ok(SemVer { + major: r.major, + minor: r.minor, + patch: 0, + }), + Version::Number(n) => Ok(Self { + major: n, + minor: 0, + patch: 0, + }), + Version::Git(_) => Err(ParseSemVerError), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_semver() { + assert_eq!( + "1.0.3".parse::(), + Ok(SemVer { + major: 1, + minor: 0, + patch: 3 + }) + ); + } +} From ee52bfb462bd5e30685ff6edc274e286c7fde772 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Mon, 3 Apr 2023 18:29:11 -0400 Subject: [PATCH 02/17] Add minitar test files; Remove creator and item modules; --- .gitignore | 2 +- src/creator/mod.rs | 143 --------------------------------------------- src/item/mod.rs | 91 ----------------------------- src/lib.rs | 4 -- src/package/mod.rs | 2 +- test/1.tar | Bin 0 -> 10240 bytes test/1.txt | 1 + test/2.tar | Bin 0 -> 10240 bytes 8 files changed, 3 insertions(+), 240 deletions(-) delete mode 100644 src/creator/mod.rs delete mode 100644 src/item/mod.rs create mode 100644 test/1.tar create mode 100644 test/1.txt create mode 100644 test/2.tar diff --git a/.gitignore b/.gitignore index 83a0302..985a402 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target /Cargo.lock -test +tags diff --git a/src/creator/mod.rs b/src/creator/mod.rs deleted file mode 100644 index e143601..0000000 --- a/src/creator/mod.rs +++ /dev/null @@ -1,143 +0,0 @@ -use { - crate::{Entry, Item, Package, Plist, Specs}, - rayon::prelude::{IntoParallelRefIterator, ParallelIterator}, - std::{ - borrow::BorrowMut, - env, - error::Error, - fs::{self, File}, - io::{self, Write}, - path::{Path, PathBuf}, - sync::{ - atomic::{AtomicUsize, Ordering}, - mpsc::Sender, - Mutex, - }, - }, - walkdir::WalkDir, - zstd::Encoder, -}; - -pub enum Message { - MemberAdded(String), - Success(String), - Failure(String), -} - -pub struct Creator { - path: PathBuf, - entries: Vec, - specs: Specs, -} - -impl Creator { - pub fn new(path: &Path, specs: Specs) -> Result { - let d = env::current_dir()?; - env::set_current_dir(path)?; - let path = path.to_path_buf(); - let entries = WalkDir::new(".") - .into_iter() - .filter(Result::is_ok) - .map(|x| x.unwrap().path().to_path_buf()) - .collect::>(); - env::set_current_dir(d)?; - Ok(Self { - path, - entries, - specs, - }) - } - - pub fn from_list(list: &[&str], specs: Specs) -> Result { - let entries = list.iter().map(|x| Path::new(x).to_path_buf()).collect(); - Ok(Self { - path: env::current_dir().unwrap_or(Path::new("/").to_path_buf()), - entries, - specs, - }) - } - - pub fn len(&self) -> usize { - self.entries.len() - } - - pub fn is_empty(&self) -> bool { - self.entries.is_empty() - } - - pub fn create(self, outdir: &Path, sender: Sender) -> Result<(), Box> { - let d = env::current_dir()?; - let plist = Mutex::new(Plist::default()); - let totalsize: AtomicUsize = 0.into(); - let fullname = format!( - "{}-{}_{}", - &self.specs.name, self.specs.version, self.specs.release - ); - if !outdir.exists() { - fs::create_dir_all(outdir)?; - } - let outdir = outdir.canonicalize()?; - let mut archive = outdir.clone(); - archive.push(&fullname); - archive.set_extension("tar.zst"); - let fd = File::create(&archive)?; - let writer = Mutex::new(Encoder::new(fd, 0)?); - let sender = Mutex::new(sender); - env::set_current_dir(&self.path)?; - self.entries - .par_iter() - .filter(|x| x.as_path() != Path::new(".")) - .for_each(|x| { - let sender = sender.lock().unwrap().clone(); - if let Ok(item) = Item::try_create(x.as_path()) { - if let Entry::File { - path: _, - sha256sum: _, - mode: _, - size, - } = &item.entry - { - totalsize.fetch_add(*size, Ordering::Release); - } - let path = match item.entry.clone() { - Entry::File { - path, - sha256sum: _, - mode: _, - size: _, - } - | Entry::Link { path, target: _ } - | Entry::Directory { path, mode: _ } => path, - }; - plist.lock().unwrap().borrow_mut().entries.push(item.entry); - match writer.lock().unwrap().borrow_mut().write_all(&item.data) { - Ok(_) => sender - .send(Message::MemberAdded(format!("{}", path.display()))) - .expect("couldn't send message"), - Err(e) => sender - .send(Message::Failure(format!("{e}"))) - .expect("couldn't send message"), - } - } else { - sender - .send(Message::Failure(format!( - "Could not process DirEntry for {}", - x.display() - ))) - .expect("could not send message"); - } - }); - let mut package: Package = self.specs.into(); - package.size = totalsize.into_inner(); - let plist = plist.into_inner()?; - package.plist = plist; - let node = package.save_ron_and_create_tar_node(&outdir)?; - let mut writer = writer.into_inner()?; - writer.write_all(&node.to_vec()?)?; - let _fd = writer.finish()?; - let sender = sender.into_inner()?; - sender.send(Message::Success(format!("{} saved", archive.display())))?; - env::set_current_dir(d)?; - Ok(()) - } -} diff --git a/src/item/mod.rs b/src/item/mod.rs deleted file mode 100644 index 6e2c60c..0000000 --- a/src/item/mod.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::Entry; -use sha2::{Digest, Sha256}; -use std::{ - error::Error, - ffi::OsStr, - fmt::Write, - fs, - io::Read, - os::unix::fs::MetadataExt, - path::{Path, PathBuf}, -}; -use crate::tar::{Node, Owner}; - -#[derive(Clone, Debug)] -pub struct Item { - pub entry: Entry, - pub data: Vec, -} - -impl Item { - pub fn try_create(path: &Path) -> Result> { - let path = fix_path(path); - let meta = fs::metadata(&path)?; - let filename = format!("{}", path.display()); - let owner = Some(Owner::default()); - if meta.is_file() { - let mut data = vec![]; - let mut fd = fs::File::open(path)?; - let path = PathBuf::from(&filename); - let size = fd.read_to_end(&mut data)?; - let mut sha256sum = String::new(); - let mut hasher = Sha256::new(); - hasher.update(&data); - let res = hasher.finalize(); - for c in res { - write!(sha256sum, "{c:02x}")?; - } - let mode = meta.mode(); - Ok(Self { - entry: Entry::File { - path, - sha256sum, - mode, - size, - }, - data: Node::read_data_to_tar(&data, &filename, &meta, owner)?.to_vec()?, - }) - } else if meta.is_dir() { - let mode = meta.mode(); - let path = PathBuf::from(&filename); - Ok(Self { - entry: Entry::Directory { path, mode }, - data: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?.to_vec()?, - }) - } else if meta.is_symlink() { - let target = fs::read_link(path)?; - let path = PathBuf::from(&filename); - Ok(Self { - entry: Entry::Link { path, target }, - data: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?.to_vec()?, - }) - } else { - unreachable!(); - } - } -} - -fn fix_path(path: &Path) -> PathBuf { - let path = if let Ok(p) = path.strip_prefix("./") { - p - } else { - path - }; - if path.is_file() || path.is_symlink() { - match path.ancestors().last().and_then(Path::to_str) { - Some("etc" | "var") => { - let ext = if let Some(x) = path.extension().and_then(OsStr::to_str) { - format!("{x}.new") - } else { - "new".to_string() - }; - let mut path = path.to_path_buf(); - path.set_extension(ext); - path - } - _ => path.to_path_buf(), - } - } else { - path.to_path_buf() - } -} diff --git a/src/lib.rs b/src/lib.rs index ea6d0ff..6dd6e1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,9 @@ pub mod tar; -mod creator; -mod item; mod package; mod plist; mod version; pub use { - creator::{Creator, Message}, - item::Item, package::{Dependency, Package, Specs}, plist::*, version::*, diff --git a/src/package/mod.rs b/src/package/mod.rs index 13bde79..c706274 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -85,7 +85,7 @@ impl Package { to_string_pretty(self, cfg) } - pub(crate) fn save_ron_and_create_tar_node( + pub fn save_ron_and_create_tar_node( &self, outdir: &Path, ) -> Result> { diff --git a/test/1.tar b/test/1.tar new file mode 100644 index 0000000000000000000000000000000000000000..ef16843f999abab1a79121ce3b1c15b6308756c6 GIT binary patch literal 10240 zcmeIu!3x7541i(Jdx|_kNhEbIvKQ#VZH3W7wXnyZrT5-?DEosDHKOseu6}DcH?&1^ z4=vff*Db5<>ustU`(WO-Vv4Sg}64`TZm#sB~S literal 0 HcmV?d00001 diff --git a/test/1.txt b/test/1.txt new file mode 100644 index 0000000..6de7b8c --- /dev/null +++ b/test/1.txt @@ -0,0 +1 @@ +This is a test file. diff --git a/test/2.tar b/test/2.tar new file mode 100644 index 0000000000000000000000000000000000000000..3e7ac15362f5829b5622aa8d1698d0ed7cab964b GIT binary patch literal 10240 zcmeIw!3o1K3_wwxJB24mC09uo=>k5aBv5jwRp|J2Z(bmWkS_!h2#obm%r&ky#5ZKO z&=!-sv|{&B`}!ZL=26Vuvg<9|BxK^{oDDRYHE3uTV8=RwkENe|IONw?0 z>AAk<;c|}E*%AQ+5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** I5ID5J58Ct@SO5S3 literal 0 HcmV?d00001 From 218abdaa87835d436aff084868ca61e657b55958 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Mon, 3 Apr 2023 18:36:54 -0400 Subject: [PATCH 03/17] Adjust re-exports --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 6dd6e1f..dd68754 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod plist; mod version; pub use { + deku, package::{Dependency, Package, Specs}, plist::*, version::*, From 90b163eb1b7373085d3c5eb93919eb8ecfedb219 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Mon, 3 Apr 2023 18:43:58 -0400 Subject: [PATCH 04/17] Add deku, ron and sha2 to re-exports --- Cargo.toml | 1 - src/lib.rs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 19b8704..648bf57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ rayon = "1.7" ron = "0.8" sha2 = "0.10" walkdir = "2.3" -zstd = "0.12" thiserror = "1.0" libc = "0.2" diff --git a/src/lib.rs b/src/lib.rs index dd68754..084113d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,5 +7,7 @@ pub use { deku, package::{Dependency, Package, Specs}, plist::*, + ron, + sha2, version::*, }; From a58150cc9ca982d3b45375ce6963300cac24d24d Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Tue, 4 Apr 2023 20:45:43 -0400 Subject: [PATCH 05/17] Add `arch` field to `Package` --- src/package/arch.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++ src/package/mod.rs | 7 ++++-- 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/package/arch.rs diff --git a/src/package/arch.rs b/src/package/arch.rs new file mode 100644 index 0000000..cde2d3c --- /dev/null +++ b/src/package/arch.rs @@ -0,0 +1,56 @@ +use serde::{Deserialize, Serialize}; +use std::{fmt, str::FromStr, error::Error}; + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)] +#[allow(non_camel_case_types)] +pub enum Arch { + armv7l, + aarch64, + i486, + i586, + i686, + riscv64, + #[default] + x86_64, +} + +impl fmt::Display for Arch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", match self { + Self::armv7l => "armv7l", + Self::aarch64 => "aarch64", + Self::i486 => "i486", + Self::i586 => "i586", + Self::i686 => "i686", + Self::riscv64 => "riscv64", + Self::x86_64 => "x86_64", + }) + } +} + +#[derive(Debug)] +pub struct ParseArchError; + +impl fmt::Display for ParseArchError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "error parsing architecture") + } +} + +impl Error for ParseArchError {} + +impl FromStr for Arch { + type Err = ParseArchError; + + fn from_str(s: &str) -> Result { + match s { + "i486" | "x86" => Ok(Self::i486), + "i586" => Ok(Self::i586), + "i686" => Ok(Self::i686), + "armv7l" | "arm" => Ok(Self::armv7l), + "arm64" | "aarch64" | "armv8" => Ok(Self::aarch64), + "riscv" | "riscv64" => Ok(Self::riscv64), + _ => Err(ParseArchError) + } + } +} diff --git a/src/package/mod.rs b/src/package/mod.rs index c706274..06e5ae3 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -1,3 +1,4 @@ +mod arch; mod dependency; mod specs; @@ -15,7 +16,7 @@ use { crate::tar::{Node, Owner}, }; -pub use {dependency::Dependency, specs::Specs}; +pub use {arch::Arch, dependency::Dependency, specs::Specs}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct User { @@ -36,6 +37,8 @@ pub struct Package { pub name: String, /// The `Version` of the package pub version: Version, + /// The architecture the package is built for + pub arch: Arch, /// The release number for this package pub release: u8, /// a single line description of the package @@ -110,7 +113,7 @@ impl Package { /// Returns the formatted full package name including version and release strings pub fn fullname(&self) -> String { - format!("{}-{}_{}", self.name, self.version, self.release) + format!("{}-{}_{}{}", self.name, self.version, self.arch, self.release) } /// Tests whether this package is an update for another From 7d6376d5c4e18f76ee7634d045466d05bac50753 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Tue, 4 Apr 2023 21:52:29 -0400 Subject: [PATCH 06/17] Store target arch as `HOST_ARCH` const, use this as default value for `Package` struct --- src/package/arch.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/package/arch.rs b/src/package/arch.rs index cde2d3c..e453f27 100644 --- a/src/package/arch.rs +++ b/src/package/arch.rs @@ -1,7 +1,18 @@ use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr, error::Error}; -#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)] +#[cfg(target_arch = "arm")] +pub const HOST_ARCH: Arch = Arch::armv7l; +#[cfg(target_arch = "aarch64")] +pub const HOST_ARCH: Arch = Arch::aarch64; +#[cfg(target_arch = "x86")] +pub const HOST_ARCH: Arch = Arch::i486; +#[cfg(target_arch = "riscv64")] +pub const HOST_ARCH: Arch = Arch::riscv64; +#[cfg(target_arch = "x86_64")] +pub const HOST_ARCH: Arch = Arch::x86_64; + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] #[allow(non_camel_case_types)] pub enum Arch { armv7l, @@ -10,10 +21,15 @@ pub enum Arch { i586, i686, riscv64, - #[default] x86_64, } +impl Default for Arch { + fn default() -> Self { + HOST_ARCH + } +} + impl fmt::Display for Arch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", match self { From 9cf9d469f644c43ee2c2d9ca3b16e669c2def944 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Tue, 4 Apr 2023 22:02:58 -0400 Subject: [PATCH 07/17] Add `any` arch; Add `archive_name` method for `Package`; Ensure architecture matches when checking if a package is an upgrade; --- src/package/arch.rs | 3 +++ src/package/mod.rs | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/package/arch.rs b/src/package/arch.rs index e453f27..5f10d1f 100644 --- a/src/package/arch.rs +++ b/src/package/arch.rs @@ -22,6 +22,7 @@ pub enum Arch { i686, riscv64, x86_64, + any, } impl Default for Arch { @@ -40,6 +41,7 @@ impl fmt::Display for Arch { Self::i686 => "i686", Self::riscv64 => "riscv64", Self::x86_64 => "x86_64", + Self::any => "any", }) } } @@ -66,6 +68,7 @@ impl FromStr for Arch { "armv7l" | "arm" => Ok(Self::armv7l), "arm64" | "aarch64" | "armv8" => Ok(Self::aarch64), "riscv" | "riscv64" => Ok(Self::riscv64), + "any" => Ok(Self::any), _ => Err(ParseArchError) } } diff --git a/src/package/mod.rs b/src/package/mod.rs index 06e5ae3..ffe2a7d 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -113,12 +113,18 @@ impl Package { /// Returns the formatted full package name including version and release strings pub fn fullname(&self) -> String { - format!("{}-{}_{}{}", self.name, self.version, self.arch, self.release) + format!("{}-{}_{}_{}", self.name, self.version, self.release, self.arch) + } + + /// Returns the name of the package archive + pub fn archive_name(&self) -> String { + format!("{}-{}_{}_{}.tar.zstd", self.name, self.version, self.release, self.arch) } /// Tests whether this package is an update for another pub fn is_upgrade(&self, other: &Self) -> bool { - self.name == other.name + self.arch == other.arch + && self.name == other.name && (self.version > other.version || (self.version == other.version && self.release > other.release)) } From 723d697e9085a575c1f6d59baf1e79819452d747 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Thu, 6 Apr 2023 18:31:52 -0400 Subject: [PATCH 08/17] Export `Arch` enum --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 084113d..a8b28de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ mod version; pub use { deku, - package::{Dependency, Package, Specs}, + package::{Arch, Dependency, Package, Specs}, plist::*, ron, sha2, From 0ab84def2e5e7ac6253c6b5f1ac20ecbf0fe70f8 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Fri, 7 Apr 2023 19:08:23 -0400 Subject: [PATCH 09/17] Make `package::User` and `package::Group` public --- src/lib.rs | 7 +++---- src/package/arch.rs | 30 +++++++++++++++++------------- src/package/mod.rs | 17 ++++++++++------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a8b28de..3381f56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,12 @@ -pub mod tar; mod package; mod plist; +pub mod tar; mod version; pub use { deku, - package::{Arch, Dependency, Package, Specs}, + package::{Arch, Dependency, Package, Specs, User, Group}, plist::*, - ron, - sha2, + ron, sha2, version::*, }; diff --git a/src/package/arch.rs b/src/package/arch.rs index 5f10d1f..59f1c01 100644 --- a/src/package/arch.rs +++ b/src/package/arch.rs @@ -1,10 +1,10 @@ use serde::{Deserialize, Serialize}; -use std::{fmt, str::FromStr, error::Error}; +use std::{error::Error, fmt, str::FromStr}; #[cfg(target_arch = "arm")] pub const HOST_ARCH: Arch = Arch::armv7l; #[cfg(target_arch = "aarch64")] -pub const HOST_ARCH: Arch = Arch::aarch64; +pub const HOST_ARCH: Arch = Arch::aarch64; #[cfg(target_arch = "x86")] pub const HOST_ARCH: Arch = Arch::i486; #[cfg(target_arch = "riscv64")] @@ -33,16 +33,20 @@ impl Default for Arch { impl fmt::Display for Arch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", match self { - Self::armv7l => "armv7l", - Self::aarch64 => "aarch64", - Self::i486 => "i486", - Self::i586 => "i586", - Self::i686 => "i686", - Self::riscv64 => "riscv64", - Self::x86_64 => "x86_64", - Self::any => "any", - }) + write!( + f, + "{}", + match self { + Self::armv7l => "armv7l", + Self::aarch64 => "aarch64", + Self::i486 => "i486", + Self::i586 => "i586", + Self::i686 => "i686", + Self::riscv64 => "riscv64", + Self::x86_64 => "x86_64", + Self::any => "any", + } + ) } } @@ -69,7 +73,7 @@ impl FromStr for Arch { "arm64" | "aarch64" | "armv8" => Ok(Self::aarch64), "riscv" | "riscv64" => Ok(Self::riscv64), "any" => Ok(Self::any), - _ => Err(ParseArchError) + _ => Err(ParseArchError), } } } diff --git a/src/package/mod.rs b/src/package/mod.rs index ffe2a7d..0020cd9 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -3,6 +3,7 @@ mod dependency; mod specs; use { + crate::tar::{Node, Owner}, crate::{Plist, Version}, ron::ser::{to_string_pretty, PrettyConfig}, serde::{Deserialize, Serialize}, @@ -13,7 +14,6 @@ use { io::{BufWriter, Write}, path::Path, }, - crate::tar::{Node, Owner}, }; pub use {arch::Arch, dependency::Dependency, specs::Specs}; @@ -88,10 +88,7 @@ impl Package { to_string_pretty(self, cfg) } - pub fn save_ron_and_create_tar_node( - &self, - outdir: &Path, - ) -> Result> { + pub fn save_ron_and_create_tar_node(&self, outdir: &Path) -> Result> { if !outdir.exists() { fs::create_dir_all(outdir)?; } @@ -113,12 +110,18 @@ impl Package { /// Returns the formatted full package name including version and release strings pub fn fullname(&self) -> String { - format!("{}-{}_{}_{}", self.name, self.version, self.release, self.arch) + format!( + "{}-{}_{}_{}", + self.name, self.version, self.release, self.arch + ) } /// Returns the name of the package archive pub fn archive_name(&self) -> String { - format!("{}-{}_{}_{}.tar.zstd", self.name, self.version, self.release, self.arch) + format!( + "{}-{}_{}_{}.tar.zstd", + self.name, self.version, self.release, self.arch + ) } /// Tests whether this package is an update for another From acbdf2d99284b69a48601f3aeb0a56f295872a4c Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Fri, 7 Apr 2023 19:11:35 -0400 Subject: [PATCH 10/17] Cargo fmt --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3381f56..fb56ef0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ mod version; pub use { deku, - package::{Arch, Dependency, Package, Specs, User, Group}, + package::{Arch, Dependency, Group, Package, Specs, User}, plist::*, ron, sha2, version::*, From ae099d94aa2b7839caba7f90645234305ce56660 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Sat, 8 Apr 2023 11:44:09 -0400 Subject: [PATCH 11/17] Fix Header::filename() method; TODO: fix other related methods, fix Archive::get() method. --- src/tar/header.rs | 20 ++++++++++++-- src/tar/mod.rs | 67 ++++++++++++++++++++++++++++++++++++---------- src/tar/node.rs | 3 ++- test/2.tar | Bin 10240 -> 10240 bytes 4 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/tar/header.rs b/src/tar/header.rs index 6e22b78..bfc363a 100644 --- a/src/tar/header.rs +++ b/src/tar/header.rs @@ -117,11 +117,21 @@ impl Default for Header { } impl Header { + /// Get the filename of this archive member + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Header; + /// + /// let header = Header::new("test/1.txt").unwrap(); + /// assert_eq!(header.filename().unwrap(), "test/1.txt"); + /// ``` pub fn filename(&self) -> Result { let mut s = String::new(); for c in self.fname { - if c != b'\0' { - write!(s, "{c}")?; + if c != 0 { + write!(s, "{}", char::from(c))?; } else { break; } @@ -217,6 +227,12 @@ impl Header { Ok(s) } + pub fn path(&self) -> Result { + let mut p = PathBuf::from(&self.prefix()?); + p.push(&self.filename()?); + Ok(p) + } + pub fn new(filename: &str) -> Result { let mut header = Header::default(); let meta = fs::symlink_metadata(filename)?; diff --git a/src/tar/mod.rs b/src/tar/mod.rs index c85010c..a84f80a 100644 --- a/src/tar/mod.rs +++ b/src/tar/mod.rs @@ -3,6 +3,8 @@ use std::{ io::{self, BufReader, Write}, }; +use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; + mod error; mod header; mod node; @@ -27,7 +29,8 @@ impl Archive { Ok(buf) } - /// Write out a vector of `TarNodes` to a file or something that implements ``std::io::Write`` and ``std::io::Copy``. + /// Write out a vector of `TarNodes` to a file or something that implements + /// ``std::io::Write`` and ``std::io::Copy``. /// /// # Example /// @@ -55,8 +58,8 @@ impl Archive { Ok(written) } - /// Create a new `TarFile` struct and initialize it with a `filename` file. This will read in the file to - /// the `TarFile` struct as a `TarNode`. + /// Create a new `TarFile` struct and initialize it with a `filename` file. + /// This will read in the file to the `TarFile` struct as a `TarNode`. /// /// # Example /// @@ -71,7 +74,8 @@ impl Archive { }) } - /// Append another file to the `TarFile.file` vector. This adds a file to the internal representation of the tar file. + /// Append another file to the `TarFile.file` vector. This adds a file to the + /// internal representation of the tar file. /// /// # Example /// @@ -87,27 +91,41 @@ impl Archive { Ok(()) } - /// Open and load an external tar file into the internal `TarFile` struct. This parses and loads up all the files - /// contained within the external tar file. + /// Open and load an external tar file into the internal `TarFile` struct. This + /// parses and loads up all the files contained within the external tar file. /// /// # Example /// /// ``` /// use hpk_package::tar::Archive; /// - /// Archive::open("test/1.tar".to_string()).unwrap(); + /// Archive::open("test/1.tar").unwrap(); /// ``` - pub fn open(filename: String) -> Result { - let file = File::open(&filename)?; + pub fn open(filename: &str) -> Result { + let file = File::open(filename)?; let mut reader = BufReader::new(file); + Self::read(&mut reader) + } + + /// Read a tar archive from anything which implements `io::Read`. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Archive; + /// use std::{fs::File, io::BufReader}; + /// + /// let file = File::open("test/1.tar").unwrap(); + /// let mut reader = BufReader::new(file); + /// Archive::read(&mut reader).unwrap(); + /// ``` + pub fn read(mut input: T) -> Result { let mut out = Self { nodes: Vec::::new(), }; - - while let Ok(t) = Node::read(&mut reader) { + while let Ok(t) = Node::read(&mut input) { out.nodes.push(t); } - Ok(out) } @@ -119,9 +137,9 @@ impl Archive { /// use hpk_package::tar::Archive; /// /// let mut data = Archive::new("test/1.tar").unwrap(); - /// data.remove("test/1.tar".to_string()).unwrap(); + /// data.remove("test/1.tar").unwrap(); /// ``` - pub fn remove(&mut self, filename: String) -> Result { + pub fn remove(&mut self, filename: &str) -> Result { let mut name = [0u8; 100]; name[..filename.len()].copy_from_slice(filename.as_bytes()); if let Some(i) = &self.nodes.iter().position(|x| x.header.fname == name) { @@ -131,4 +149,25 @@ impl Archive { Ok(false) } + + /// Get the first node from the archive that matches the given filename. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Archive; + /// let archive = Archive::new("test/1.tar").unwrap(); + /// let node = archive.get("1.txt"); + /// assert!(node.is_some()); + /// ``` + pub fn get(&self, filename: &str) -> Option { + self.nodes.par_iter().find_any(|x| { + if let Ok(name) = x.header.filename() { + if &name == filename { + return true; + } + } + false + }).cloned() + } } diff --git a/src/tar/node.rs b/src/tar/node.rs index eac092c..6d31e14 100644 --- a/src/tar/node.rs +++ b/src/tar/node.rs @@ -21,7 +21,8 @@ impl Node { Ok(buf) } - /// Write out a single file within the tar to a file or something with a ``std::io::Write`` trait. + /// Write out a single file within the tar to a file or something with a + /// ``std::io::Write`` trait. pub fn write(self, mut input: T) -> Result { input.write_all(&self.header.to_bytes()?)?; let mut written = 512; diff --git a/test/2.tar b/test/2.tar index 3e7ac15362f5829b5622aa8d1698d0ed7cab964b..bc84f02484222b4020cc8cdd5186e129570ce99c 100644 GIT binary patch delta 48 zcmZn&Xb70lBWP@3W@cbw%wS+>U}|K-pkOdrkTHd)w74X(NI`)iFR>(bvLe&xMNDb{ DJ75h@ delta 50 zcmZn&Xb70lBWPq|U~FV)#$aG*XkcQ*pkOdrkTHd?w74X(NI`)iFR>&eF>kUW)5k?j FY5-Rd4jBLd From 32b4f80715faef74d98e20875869de75262db1cc Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Sat, 8 Apr 2023 19:10:30 -0400 Subject: [PATCH 12/17] Fix some issues with getting tar header fields; Add some doc tests in tar module; Add some tests for version checks; --- src/tar/header.rs | 142 +++++++++++++++++++++++++++---------------- src/version/rapid.rs | 26 ++++++++ test/2.tar | Bin 10240 -> 10240 bytes 3 files changed, 117 insertions(+), 51 deletions(-) diff --git a/src/tar/header.rs b/src/tar/header.rs index 6e22b78..6c669c2 100644 --- a/src/tar/header.rs +++ b/src/tar/header.rs @@ -117,25 +117,45 @@ impl Default for Header { } impl Header { + /// Gets the filename of this archive member + /// + /// # Example + /// ``` + /// use hpk_package::tar::Header; + /// + /// let header = Header::new("test/1.txt").unwrap(); + /// let filename = header.filename().unwrap(); + /// assert_eq!(filename.as_str(), "1.txt"); + /// ``` pub fn filename(&self) -> Result { let mut s = String::new(); for c in self.fname { - if c != b'\0' { - write!(s, "{c}")?; - } else { + if c == 0 { break; + } else { + write!(s, "{}", char::from(c))?; } } Ok(s) } + /// Gets the Unix mode of this archive member + /// + /// # Example + /// ``` + /// use hpk_package::tar::Header; + /// + /// let header = Header::new("test/1.txt").unwrap(); + /// let mode = header.mode().unwrap(); + /// assert_eq!(mode, 420); + /// ``` pub fn mode(&self) -> Result { let mut s = String::new(); for c in self.mode { - if c != b'\0' { - write!(s, "{c}")?; - } else { + if c == 0 { break; + } else { + write!(s, "{}", char::from(c))?; } } let mode = u32::from_str_radix(&s, 8)?; @@ -145,10 +165,10 @@ impl Header { fn uid(&self) -> Result { let mut s = String::new(); for c in self.mode { - if c != b'\0' { - write!(s, "{c}")?; - } else { + if c == 0 { break; + } else { + write!(s, "{}", char::from(c))?; } } let uid = u32::from_str_radix(&s, 8)?; @@ -158,10 +178,10 @@ impl Header { fn gid(&self) -> Result { let mut s = String::new(); for c in self.mode { - if c != b'\0' { - write!(s, "{c}")?; - } else { + if c == 0 { break; + } else { + write!(s, "{}", char::from(c))?; } } let gid = u32::from_str_radix(&s, 8)?; @@ -171,10 +191,10 @@ impl Header { fn username(&self) -> Result { let mut s = String::new(); for c in self.username { - if c != b'\0' { - write!(s, "{c}")?; - } else { + if c == 0 { break; + } else { + write!(s, "{}", char::from(c))?; } } Ok(s) @@ -183,10 +203,10 @@ impl Header { fn groupname(&self) -> Result { let mut s = String::new(); for c in self.groupname { - if c != b'\0' { - write!(s, "{c}")?; - } else { + if c == 0 { break; + } else { + write!(s, "{}", char::from(c))?; } } Ok(s) @@ -205,25 +225,61 @@ impl Header { }) } - pub fn prefix(&self) -> Result { + /// Gets the path to the file minus it's final component + /// + /// # Example + /// ``` + /// use hpk_package::tar::Header; + /// + /// let header = Header::new("test/1.txt").unwrap(); + /// let prefix = header.prefix().unwrap(); + /// assert_eq!(prefix.as_str(), "test"); + /// ``` + pub fn prefix(&self) -> Option { let mut s = String::new(); for c in self.file_prefix { - if c != b'\0' { - write!(s, "{c}")?; + if c != 0 { + write!(s, "{}", char::from(c)).ok()?; } else { break; } } - Ok(s) + if s.is_empty() { + None + } else { + Some(s) + } + } + + /// Gets the full file path to this archive member. + /// + /// # Example + /// + /// ``` + /// use hpk_package::tar::Header; + /// use std::path::PathBuf; + /// + /// let header = Header::new("test/1.txt").unwrap(); + /// let path = header.file_path().unwrap(); + /// assert_eq!(PathBuf::from("test/1.txt"), path); + /// ``` + pub fn file_path(&self) -> Result { + let mut path = match self.prefix() { + Some(p) => PathBuf::from(&p), + None => PathBuf::new(), + }; + let name = self.filename()?; + path.push(&name); + Ok(path) } pub fn new(filename: &str) -> Result { let mut header = Header::default(); let meta = fs::symlink_metadata(filename)?; - let (filename, prefix) = if filename.len() < 100 { - (filename.to_string(), None) - } else { - // Deal with file names longer than 100 bytes + let (filename, prefix) = { + // Original tar has a maximum file name length of 100 bytes. The ustar + // revision allows storing the path prefix separately, with 100 bytes + // reserved for the file name and 150 bytes for the rest of the path. let path = PathBuf::from(&filename); let name = match path.file_name().and_then(|n| n.to_str()) { Some(n) => n.to_string(), @@ -234,16 +290,8 @@ impl Header { ))) } }; - let dir = match path.parent() { - Some(d) => d, - None => { - return Err(Error::Io(io::Error::new( - io::ErrorKind::Other, - "Cannot get path prefix", - ))) - } - }; - (name, Some(format!("{}", dir.display()))) + let dir = path.parent().map(|x| format!("{}", x.display())); + (name, dir) }; /* Fill in metadata */ @@ -293,10 +341,10 @@ impl Header { owner: Option, ) -> Result { let mut header = Header::default(); - let (filename, prefix) = if filename.len() < 100 { - (filename.to_string(), None) - } else { - // Deal with file names longer than 100 bytes + let (filename, prefix) = { + // Original tar has a maximum file name length of 100 bytes. The ustar + // revision allows storing the path prefix separately, with 100 bytes + // reserved for the file name and 150 bytes for the rest of the path. let path = PathBuf::from(&filename); let name = match path.file_name().and_then(|n| n.to_str()) { Some(n) => n.to_string(), @@ -307,16 +355,8 @@ impl Header { ))) } }; - let dir = match path.parent() { - Some(d) => d, - None => { - return Err(Error::Io(io::Error::new( - io::ErrorKind::Other, - "Cannot get path prefix", - ))) - } - }; - (name, Some(format!("{}", dir.display()))) + let dir = path.parent().map(|x| format!("{}", x.display())); + (name, dir) }; header.fname[..filename.len()].copy_from_slice(filename.as_bytes()); let mode = format!("{:07o}", meta.st_mode()); @@ -431,7 +471,7 @@ fn get_username_for_uid<'a>(uid: u32) -> Result<&'a str, std::str::Utf8Error> { user.to_str() } -pub fn get_groupname_for_gid<'a>(gid: u32) -> Result<&'a str, std::str::Utf8Error> { +fn get_groupname_for_gid<'a>(gid: u32) -> Result<&'a str, std::str::Utf8Error> { let group = unsafe { let gr = libc::getgrgid(gid); let name = (*gr).gr_name; diff --git a/src/version/rapid.rs b/src/version/rapid.rs index 0be2946..e7842fa 100644 --- a/src/version/rapid.rs +++ b/src/version/rapid.rs @@ -165,4 +165,30 @@ mod test { }) ); } + + #[test] + fn rapid_num_eq() { + let rapid = Rapid { major: 42, minor: 0 }; + assert_eq!(rapid, 42); + } + + #[test] + fn rapid_num_gt() { + let rapid = Rapid { major: 1, minor: 42 }; + assert!(rapid > 1); + } + + #[test] + fn rapid_semver_eq() { + let rapid = Rapid { major: 42, minor: 69 }; + let semver = SemVer { major: 42, minor: 69, patch: 0 }; + assert_eq!(rapid, semver); + } + + #[test] + fn rapid_semver_lt() { + let rapid = Rapid { major: 42, minor: 69 }; + let semver = SemVer { major: 42, minor: 69, patch: 1 }; + assert!(rapid < semver); + } } diff --git a/test/2.tar b/test/2.tar index 3e7ac15362f5829b5622aa8d1698d0ed7cab964b..98902ce9c96775fa2ed4c0b8194684c56b3bd9ca 100644 GIT binary patch delta 44 ycmZn&Xb506)GMhdnYb{1Vu}&Ffw`G6gMz_iLB^EHjEoU1C8@ Date: Sun, 9 Apr 2023 01:38:39 -0400 Subject: [PATCH 13/17] Remove stale backup of version::mod.rs --- src/version/mod.rs.bak | 206 ----------------------------------------- 1 file changed, 206 deletions(-) delete mode 100644 src/version/mod.rs.bak diff --git a/src/version/mod.rs.bak b/src/version/mod.rs.bak deleted file mode 100644 index 9208624..0000000 --- a/src/version/mod.rs.bak +++ /dev/null @@ -1,206 +0,0 @@ -use { - chrono::{offset::Utc, DateTime}, - serde::{Deserialize, Serialize}, - std::{error::Error, fmt, str::FromStr}, -}; - -mod gitrev; -mod rapid; -mod semver; - -pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer}; - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub enum Version { - Number(u32), - Rapid { - major: u32, - minor: u32, - }, - SemVer { - major: u32, - minor: u32, - patch: u32, - }, - Git { - hash: String, - datetime: DateTime, - }, -} - -impl Default for Version { - fn default() -> Self { - Self::SemVer { - major: 0, - minor: 1, - patch: 0, - } - } -} - -impl fmt::Display for Version { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Number(n) => write!(f, "{n}"), - Self::SemVer { - major, - minor, - patch, - } => { - let v = SemVer { - major: *major, - minor: *minor, - patch: *patch, - }; - write!(f, "{v}") - } - Self::Rapid { major, minor } => { - let v = Rapid { - major: *major, - minor: *minor, - }; - write!(f, "{v}") - } - Self::Git { hash, datetime } => { - let v = GitRev { - hash: hash.clone(), - datetime: *datetime, - }; - write!(f, "{v}") - } - } - } -} - -impl From for Version { - fn from(value: SemVer) -> Self { - Self::SemVer { - major: value.major, - minor: value.minor, - patch: value.patch, - } - } -} - -impl From for Version { - fn from(value: Rapid) -> Self { - Self::Rapid { - major: value.major, - minor: value.minor, - } - } -} - -impl From for Version { - fn from(value: GitRev) -> Self { - Self::Git { - hash: value.hash, - datetime: value.datetime, - } - } -} - -impl From for Version { - fn from(value: u32) -> Self { - Self::Number(value) - } -} - -impl PartialOrd for Version { - #[allow(clippy::many_single_char_names)] - fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (Self::Number(s), Self::Number(o)) => s.partial_cmp(o), - ( - Self::SemVer { - major, - minor, - patch, - }, - Self::SemVer { - major: a, - minor: b, - patch: c, - }, - ) => (major, minor, patch).partial_cmp(&(a, b, c)), - (Self::Rapid { major, minor }, Self::Rapid { major: a, minor: b }) => { - (major, minor).partial_cmp(&(a, b)) - } - ( - Self::Git { - hash: _a, - datetime: b, - }, - Self::Git { - hash: _c, - datetime: d, - }, - ) => b.partial_cmp(&d), - ( - Self::SemVer { - major, - minor, - patch, - }, - Self::Rapid { major: a, minor: b }, - ) => SemVer { - major: *major, - minor: *minor, - patch: *patch, - } - .partial_cmp(&Rapid { - major: *a, - minor: *b, - }), - _ => None, - } - } -} - -#[derive(Debug)] -pub struct ParseVersionError; - -impl fmt::Display for ParseVersionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "error parsing version") - } -} - -impl Error for ParseVersionError {} - -impl FromStr for Version { - type Err = ParseVersionError; - - fn from_str(s: &str) -> Result { - if let Ok(v) = s.parse::() { - Ok(v.into()) - } else if let Ok(v) = s.parse::() { - Ok(v.into()) - } else if let Ok(v) = s.parse::() { - Ok(v.into()) - } else if let Ok(v) = s.parse::() { - Ok(v.into()) - } else { - Err(ParseVersionError) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn cmp_semver_rapid_gt() { - let sem = "1.42.1".parse::().unwrap(); - let rpd = "1.42".parse::().unwrap(); - assert!(sem > rpd); - } - - #[test] - fn cmp_semver_rapid_eq() { - let sem = "1.42.0".parse::().unwrap(); - let rpd = "1.42".parse::().unwrap(); - assert!(sem == rpd); - } -} From 89d36281e31691cf25905eeb6452eff41538cdee Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Sun, 9 Apr 2023 19:14:16 -0400 Subject: [PATCH 14/17] Added some testing for version comparison and implemented PartialEq for Version manually --- src/package/dependency.rs | 7 ++++++ src/version/gitrev.rs | 22 +++++++++++++++++ src/version/mod.rs | 49 +++++++++++++++++++++++++++++++++++++- src/version/semver.rs | 14 +++++++++++ test/2.tar | Bin 10240 -> 10240 bytes 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/package/dependency.rs b/src/package/dependency.rs index 64cbc29..a9e99a8 100644 --- a/src/package/dependency.rs +++ b/src/package/dependency.rs @@ -5,13 +5,20 @@ use { }; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +/// Specifies a dependency requirement pub struct Dependency { + /// The name of the dependency. All packages must have a unique name. pub name: String, + /// The version requirements for this dependency. If the low + /// version is `Some`, then the version must be equal to or + /// greater than this version. If the high version is `Some`, + /// then the version must be less than this version. pub version: (Option, Option), } impl Dependency { #[allow(clippy::must_use_candidate)] + /// Checks whether a package satisfies a given dependency pub fn satisfied(&self, package: &Package) -> bool { if self.name.as_str() == package.name.as_str() { match &self.version { diff --git a/src/version/gitrev.rs b/src/version/gitrev.rs index 4b6ef38..b40331f 100644 --- a/src/version/gitrev.rs +++ b/src/version/gitrev.rs @@ -6,6 +6,7 @@ use { }; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// Represents a Git revision pub struct GitRev { /// the short revision hash pub hash: String, @@ -77,3 +78,24 @@ impl TryFrom for GitRev { } } } + +#[cfg(test)] +mod test { + use std::{thread, time::Duration}; + + use super::*; + + #[test] + fn ord() { + let a = GitRev { + hash: "aaab".to_string(), + datetime: Utc::now(), + }; + thread::sleep(Duration::from_millis(10)); + let b = GitRev { + hash: "aaaa".to_string(), + datetime: Utc::now(), + }; + assert!(a < b); + } +} diff --git a/src/version/mod.rs b/src/version/mod.rs index 06b8057..903b117 100644 --- a/src/version/mod.rs +++ b/src/version/mod.rs @@ -9,11 +9,25 @@ mod semver; pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer}; -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize)] +/// An enum representing the most common versioning schemes. +/// Each scheme must implement Eq and Ord with itself, and +/// may optionally implement PartialEq and PartialOrd with +/// other schemes. Number, Rapid, and SemVer do this, while +/// Git can only compare with itself. pub enum Version { + /// A single replease number, as in Firefox 102 Number(u32), + /// Rapid versioning consists of two numbers separated by + /// a dot (.) character, and is used notably by Gnome Rapid(Rapid), + /// SemVer is the most common versioning scheme and consists + /// of three dot (.) separated numbers. The numbers are major, + /// minor, and patch respctively. SemVer(SemVer), + /// A git revision. Use of this scheme is to be avoided in + /// official packaging as it cannot be readily compared with + /// other versioning schemes for dependency resolution. Git(GitRev), } @@ -81,6 +95,25 @@ impl PartialOrd for Version { } } +impl PartialEq for Version { + #[allow(clippy::many_single_char_names)] + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Number(s), Self::Number(o)) => s.eq(o), + (Self::Number(s), Self::Rapid(o)) => s.eq(o), + (Self::Number(s), Self::SemVer(o)) => s.eq(o), + (Self::Rapid(s), Self::Number(o)) => s.eq(o), + (Self::Rapid(s), Self::Rapid(o)) => s.eq(o), + (Self::Rapid(s), Self::SemVer(o)) => s.eq(o), + (Self::SemVer(s), Self::Number(o)) => s.eq(o), + (Self::SemVer(s), Self::Rapid(o)) => s.eq(o), + (Self::SemVer(s), Self::SemVer(o)) => s.eq(o), + (Self::Git(s), Self::Git(o)) => s.eq(o), + _ => false, + } + } +} + #[derive(Debug)] pub struct ParseVersionError; @@ -127,4 +160,18 @@ mod test { let rpd = "1.42".parse::().unwrap(); assert!(sem == rpd); } + + #[test] + fn cmp_semver_num_gt() { + let sem = Version::SemVer("42.69.0".parse().unwrap()); + let num = Version::Number(42); + assert!(sem > num); + } + + #[test] + fn cmp_semver_num_eq() { + let sem = Version::SemVer("42.0.0".parse().unwrap()); + let num = Version::Number(42); + assert_eq!(sem, num); + } } diff --git a/src/version/semver.rs b/src/version/semver.rs index cc9d3da..89758ec 100644 --- a/src/version/semver.rs +++ b/src/version/semver.rs @@ -188,4 +188,18 @@ mod test { }) ); } + + #[test] + fn cmp_semver_num_gt() { + let sem = "42.69.0".parse::().unwrap(); + let num = 42; + assert!(sem > num); + } + + #[test] + fn cmp_semver_num_eq() { + let sem = "42.0.0".parse::().unwrap(); + let num = 42; + assert_eq!(sem, num); + } } diff --git a/test/2.tar b/test/2.tar index 7593697876d7836908460e72aaad1e8d22c31bbd..98902ce9c96775fa2ed4c0b8194684c56b3bd9ca 100644 GIT binary patch delta 51 zcmZn&Xb70lBWPq|U~FV)#$aG*U~Xp2pkOdLkuim@w74X(NI`)iFR>&eF>j)v*XBsZ GU}9p*pkOdLkuim*w74X(NI`)iFR>(bqM*;_NXF%A E08nNP_5c6? From 43d8c82c08512aef84014cb4e4a730667b5a6bfa Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Mon, 10 Apr 2023 18:41:23 -0400 Subject: [PATCH 15/17] Remove appstream from Specs and Package structs --- src/package/mod.rs | 5 ----- src/package/specs.rs | 4 ---- 2 files changed, 9 deletions(-) diff --git a/src/package/mod.rs b/src/package/mod.rs index 0020cd9..bcef93f 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -45,10 +45,6 @@ pub struct Package { pub description: String, /// a more verbose description of the package pub long_description: String, - /// an optional link to an - /// [AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/) - /// metadata file - pub appstream_data: Option, /// a listing of all files, directories and symlinks which are a part of /// this package pub plist: Plist, @@ -72,7 +68,6 @@ impl From for Package { release: value.release, description: value.description, long_description: value.long_description, - appstream_data: value.appstream_data, dependencies: value.dependencies, users: value.users, groups: value.groups, diff --git a/src/package/specs.rs b/src/package/specs.rs index 1cfcc83..abe182b 100644 --- a/src/package/specs.rs +++ b/src/package/specs.rs @@ -16,10 +16,6 @@ pub struct Specs { pub description: String, /// a more verbose description of the package pub long_description: String, - /// an optional link to an - /// [AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/) - /// metadata file - pub appstream_data: Option, /// all of this package's runtime dependencies pub dependencies: Vec, /// an optional list of users to be created upon installation From 00683722d64a9c2f619e6c5378ab7b571b32ecc8 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Mon, 10 Apr 2023 19:17:26 -0400 Subject: [PATCH 16/17] Make Archive::remove parallel; Add Archive::pop method; --- src/tar/mod.rs | 15 +++++++++++++-- test/2.tar | Bin 10240 -> 10240 bytes 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/tar/mod.rs b/src/tar/mod.rs index 004b94e..f94798d 100644 --- a/src/tar/mod.rs +++ b/src/tar/mod.rs @@ -4,7 +4,7 @@ use std::{ path::PathBuf, }; -use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; +use rayon::prelude::{IntoParallelRefIterator, ParallelIterator, IndexedParallelIterator}; mod error; mod header; @@ -143,7 +143,7 @@ impl Archive { pub fn remove(&mut self, filename: &str) -> Result { let mut name = [0u8; 100]; name[..filename.len()].copy_from_slice(filename.as_bytes()); - if let Some(i) = &self.nodes.iter().position(|x| x.header.fname == name) { + if let Some(i) = &self.nodes.par_iter().position_any(|x| x.header.fname == name) { self.nodes.remove(*i); return Ok(true); } @@ -166,6 +166,17 @@ impl Archive { x.header.file_path() == Ok(PathBuf::from(filename)) }).cloned() } + + pub fn pop(&mut self, filename: &str) -> Option { + if let Some(i) = self.nodes.par_iter().position_any(|x| { + x.header.file_path() == Ok(PathBuf::from(filename)) + }) { + let node = self.nodes.get(i).cloned(); + self.nodes.remove(i); + return node; + } + None + } } #[cfg(test)] diff --git a/test/2.tar b/test/2.tar index 98902ce9c96775fa2ed4c0b8194684c56b3bd9ca..d37f5f88b97f1e407fb36b86a6fbcba352351702 100644 GIT binary patch delta 33 ocmZn&Xb70lBWP-9XkcJs#$aG*U~Xo_pkOdLkuha6BjbNH0Ge+I;s5{u delta 33 ocmZn&Xb70lBWPq|U~FV)#$aG*U~Xp2pkOdLkuha6BjbNH0Gg8s Date: Tue, 11 Apr 2023 22:03:09 -0400 Subject: [PATCH 17/17] Writer for tar nodes takes `&self` instead of `self` --- src/tar/node.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tar/node.rs b/src/tar/node.rs index 6d31e14..e618910 100644 --- a/src/tar/node.rs +++ b/src/tar/node.rs @@ -23,11 +23,11 @@ impl Node { /// Write out a single file within the tar to a file or something with a /// ``std::io::Write`` trait. - pub fn write(self, mut input: T) -> Result { + pub fn write(&self, mut input: T) -> Result { input.write_all(&self.header.to_bytes()?)?; let mut written = 512; - for d in self.data { - input.write_all(&d)?; + for d in &self.data { + input.write_all(d)?; written += d.len(); }