From 13dff6bd96c06367a85ed1251976b2cf29a6dae2 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Tue, 11 Apr 2023 14:05:37 -0400 Subject: [PATCH] Get basic package installation implemented, with several TODOS left. --- Cargo.lock | 6 +- package-format.md | 18 ++++- src/creator/mod.rs | 1 + src/db/mod.rs | 6 +- src/installer/mod.rs | 185 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 203 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50c8171..16a2499 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -282,9 +282,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -560,7 +560,7 @@ dependencies = [ [[package]] name = "hpk-package" version = "0.1.0" -source = "git+https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git#acbdf2d99284b69a48601f3aeb0a56f295872a4c" +source = "git+https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git#00683722d64a9c2f619e6c5378ab7b571b32ecc8" dependencies = [ "chrono", "deku", diff --git a/package-format.md b/package-format.md index d5e9839..ac6586b 100644 --- a/package-format.md +++ b/package-format.md @@ -16,6 +16,8 @@ There is no top level directory in a package archive. Files intended to go into ```Sh . ├── package.ron +├── appdata.xml +├── postinstall.sh ├── etc │   └── hpk │   └── conf.ron.new @@ -33,7 +35,6 @@ There is no top level directory in a package archive. Files intended to go into └── man8 └── hpk-package-format.8 -11 directories, 7 files ``` Files inside of *etc* and *var* are expected to be configurable by the system administrator or may change at runtime, respectively, and so are not overwritten @@ -50,7 +51,7 @@ to start at boot or other related tasks. The file *package.ron* contains all of the required metadata about a package, such as a list of files to be installed, their checksums, runtime dependencies etc. -## package.ron +## package.ron [required] An example *package.ron* ```Ron Package( @@ -95,7 +96,6 @@ Package( gid: Some(42), ), ]), - post_install: Some("bar -u baz") ) ``` ### Descriptions of the fields @@ -116,3 +116,15 @@ Package( do not already exist - post_install: an optional Posix shell script (minus shebang line) to be run after installing the package + +## appdata.xml [optional] +This is an [appstream](https://www.freedesktop.org/wiki/Distributions/AppStream/) metadata +file used primarily by software stores or graphical package managers. If the source +distribution includes this file, it should be included in the package. If the source +distribution does not contain this file, then it can be omitted or an appropriate file +generated at the packager's discretion. + +## postinstall.sh [optional] +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. diff --git a/src/creator/mod.rs b/src/creator/mod.rs index 7eb5fea..866cd10 100644 --- a/src/creator/mod.rs +++ b/src/creator/mod.rs @@ -25,6 +25,7 @@ pub enum Message { Failure(String), } +/// Creates a package archive pub struct Creator { path: PathBuf, entries: Vec, diff --git a/src/db/mod.rs b/src/db/mod.rs index 000e080..ad21626 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -26,7 +26,11 @@ pub struct Update { impl fmt::Display for Update { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}-{}_{}_{}", self.name, self.version, self.arch, self.release) + write!( + f, + "{}-{}_{}_{}", + self.name, self.version, self.arch, self.release + ) } } diff --git a/src/installer/mod.rs b/src/installer/mod.rs index b5b01ea..aaeaac3 100644 --- a/src/installer/mod.rs +++ b/src/installer/mod.rs @@ -1,6 +1,24 @@ -use std::{io, path::{PathBuf, Path}, ffi::OsStr, sync::mpsc::Sender}; +#![allow(dead_code)] pub use error::InstallError; -use hpk_package::{Package, User, Group}; +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; pub struct Link { pub path: PathBuf, @@ -8,6 +26,8 @@ pub struct Link { } pub enum InstallMessage { + ArchiveRead, + ArchiveLen(usize), FileInstalled(PathBuf), DirectoryCreated(PathBuf), LinkCreated(Link), @@ -17,6 +37,8 @@ pub enum InstallMessage { } pub struct Installer { + /// The filesystem root under which to install this package. + /// Normally this is "/". path: PathBuf, package: Package, reader: T, @@ -25,22 +47,146 @@ pub struct Installer { impl Installer { pub fn new>(path: P, package: Package, reader: T) -> Self { let path = Path::new(&path).to_path_buf(); - Self { path, package, reader } + Self { + path, + package, + reader, + } } - pub fn install(&mut self, sender: Sender) -> Result<(), Box> { - unimplemented!(); + pub fn install(self, sender: Sender) -> Result<(), InstallError> { + let reader = Decoder::new(self.reader)?; + let mut archive = Archive::read(reader)?; + sender.send(InstallMessage::ArchiveRead)?; + let pr_node = match archive.pop("package.ron") { + 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())); + db_pkgdir.push(&self.package.name); + if !db_pkgdir.exists() { + fs::create_dir_all(&db_pkgdir)?; + } + let mut db_file = db_pkgdir.clone(); + db_file.push("package.ron"); + if db_file.exists() { + fs::remove_file(&db_file)?; + } + let fd = File::create(&db_file)?; + let writer = BufWriter::new(fd); + pr_node.write(writer)?; + let path = &self.path; + let package = &self.package; + let sender = Mutex::new(sender); + archive.nodes.par_iter().try_for_each(|node| { + let mut path = path.clone(); + let fpath = node.header.file_path()?; + let entry = package + .plist + .entries + .iter() + .find(|&x| match x { + Entry::File { + path, + sha256sum: _, + mode: _, + size: _, + } => path == &fpath, + Entry::Link { path, target: _ } => path == &fpath, + Entry::Directory { path, mode: _ } => path == &fpath, + }) + .unwrap(); + path = path.join(&fpath); + if let Some(parent) = path.parent() { + if !parent.exists() { + fs::create_dir_all(&parent)?; + } + } + if path.exists() { + if path.is_dir() { + fs::remove_dir(&path)?; + } else if path.is_symlink() || path.is_file() { + 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))?; + } + } + 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(()) } } mod error { - use std::{io, fmt, error::Error}; use hpk_package::tar; + use std::{error::Error, fmt, io, sync::{mpsc::{SendError, Sender}, PoisonError}}; + + use super::InstallMessage; #[derive(Debug)] pub enum InstallError { + Fmt(fmt::Error), IO(io::Error), + SendError(SendError), Tar(tar::Error), + MissingManifest, + MutexError, + ChecksumMismatch, } impl fmt::Display for InstallError { @@ -52,15 +198,42 @@ mod error { impl Error for InstallError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { + Self::Fmt(e) => Some(e), Self::IO(e) => Some(e), Self::Tar(e) => Some(e), + Self::SendError(e) => Some(e), + _ => None, } } } + impl From for InstallError { + fn from(value: fmt::Error) -> Self { + Self::Fmt(value) + } + } + impl From for InstallError { fn from(value: io::Error) -> Self { Self::IO(value) } } + + 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: hpk_package::tar::Error) -> Self { + Self::Tar(value) + } + } }