From be0c8dc6e749f51cd305a073f2a6f24f084bc467 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Tue, 11 Apr 2023 22:37:02 -0400 Subject: [PATCH] Massage Installer for better code readability --- .gitignore | 2 + Cargo.lock | 22 ++-- Cargo.toml | 2 +- package-format.md | 5 + src/bootstrap.rs | 1 + src/db/mod.rs | 7 +- src/hooks/mod.rs | 78 +++++++++++-- src/installer/mod.rs | 258 +++++++++++++++++++++++++++++------------- src/item/mod.rs | 1 + src/lib.rs | 3 +- src/repository/mod.rs | 6 + 11 files changed, 282 insertions(+), 103 deletions(-) diff --git a/.gitignore b/.gitignore index 4fdf8dc..04d060f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target tags +tags.lock +tags.temp package.specs /pkg diff --git a/Cargo.lock b/Cargo.lock index 16a2499..4f579c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,7 +357,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -374,7 +374,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -560,7 +560,7 @@ dependencies = [ [[package]] name = "hpk-package" version = "0.1.0" -source = "git+https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git#00683722d64a9c2f619e6c5378ab7b571b32ecc8" +source = "git+https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git#e6f392bd7a91ffe3f0b080627e915ea458eade5a" dependencies = [ "chrono", "deku", @@ -1021,22 +1021,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -1086,9 +1086,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5" dependencies = [ "proc-macro2", "quote", @@ -1127,7 +1127,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 90d6796..ef0e943 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ walkdir = "2.3" zstd = "0.12" [dependencies.clap] -version = "4.1" +version = "4.2" optional = true [dependencies.indicatif] diff --git a/package-format.md b/package-format.md index ac6586b..53e3626 100644 --- a/package-format.md +++ b/package-format.md @@ -128,3 +128,8 @@ generated at the packager's discretion. This is a POSIX shell compatible script to be run in order to finish installation. Heavy use of this capability is discouraged. If the desired action could be provided by hooks [see src/hooks/mod.rs] then please file a bug report to request the feature be added. + +Scripts will be run with the environment variable `HPK_ROOT` set to the root under which +the package is to be installed. Post install scripts **must** respect this environment +variable and treat paths accordingly, so as to allow hpk to install packages into a +chroot which may or may not be binary compatible with the host. diff --git a/src/bootstrap.rs b/src/bootstrap.rs index 9ee1d91..77869e3 100644 --- a/src/bootstrap.rs +++ b/src/bootstrap.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] mod cli; use { clap::{Arg, Command}, diff --git a/src/db/mod.rs b/src/db/mod.rs index ad21626..7858825 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -16,6 +16,7 @@ use { }; #[derive(Clone, Debug)] +/// Represents an updated package to be installed on the system pub struct Update { pub name: String, pub version: Version, @@ -41,8 +42,12 @@ impl Update { } #[derive(Clone, Debug, Deserialize, Serialize)] +/// A struct representing all locally installed packages and all packages which +/// are available in a remote repository pub struct Database { + /// All locally installed packages pub packages: HashMap, + /// All currently configured repositories pub available: HashMap, } @@ -137,7 +142,7 @@ impl Database { Ok(()) } - pub fn rebuild(prefix: Option) -> Result> { + pub fn rebuild(_prefix: Option) -> Result> { unimplemented!(); } } diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index dc60598..478b2f9 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -1,9 +1,23 @@ -use std::{ - io, - process::{Command, Output}, +use hpk_package::{Group, User}; + +use { + crate::InstallError, + std::{ + path::PathBuf, + process::{Command, Output}, + }, }; +#[derive(Debug, Clone, PartialEq)] +/// A post install script extracted from a package archive and the sysroot in +/// which it should be run (usually "/") +pub struct Pinstall { + script: PathBuf, + root: PathBuf, +} + #[non_exhaustive] +#[derive(Debug, Clone, PartialEq)] /// Defines a set of commands to be run in order to finish package installation pub enum Hooks { /// Runs `makewhatis` to update the mandoc database if the package contains @@ -13,28 +27,74 @@ pub enum Hooks { GlibSchema, /// runs `install-info` to update the GNU Texinfo database Info(String), + /// runs the post install script for a package + Pinstall(Pinstall), + /// creates a new system user + User(User, Option), + /// creates a new system group + Group(Group, Option), +} + +impl From for Hooks { + fn from(value: Pinstall) -> Self { + Self::Pinstall(value) + } +} + +impl From<(User, Option)> for Hooks { + fn from(value: (User, Option)) -> Self { + Self::User(value.0, value.1) + } +} + +impl From<(Group, Option)> for Hooks { + fn from(value: (Group, Option)) -> Self { + Self::Group(value.0, value.1) + } } impl Hooks { - pub fn run(&self) -> Result { + /// Runs a hook and returns it's output + pub fn run(&self) -> Result { match self { Self::Man => makeinfo(), Self::GlibSchema => compile_schemas(), Self::Info(path) => install_info(path), + Self::Pinstall(p) => p.run(), + Self::User(_u, _p) => unimplemented!(), + Self::Group(_g, _p) => unimplemented!(), } } } -fn makeinfo() -> Result { - Command::new("makewhatis").output() +fn makeinfo() -> Result { + Command::new("makewhatis").output().map_err(|e| e.into()) } -fn compile_schemas() -> Result { +fn compile_schemas() -> Result { Command::new("glib-compile-schemas") .arg("/usr/share/glib-2.0/schemas") .output() + .map_err(|e| e.into()) } -fn install_info(path: &str) -> Result { - Command::new("install-info").arg(path).output() +fn install_info(path: &str) -> Result { + Command::new("install-info") + .arg(path) + .output() + .map_err(|e| e.into()) +} + +impl Pinstall { + pub fn new(script: PathBuf, root: PathBuf) -> Self { + Self { script, root } + } + + fn run(&self) -> Result { + Command::new("/bin/sh") + .arg(self.script.to_str().unwrap_or("")) + .env("HPK_ROOT", self.root.to_str().unwrap_or("")) + .output() + .map_err(|e| e.into()) + } } diff --git a/src/installer/mod.rs b/src/installer/mod.rs index aaeaac3..396ac99 100644 --- a/src/installer/mod.rs +++ b/src/installer/mod.rs @@ -1,41 +1,58 @@ #![allow(dead_code)] pub use error::InstallError; -use hpk_package::{ - sha2::{Digest, Sha256}, - tar::Archive, - Entry, Group, Package, User, -}; -use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; -use std::{ - ffi::OsStr, - fmt::Write as _, - fs::{self, DirBuilder, File}, - io::{self, BufWriter, Write}, - os::{ - self, - unix::{fs::DirBuilderExt, prelude::OpenOptionsExt}, - }, - path::{Path, PathBuf}, - sync::{mpsc::Sender, Mutex}, -}; -use zstd::Decoder; +use { + crate::{Hooks, Pinstall}, + hpk_package::{ + sha2::{Digest, Sha256}, + tar::{Archive, Node}, + Entry, Group, Package, User, + }, + rayon::prelude::{IntoParallelRefIterator, ParallelIterator}, + std::{ + ffi::OsStr, + fmt::Write as _, + fs::{self, DirBuilder, File}, + io::{self, BufWriter, Write}, + os::{ + self, + unix::{fs::DirBuilderExt, prelude::OpenOptionsExt}, + }, + path::{Path, PathBuf}, + sync::{mpsc::Sender, Mutex}, + }, + zstd::Decoder, +}; + +/// Represents a symbolic link pub struct Link { pub path: PathBuf, pub target: PathBuf, } +/// Messages sent from and `Installer` to the calling thread pub enum InstallMessage { ArchiveRead, + /// The number of files to be extracted from the archive, sent before any + /// extraction takes place to facilitate building eg. progress bars ArchiveLen(usize), + /// A file has been successfully installed FileInstalled(PathBuf), + /// A directory has been successfully created DirectoryCreated(PathBuf), + /// A `Link` has been successfully created LinkCreated(Link), + /// A `User` has been successfully created UserCreated(User), + /// A `Group` has been successfully created GroupCreated(Group), - PostInstall(String), + /// The output of the post install script sent to stdout + PostInstallStdout(String), + /// The output of the post install script sent to stderr + PostInstallStderr(String), } +/// Installs a package into it's specified rootfs pub struct Installer { /// The filesystem root under which to install this package. /// Normally this is "/". @@ -45,6 +62,7 @@ pub struct Installer { } impl Installer { + /// Creates a new installer from an open archive. pub fn new>(path: P, package: Package, reader: T) -> Self { let path = Path::new(&path).to_path_buf(); Self { @@ -54,7 +72,8 @@ impl Installer { } } - pub fn install(self, sender: Sender) -> Result<(), InstallError> { + pub fn install(mut self, sender: Sender) -> Result, InstallError> { + let mut hooks = self.init_hooks(); let reader = Decoder::new(self.reader)?; let mut archive = Archive::read(reader)?; sender.send(InstallMessage::ArchiveRead)?; @@ -62,8 +81,6 @@ impl Installer { Some(node) => node, None => return Err(InstallError::MissingManifest), }; - let pinstall = archive.pop("postinstall.sh"); - let appstream = archive.pop("appdata.xml"); let len = archive.nodes.len(); sender.send(InstallMessage::ArchiveLen(len))?; let mut db_pkgdir = crate::get_dbdir(Some(self.path.clone())); @@ -71,7 +88,9 @@ impl Installer { if !db_pkgdir.exists() { fs::create_dir_all(&db_pkgdir)?; } - let mut db_file = db_pkgdir.clone(); + pop_appstream(&mut archive, &db_pkgdir)?; + pop_pinstall(&mut archive, &mut hooks, &self.package.name, &self.path)?; + let mut db_file = db_pkgdir; db_file.push("package.ron"); if db_file.exists() { fs::remove_file(&db_file)?; @@ -82,9 +101,29 @@ impl Installer { let path = &self.path; let package = &self.package; let sender = Mutex::new(sender); + let hooks = Mutex::new(hooks); archive.nodes.par_iter().try_for_each(|node| { let mut path = path.clone(); let fpath = node.header.file_path()?; + if let Some(s) = node.header.prefix() { + if s.contains("/share/man/") { + let mut h = hooks.lock().unwrap(); + if !h.contains(&Hooks::Man) { + h.push(Hooks::Man); + } + } else if s.contains("/share/info") { + hooks + .lock() + .unwrap() + .push(Hooks::Info(fpath.to_str().unwrap().to_string())) + } else if s.contains("/share/glib-2.0/schemas") { + let mut h = hooks.lock().unwrap(); + if !h.contains(&Hooks::GlibSchema) { + h.push(Hooks::GlibSchema); + } + } + } + // Match up a package entry with a tar node let entry = package .plist .entries @@ -113,68 +152,114 @@ impl Installer { fs::remove_file(&path)?; } } - match entry { - Entry::Directory { path: _, mode } => { - DirBuilder::new().mode(*mode).create(&path)?; - sender - .lock() - .unwrap() - .send(InstallMessage::DirectoryCreated(path))?; - } - Entry::Link { path: _, target } => { - os::unix::fs::symlink(&path, &target)?; - sender - .lock() - .unwrap() - .send(InstallMessage::LinkCreated(Link { - path, - target: target.clone(), - }))?; - } - Entry::File { - path: _, - sha256sum, - mode, - size: _, - } => { - let mut buf = vec![]; - node.clone().write(&mut buf)?; - let mut sum = String::new(); - let mut hasher = Sha256::new(); - hasher.update(&buf); - let res = hasher.finalize(); - for c in res { - write!(sum, "{c:02x}")?; - } - if sha256sum != &sum { - return Err(InstallError::ChecksumMismatch); - } - let fd = File::options().mode(*mode).write(true).open(&path)?; - let mut writer = BufWriter::new(fd); - writer.write_all(&buf)?; - sender - .lock() - .unwrap() - .send(InstallMessage::FileInstalled(path))?; - } - } + let msg = extract_entry(entry, node, path)?; + sender.lock().unwrap().send(msg)?; Ok::<(), InstallError>(()) })?; - let sender = sender.into_inner()?; - if let Some(node) = appstream { - let mut appdatafile = db_pkgdir; - appdatafile.push("appdata.xml"); - let fd = File::open(appdatafile)?; - let writer = BufWriter::new(fd); - node.write(writer)?; - } - Ok(()) + Ok(hooks.into_inner()?) } + + fn init_hooks(&mut self) -> Vec { + let mut hooks = Vec::::new(); + if let Some(ref users) = self.package.users { + users + .iter() + .for_each(|u| hooks.push((u.clone(), Some(self.path.clone())).into())); + } + if let Some(ref groups) = self.package.groups { + groups + .iter() + .for_each(|g| hooks.push((g.clone(), Some(self.path.clone())).into())); + } + hooks + } +} + +fn extract_entry(entry: &Entry, node: &Node, path: PathBuf) -> Result { + match entry { + Entry::Directory { path: _, mode } => { + DirBuilder::new().mode(*mode).create(&path)?; + Ok(InstallMessage::DirectoryCreated(path)) + } + Entry::Link { path: _, target } => { + os::unix::fs::symlink(&path, &target)?; + Ok(InstallMessage::LinkCreated(Link { + path, + target: target.clone(), + })) + } + Entry::File { + path: _, + sha256sum, + mode, + size: _, + } => { + let mut buf = vec![]; + node.write(&mut buf)?; + let mut sum = String::new(); + let mut hasher = Sha256::new(); + hasher.update(&buf); + let res = hasher.finalize(); + for c in res { + write!(sum, "{c:02x}")?; + } + if sha256sum != &sum { + return Err(InstallError::ChecksumMismatch); + } + let fd = File::options().mode(*mode).write(true).open(&path)?; + let mut writer = BufWriter::new(fd); + writer.write_all(&buf)?; + Ok(InstallMessage::FileInstalled(path)) + } + } +} + +fn pop_appstream(archive: &mut Archive, db_pkgdir: &Path) -> Result<(), InstallError> { + let appstream = archive.pop("appdata.xml"); + if let Some(node) = appstream { + let mut appdatafile = db_pkgdir.to_path_buf(); + appdatafile.push("appdata.xml"); + let fd = File::open(appdatafile)?; + let writer = BufWriter::new(fd); + node.write(writer)?; + } + Ok(()) +} + +fn pop_pinstall( + archive: &mut Archive, + hooks: &mut Vec, + pkgname: &str, + root: &Path, +) -> Result<(), InstallError> { + let pinstall = archive.pop("postinstall.sh"); + if let Some(node) = pinstall { + let mut path: PathBuf = ["/", "tmp", "hpk", pkgname].iter().collect(); + if !path.exists() { + fs::create_dir_all(&path)?; + } + path.push("pinstall.sh"); + let fd = File::open(&path)?; + let writer = BufWriter::new(fd); + node.write(writer)?; + hooks.push(Pinstall::new(path, root.to_path_buf()).into()); + } + Ok(()) } mod error { use hpk_package::tar; - use std::{error::Error, fmt, io, sync::{mpsc::{SendError, Sender}, PoisonError}}; + use std::{ + error::Error, + fmt, io, + string::FromUtf8Error, + sync::{ + mpsc::{SendError, Sender}, + PoisonError, + }, + }; + + use crate::Hooks; use super::InstallMessage; @@ -184,9 +269,10 @@ mod error { IO(io::Error), SendError(SendError), Tar(tar::Error), + ChecksumMismatch, MissingManifest, MutexError, - ChecksumMismatch, + Utf8, } impl fmt::Display for InstallError { @@ -225,12 +311,24 @@ mod error { } } + impl From>> for InstallError { + fn from(_value: PoisonError>) -> Self { + Self::MutexError + } + } + impl From> for InstallError { fn from(value: SendError) -> Self { Self::SendError(value) } } + impl From for InstallError { + fn from(_value: FromUtf8Error) -> Self { + Self::Utf8 + } + } + impl From for InstallError { fn from(value: hpk_package::tar::Error) -> Self { Self::Tar(value) diff --git a/src/item/mod.rs b/src/item/mod.rs index 33829d0..f287b93 100644 --- a/src/item/mod.rs +++ b/src/item/mod.rs @@ -14,6 +14,7 @@ use std::{ }; #[derive(Clone, Debug)] +/// An intermediate type used in the creation of a package archive pub struct Item { pub entry: Entry, pub data: Vec, diff --git a/src/lib.rs b/src/lib.rs index 2b62048..ce09a1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,9 @@ use std::path::PathBuf; pub use { creator::{Creator, Message}, db::Database, - hooks::Hooks, + hooks::{Hooks, Pinstall}, hpk_package::{tar, Arch, Dependency, GitRev, Package, Plist, Rapid, SemVer, Specs, Version}, + installer::{InstallError, InstallMessage, Installer}, item::Item, repository::Repository, }; diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 4ece0b5..60c67b1 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -14,13 +14,19 @@ use { }; #[derive(Clone, Debug, Deserialize, Serialize)] +/// A struct representing all of the packages available in a remote repository pub struct Repository { + /// The unique name for this repository pub name: String, + /// The base url for this repository pub base_url: Url, + /// a `HashMap` representing all available packages on this server, with the + /// keys being the unique name of each package pub packages: HashMap, } #[derive(Clone, Debug)] +/// Messages sent to the caller thread when fetching an update for a repository pub enum Message { ContentLength(usize), BytesRead(usize),