Get basic package installation implemented, with several TODOS left.

This commit is contained in:
Nathan Fisher 2023-04-11 14:05:37 -04:00
parent f4e87ee500
commit 13dff6bd96
5 changed files with 203 additions and 13 deletions

6
Cargo.lock generated
View File

@ -282,9 +282,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.7" version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"crossbeam-utils", "crossbeam-utils",
@ -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#acbdf2d99284b69a48601f3aeb0a56f295872a4c" source = "git+https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git#00683722d64a9c2f619e6c5378ab7b571b32ecc8"
dependencies = [ dependencies = [
"chrono", "chrono",
"deku", "deku",

View File

@ -16,6 +16,8 @@ There is no top level directory in a package archive. Files intended to go into
```Sh ```Sh
. .
├── package.ron ├── package.ron
├── appdata.xml
├── postinstall.sh
├── etc ├── etc
│   └── hpk │   └── hpk
│   └── conf.ron.new │   └── conf.ron.new
@ -33,7 +35,6 @@ There is no top level directory in a package archive. Files intended to go into
└── man8 └── man8
└── hpk-package-format.8 └── hpk-package-format.8
11 directories, 7 files
``` ```
Files inside of *etc* and *var* are expected to be configurable by the system 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 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 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. as a list of files to be installed, their checksums, runtime dependencies etc.
## package.ron ## package.ron [required]
An example *package.ron* An example *package.ron*
```Ron ```Ron
Package( Package(
@ -95,7 +96,6 @@ Package(
gid: Some(42), gid: Some(42),
), ),
]), ]),
post_install: Some("bar -u baz")
) )
``` ```
### Descriptions of the fields ### Descriptions of the fields
@ -116,3 +116,15 @@ Package(
do not already exist do not already exist
- post_install: an optional Posix shell script (minus shebang line) to be run - post_install: an optional Posix shell script (minus shebang line) to be run
after installing the package 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.

View File

@ -25,6 +25,7 @@ pub enum Message {
Failure(String), Failure(String),
} }
/// Creates a package archive
pub struct Creator { pub struct Creator {
path: PathBuf, path: PathBuf,
entries: Vec<PathBuf>, entries: Vec<PathBuf>,

View File

@ -26,7 +26,11 @@ pub struct Update {
impl fmt::Display for Update { impl fmt::Display for Update {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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
)
} }
} }

View File

@ -1,6 +1,24 @@
use std::{io, path::{PathBuf, Path}, ffi::OsStr, sync::mpsc::Sender}; #![allow(dead_code)]
pub use error::InstallError; 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 struct Link {
pub path: PathBuf, pub path: PathBuf,
@ -8,6 +26,8 @@ pub struct Link {
} }
pub enum InstallMessage { pub enum InstallMessage {
ArchiveRead,
ArchiveLen(usize),
FileInstalled(PathBuf), FileInstalled(PathBuf),
DirectoryCreated(PathBuf), DirectoryCreated(PathBuf),
LinkCreated(Link), LinkCreated(Link),
@ -17,6 +37,8 @@ pub enum InstallMessage {
} }
pub struct Installer<T: io::Read> { pub struct Installer<T: io::Read> {
/// The filesystem root under which to install this package.
/// Normally this is "/".
path: PathBuf, path: PathBuf,
package: Package, package: Package,
reader: T, reader: T,
@ -25,22 +47,146 @@ pub struct Installer<T: io::Read> {
impl<T: io::Read> Installer<T> { impl<T: io::Read> Installer<T> {
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 { path, package, reader } Self {
path,
package,
reader,
}
} }
pub fn install(&mut self, sender: Sender<InstallMessage>) -> Result<(), Box<InstallError>> { pub fn install(self, sender: Sender<InstallMessage>) -> Result<(), InstallError> {
unimplemented!(); 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 { mod error {
use std::{io, fmt, error::Error};
use hpk_package::tar; use hpk_package::tar;
use std::{error::Error, fmt, io, sync::{mpsc::{SendError, Sender}, PoisonError}};
use super::InstallMessage;
#[derive(Debug)] #[derive(Debug)]
pub enum InstallError { pub enum InstallError {
Fmt(fmt::Error),
IO(io::Error), IO(io::Error),
SendError(SendError<InstallMessage>),
Tar(tar::Error), Tar(tar::Error),
MissingManifest,
MutexError,
ChecksumMismatch,
} }
impl fmt::Display for InstallError { impl fmt::Display for InstallError {
@ -52,15 +198,42 @@ mod error {
impl Error for InstallError { impl Error for InstallError {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
match self { match self {
Self::Fmt(e) => Some(e),
Self::IO(e) => Some(e), Self::IO(e) => Some(e),
Self::Tar(e) => Some(e), Self::Tar(e) => Some(e),
Self::SendError(e) => Some(e),
_ => None,
} }
} }
} }
impl From<fmt::Error> for InstallError {
fn from(value: fmt::Error) -> Self {
Self::Fmt(value)
}
}
impl From<io::Error> for InstallError { impl From<io::Error> for InstallError {
fn from(value: io::Error) -> Self { fn from(value: io::Error) -> Self {
Self::IO(value) Self::IO(value)
} }
} }
impl From<PoisonError<Sender<InstallMessage>>> for InstallError {
fn from(_value: PoisonError<Sender<InstallMessage>>) -> Self {
Self::MutexError
}
}
impl From<SendError<InstallMessage>> for InstallError {
fn from(value: SendError<InstallMessage>) -> Self {
Self::SendError(value)
}
}
impl From<hpk_package::tar::Error> for InstallError {
fn from(value: hpk_package::tar::Error) -> Self {
Self::Tar(value)
}
}
} }