Get basic package installation implemented, with several TODOS left.
This commit is contained in:
parent
f4e87ee500
commit
13dff6bd96
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -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",
|
||||
|
@ -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.
|
||||
|
@ -25,6 +25,7 @@ pub enum Message {
|
||||
Failure(String),
|
||||
}
|
||||
|
||||
/// Creates a package archive
|
||||
pub struct Creator {
|
||||
path: PathBuf,
|
||||
entries: Vec<PathBuf>,
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<T: io::Read> {
|
||||
/// 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<T: io::Read> {
|
||||
impl<T: io::Read> Installer<T> {
|
||||
pub fn new<P: AsRef<OsStr>>(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<InstallMessage>) -> Result<(), Box<InstallError>> {
|
||||
unimplemented!();
|
||||
pub fn install(self, sender: Sender<InstallMessage>) -> 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<InstallMessage>),
|
||||
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<fmt::Error> for InstallError {
|
||||
fn from(value: fmt::Error) -> Self {
|
||||
Self::Fmt(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for InstallError {
|
||||
fn from(value: io::Error) -> Self {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user