Massage Installer for better code readability

This commit is contained in:
Nathan Fisher 2023-04-11 22:37:02 -04:00
parent 13dff6bd96
commit be0c8dc6e7
11 changed files with 282 additions and 103 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
/target /target
tags tags
tags.lock
tags.temp
package.specs package.specs
/pkg /pkg

22
Cargo.lock generated
View File

@ -357,7 +357,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"scratch", "scratch",
"syn 2.0.13", "syn 2.0.14",
] ]
[[package]] [[package]]
@ -374,7 +374,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.13", "syn 2.0.14",
] ]
[[package]] [[package]]
@ -560,7 +560,7 @@ dependencies = [
[[package]] [[package]]
name = "hpk-package" name = "hpk-package"
version = "0.1.0" 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 = [ dependencies = [
"chrono", "chrono",
"deku", "deku",
@ -1021,22 +1021,22 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.159" version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.159" version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.13", "syn 2.0.14",
] ]
[[package]] [[package]]
@ -1086,9 +1086,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.13" version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1127,7 +1127,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.13", "syn 2.0.14",
] ]
[[package]] [[package]]

View File

@ -28,7 +28,7 @@ walkdir = "2.3"
zstd = "0.12" zstd = "0.12"
[dependencies.clap] [dependencies.clap]
version = "4.1" version = "4.2"
optional = true optional = true
[dependencies.indicatif] [dependencies.indicatif]

View File

@ -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 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 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. [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.

View File

@ -1,3 +1,4 @@
#![allow(dead_code)]
mod cli; mod cli;
use { use {
clap::{Arg, Command}, clap::{Arg, Command},

View File

@ -16,6 +16,7 @@ use {
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// Represents an updated package to be installed on the system
pub struct Update { pub struct Update {
pub name: String, pub name: String,
pub version: Version, pub version: Version,
@ -41,8 +42,12 @@ impl Update {
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[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 { pub struct Database {
/// All locally installed packages
pub packages: HashMap<String, Package>, pub packages: HashMap<String, Package>,
/// All currently configured repositories
pub available: HashMap<String, Repository>, pub available: HashMap<String, Repository>,
} }
@ -137,7 +142,7 @@ impl Database {
Ok(()) Ok(())
} }
pub fn rebuild(prefix: Option<PathBuf>) -> Result<Self, Box<dyn Error>> { pub fn rebuild(_prefix: Option<PathBuf>) -> Result<Self, Box<dyn Error>> {
unimplemented!(); unimplemented!();
} }
} }

View File

@ -1,9 +1,23 @@
use std::{ use hpk_package::{Group, User};
io,
process::{Command, Output}, 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] #[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
/// Defines a set of commands to be run in order to finish package installation /// Defines a set of commands to be run in order to finish package installation
pub enum Hooks { pub enum Hooks {
/// Runs `makewhatis` to update the mandoc database if the package contains /// Runs `makewhatis` to update the mandoc database if the package contains
@ -13,28 +27,74 @@ pub enum Hooks {
GlibSchema, GlibSchema,
/// runs `install-info` to update the GNU Texinfo database /// runs `install-info` to update the GNU Texinfo database
Info(String), Info(String),
/// runs the post install script for a package
Pinstall(Pinstall),
/// creates a new system user
User(User, Option<PathBuf>),
/// creates a new system group
Group(Group, Option<PathBuf>),
}
impl From<Pinstall> for Hooks {
fn from(value: Pinstall) -> Self {
Self::Pinstall(value)
}
}
impl From<(User, Option<PathBuf>)> for Hooks {
fn from(value: (User, Option<PathBuf>)) -> Self {
Self::User(value.0, value.1)
}
}
impl From<(Group, Option<PathBuf>)> for Hooks {
fn from(value: (Group, Option<PathBuf>)) -> Self {
Self::Group(value.0, value.1)
}
} }
impl Hooks { impl Hooks {
pub fn run(&self) -> Result<Output, io::Error> { /// Runs a hook and returns it's output
pub fn run(&self) -> Result<Output, InstallError> {
match self { match self {
Self::Man => makeinfo(), Self::Man => makeinfo(),
Self::GlibSchema => compile_schemas(), Self::GlibSchema => compile_schemas(),
Self::Info(path) => install_info(path), Self::Info(path) => install_info(path),
Self::Pinstall(p) => p.run(),
Self::User(_u, _p) => unimplemented!(),
Self::Group(_g, _p) => unimplemented!(),
} }
} }
} }
fn makeinfo() -> Result<Output, io::Error> { fn makeinfo() -> Result<Output, InstallError> {
Command::new("makewhatis").output() Command::new("makewhatis").output().map_err(|e| e.into())
} }
fn compile_schemas() -> Result<Output, io::Error> { fn compile_schemas() -> Result<Output, InstallError> {
Command::new("glib-compile-schemas") Command::new("glib-compile-schemas")
.arg("/usr/share/glib-2.0/schemas") .arg("/usr/share/glib-2.0/schemas")
.output() .output()
.map_err(|e| e.into())
} }
fn install_info(path: &str) -> Result<Output, io::Error> { fn install_info(path: &str) -> Result<Output, InstallError> {
Command::new("install-info").arg(path).output() 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<Output, InstallError> {
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())
}
} }

View File

@ -1,41 +1,58 @@
#![allow(dead_code)] #![allow(dead_code)]
pub use error::InstallError; 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 struct Link {
pub path: PathBuf, pub path: PathBuf,
pub target: PathBuf, pub target: PathBuf,
} }
/// Messages sent from and `Installer` to the calling thread
pub enum InstallMessage { pub enum InstallMessage {
ArchiveRead, 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), ArchiveLen(usize),
/// A file has been successfully installed
FileInstalled(PathBuf), FileInstalled(PathBuf),
/// A directory has been successfully created
DirectoryCreated(PathBuf), DirectoryCreated(PathBuf),
/// A `Link` has been successfully created
LinkCreated(Link), LinkCreated(Link),
/// A `User` has been successfully created
UserCreated(User), UserCreated(User),
/// A `Group` has been successfully created
GroupCreated(Group), 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<T: io::Read> { pub struct Installer<T: io::Read> {
/// The filesystem root under which to install this package. /// The filesystem root under which to install this package.
/// Normally this is "/". /// Normally this is "/".
@ -45,6 +62,7 @@ pub struct Installer<T: io::Read> {
} }
impl<T: io::Read> Installer<T> { impl<T: io::Read> Installer<T> {
/// Creates a new installer from an open archive.
pub fn new<P: AsRef<OsStr>>(path: P, package: Package, reader: T) -> Self { pub fn new<P: AsRef<OsStr>>(path: P, package: Package, reader: T) -> Self {
let path = Path::new(&path).to_path_buf(); let path = Path::new(&path).to_path_buf();
Self { Self {
@ -54,7 +72,8 @@ impl<T: io::Read> Installer<T> {
} }
} }
pub fn install(self, sender: Sender<InstallMessage>) -> Result<(), InstallError> { pub fn install(mut self, sender: Sender<InstallMessage>) -> Result<Vec<Hooks>, InstallError> {
let mut hooks = self.init_hooks();
let reader = Decoder::new(self.reader)?; let reader = Decoder::new(self.reader)?;
let mut archive = Archive::read(reader)?; let mut archive = Archive::read(reader)?;
sender.send(InstallMessage::ArchiveRead)?; sender.send(InstallMessage::ArchiveRead)?;
@ -62,8 +81,6 @@ impl<T: io::Read> Installer<T> {
Some(node) => node, Some(node) => node,
None => return Err(InstallError::MissingManifest), None => return Err(InstallError::MissingManifest),
}; };
let pinstall = archive.pop("postinstall.sh");
let appstream = archive.pop("appdata.xml");
let len = archive.nodes.len(); let len = archive.nodes.len();
sender.send(InstallMessage::ArchiveLen(len))?; sender.send(InstallMessage::ArchiveLen(len))?;
let mut db_pkgdir = crate::get_dbdir(Some(self.path.clone())); let mut db_pkgdir = crate::get_dbdir(Some(self.path.clone()));
@ -71,7 +88,9 @@ impl<T: io::Read> Installer<T> {
if !db_pkgdir.exists() { if !db_pkgdir.exists() {
fs::create_dir_all(&db_pkgdir)?; 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"); db_file.push("package.ron");
if db_file.exists() { if db_file.exists() {
fs::remove_file(&db_file)?; fs::remove_file(&db_file)?;
@ -82,9 +101,29 @@ impl<T: io::Read> Installer<T> {
let path = &self.path; let path = &self.path;
let package = &self.package; let package = &self.package;
let sender = Mutex::new(sender); let sender = Mutex::new(sender);
let hooks = Mutex::new(hooks);
archive.nodes.par_iter().try_for_each(|node| { archive.nodes.par_iter().try_for_each(|node| {
let mut path = path.clone(); let mut path = path.clone();
let fpath = node.header.file_path()?; 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 let entry = package
.plist .plist
.entries .entries
@ -113,68 +152,114 @@ impl<T: io::Read> Installer<T> {
fs::remove_file(&path)?; fs::remove_file(&path)?;
} }
} }
match entry { let msg = extract_entry(entry, node, path)?;
Entry::Directory { path: _, mode } => { sender.lock().unwrap().send(msg)?;
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))?;
}
}
Ok::<(), InstallError>(()) Ok::<(), InstallError>(())
})?; })?;
let sender = sender.into_inner()?; Ok(hooks.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(())
} }
fn init_hooks(&mut self) -> Vec<Hooks> {
let mut hooks = Vec::<Hooks>::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<InstallMessage, InstallError> {
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<Hooks>,
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 { mod error {
use hpk_package::tar; 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; use super::InstallMessage;
@ -184,9 +269,10 @@ mod error {
IO(io::Error), IO(io::Error),
SendError(SendError<InstallMessage>), SendError(SendError<InstallMessage>),
Tar(tar::Error), Tar(tar::Error),
ChecksumMismatch,
MissingManifest, MissingManifest,
MutexError, MutexError,
ChecksumMismatch, Utf8,
} }
impl fmt::Display for InstallError { impl fmt::Display for InstallError {
@ -225,12 +311,24 @@ mod error {
} }
} }
impl From<PoisonError<Vec<Hooks>>> for InstallError {
fn from(_value: PoisonError<Vec<Hooks>>) -> Self {
Self::MutexError
}
}
impl From<SendError<InstallMessage>> for InstallError { impl From<SendError<InstallMessage>> for InstallError {
fn from(value: SendError<InstallMessage>) -> Self { fn from(value: SendError<InstallMessage>) -> Self {
Self::SendError(value) Self::SendError(value)
} }
} }
impl From<FromUtf8Error> for InstallError {
fn from(_value: FromUtf8Error) -> Self {
Self::Utf8
}
}
impl From<hpk_package::tar::Error> for InstallError { impl From<hpk_package::tar::Error> for InstallError {
fn from(value: hpk_package::tar::Error) -> Self { fn from(value: hpk_package::tar::Error) -> Self {
Self::Tar(value) Self::Tar(value)

View File

@ -14,6 +14,7 @@ use std::{
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// An intermediate type used in the creation of a package archive
pub struct Item { pub struct Item {
pub entry: Entry, pub entry: Entry,
pub data: Vec<u8>, pub data: Vec<u8>,

View File

@ -11,8 +11,9 @@ use std::path::PathBuf;
pub use { pub use {
creator::{Creator, Message}, creator::{Creator, Message},
db::Database, db::Database,
hooks::Hooks, hooks::{Hooks, Pinstall},
hpk_package::{tar, Arch, Dependency, GitRev, Package, Plist, Rapid, SemVer, Specs, Version}, hpk_package::{tar, Arch, Dependency, GitRev, Package, Plist, Rapid, SemVer, Specs, Version},
installer::{InstallError, InstallMessage, Installer},
item::Item, item::Item,
repository::Repository, repository::Repository,
}; };

View File

@ -14,13 +14,19 @@ use {
}; };
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
/// A struct representing all of the packages available in a remote repository
pub struct Repository { pub struct Repository {
/// The unique name for this repository
pub name: String, pub name: String,
/// The base url for this repository
pub base_url: Url, 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<String, Package>, pub packages: HashMap<String, Package>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// Messages sent to the caller thread when fetching an update for a repository
pub enum Message { pub enum Message {
ContentLength(usize), ContentLength(usize),
BytesRead(usize), BytesRead(usize),