Extend tar functionality to turn nodes and archives into byte vectors;

Implement package creation using those methods;
This commit is contained in:
Nathan Fisher 2023-03-25 12:28:44 -04:00
parent 318968db4b
commit ffcd6820d0
13 changed files with 199 additions and 34 deletions

1
Cargo.lock generated
View File

@ -398,6 +398,7 @@ dependencies = [
"chrono", "chrono",
"clap", "clap",
"cli", "cli",
"deku",
"rayon", "rayon",
"ron", "ron",
"serde", "serde",

View File

@ -15,9 +15,11 @@ path = "src/hpk.rs"
[workspace.dependencies] [workspace.dependencies]
clap = "4.1" clap = "4.1"
deku = "0.16"
[dependencies] [dependencies]
clap = { workspace = true } clap = { workspace = true }
deku.workspace = true
cli = { path = "cli" } cli = { path = "cli" }
rayon = "1.7" rayon = "1.7"
ron = "0.8" ron = "0.8"

View File

@ -20,7 +20,7 @@ impl Hooks {
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),
} }
} }
} }

View File

@ -2,6 +2,7 @@ use crate::Entry;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::{ use std::{
error::Error, error::Error,
ffi::OsStr,
fmt::Write, fmt::Write,
fs, fs,
io::Read, io::Read,
@ -13,13 +14,14 @@ use tar::{Node, Owner};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Item { pub struct Item {
pub entry: Entry, pub entry: Entry,
pub node: Node, pub data: Vec<u8>,
} }
impl Item { impl Item {
pub fn try_create(path: &Path) -> Result<Self, Box<dyn Error>> { pub fn try_create(path: &Path) -> Result<Self, Box<dyn Error>> {
let meta = fs::metadata(path)?; let path = fix_path(path);
let filename = format!("/{}", path.display()); let meta = fs::metadata(&path)?;
let filename = format!("{}", path.display());
let owner = Some(Owner::default()); let owner = Some(Owner::default());
if meta.is_file() { if meta.is_file() {
let mut data = vec![]; let mut data = vec![];
@ -41,24 +43,44 @@ impl Item {
mode, mode,
size, size,
}, },
node: Node::read_data_to_tar(&data, &filename, &meta, owner)?, data: Node::read_data_to_tar(&data, &filename, &meta, owner)?.to_vec()?,
}) })
} else if meta.is_dir() { } else if meta.is_dir() {
let mode = meta.mode(); let mode = meta.mode();
let path = PathBuf::from(&filename); let path = PathBuf::from(&filename);
Ok(Self { Ok(Self {
entry: Entry::Directory { path, mode }, entry: Entry::Directory { path, mode },
node: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?, data: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?.to_vec()?,
}) })
} else if meta.is_symlink() { } else if meta.is_symlink() {
let target = fs::read_link(path)?; let target = fs::read_link(path)?;
let path = PathBuf::from(&filename); let path = PathBuf::from(&filename);
Ok(Self { Ok(Self {
entry: Entry::Link { path, target }, entry: Entry::Link { path, target },
node: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?, data: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?.to_vec()?,
}) })
} else { } else {
unreachable!(); unreachable!();
} }
} }
} }
fn fix_path(path: &Path) -> PathBuf {
if path.is_file() || path.is_symlink() {
match path.ancestors().last().and_then(Path::to_str) {
Some("etc" | "var") => {
let ext = if let Some(x) = path.extension().and_then(OsStr::to_str) {
format!("{x}.new")
} else {
"new".to_string()
};
let mut path = path.to_path_buf();
path.set_extension(ext);
path
}
_ => path.to_path_buf(),
}
} else {
path.to_path_buf()
}
}

View File

@ -9,7 +9,7 @@ mod version;
pub use { pub use {
hooks::Hooks, hooks::Hooks,
item::Item, item::Item,
package::{Dependency, Package, Specs}, package::{create as create_package, Dependency, Package, Specs},
plist::*, plist::*,
repository::Repository, repository::Repository,
tar, tar,

View File

@ -1,9 +1,25 @@
use crate::Item;
use std::path::PathBuf;
use zstd::Encoder;
mod dependency; mod dependency;
mod specs; mod specs;
use { use {
crate::{Plist, Version}, crate::{Entry, Plist, Version},
rayon::prelude::*,
ron::ser::{to_string_pretty, PrettyConfig},
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
std::{
error::Error,
fs::File,
io::{BufWriter, Write},
path::Path,
},
tar::Node,
walkdir::WalkDir,
}; };
pub use {dependency::Dependency, specs::Specs}; pub use {dependency::Dependency, specs::Specs};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@ -18,7 +34,7 @@ pub struct Group {
pub gid: Option<u32>, pub gid: Option<u32>,
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Package { pub struct Package {
/// The name of the package minus all version information /// The name of the package minus all version information
pub name: String, pub name: String,
@ -48,3 +64,83 @@ pub struct Package {
/// an optional post installation shell script to be run /// an optional post installation shell script to be run
pub post_install: Option<String>, pub post_install: Option<String>,
} }
impl From<Specs> for Package {
fn from(value: Specs) -> Self {
let mut package = Package::default();
package.name = value.name;
package.version = value.version;
package.release = value.release;
package.description = value.description;
package.long_description = value.long_description;
package.appstream_data = value.appstream_data;
package.dependencies = value.dependencies;
package.users = value.users;
package.groups = value.groups;
package.post_install = value.post_install;
package
}
}
impl Package {
fn as_ron(&self) -> Result<String, ron::Error> {
let cfg = PrettyConfig::new().struct_names(true);
to_string_pretty(self, cfg)
}
pub fn save_ron_and_create_tar_node(&self) -> Result<Node, Box<dyn Error>> {
let buf = File::open("package.ron")?;
let meta = buf.metadata()?;
let s = self.as_ron()?;
let mut writer = BufWriter::new(buf);
writer.write_all(s.as_bytes())?;
let node = Node::read_data_to_tar(s.as_bytes(), "package.ron", &meta, None)?;
Ok(node)
}
pub fn fullname(&self) -> String {
format!("{}-{}_{}", self.name, self.version, self.release)
}
}
pub fn create(path: &Path, specs: Specs) -> Result<(), Box<dyn Error>> {
let mut items = WalkDir::new(path)
.into_iter()
.collect::<Vec<_>>()
.par_iter()
.filter(|x| x.is_ok())
.filter_map(|x| {
Item::try_create(x.as_ref().unwrap().path().to_path_buf().as_path()).ok()
})
.collect::<Vec<_>>();
let mut plist = Plist::default();
let mut archive = vec![];
let mut totalsize = 0;
while let Some(item) = items.pop() {
match &item.entry {
Entry::File {
path: _,
sha256sum: _,
mode: _,
size,
} => totalsize += size,
_ => {}
}
plist.entries.push(item.entry);
archive.extend(item.data);
}
let mut package: Package = specs.into();
package.plist = plist;
package.size = totalsize;
let node = package.save_ron_and_create_tar_node()?.to_vec()?;
archive.extend(node);
archive.write_all(&[0; 9216])?;
let name = package.fullname();
let mut path = PathBuf::from(&name);
path.set_extension("tar.zst");
let fd = File::open(&path)?;
let mut writer = Encoder::new(fd, 0)?;
writer.write_all(&archive)?;
let _fd = writer.finish()?;
Ok(())
}

View File

@ -3,7 +3,7 @@ use {
crate::{Dependency, Version}, crate::{Dependency, Version},
clap::ArgMatches, clap::ArgMatches,
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
std::{env, error::Error}, std::{env, error::Error, ffi::OsStr},
}; };
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
@ -35,35 +35,30 @@ pub struct Specs {
impl TryFrom<&ArgMatches> for Specs { impl TryFrom<&ArgMatches> for Specs {
type Error = Box<dyn Error>; type Error = Box<dyn Error>;
#[allow(clippy::cast_possible_truncation)]
fn try_from(matches: &ArgMatches) -> Result<Self, Self::Error> { fn try_from(matches: &ArgMatches) -> Result<Self, Self::Error> {
let mut specs = Specs::default(); let mut specs = Specs::default();
let mut name: Option<String> = None; let mut name: Option<String> = None;
let mut version: Option<Version> = None; let mut version: Option<Version> = None;
let cwd = env::current_dir()?; let cwd = env::current_dir()?;
if let Some(dir) = cwd.file_name() { if let Some(dir) = cwd.file_name().and_then(OsStr::to_str) {
if let Some(n) = dir.to_str() { if let Some((n, v)) = dir.split_once('-') {
if let Some((n, v)) = n.split_once('-') {
name = Some(n.to_string()); name = Some(n.to_string());
if let Ok(v) = v.parse() { if let Ok(v) = v.parse() {
version = Some(v); version = Some(v);
} }
} }
} }
}
if let Some(name) = matches.get_one::<String>("name") { if let Some(name) = matches.get_one::<String>("name") {
specs.name = name.to_string(); specs.name = name.to_string();
} else { } else if let Some(n) = name {
if let Some(n) = name {
specs.name = n; specs.name = n;
} }
}
if let Some(version) = matches.get_one::<String>("package-version") { if let Some(version) = matches.get_one::<String>("package-version") {
specs.version = version.parse()?; specs.version = version.parse()?;
} else { } else if let Some(v) = version {
if let Some(v) = version {
specs.version = v; specs.version = v;
} }
}
if let Some(release) = matches.get_one::<usize>("release") { if let Some(release) = matches.get_one::<usize>("release") {
specs.release = *release as u8; specs.release = *release as u8;
} }

View File

@ -13,7 +13,7 @@ use {
walkdir::WalkDir, walkdir::WalkDir,
}; };
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Plist { pub struct Plist {
pub entries: Vec<Entry>, pub entries: Vec<Entry>,
} }

View File

@ -33,6 +33,33 @@ impl Default for Version {
} }
} }
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Number(n) => write!(f, "{n}"),
Self::SemVer {
major,
minor,
patch,
} => {
let v = SemVer {
major: *major,
minor: *minor,
patch: *patch,
};
write!(f, "{v}")
}
Self::Git { hash, datetime } => {
let v = GitRev {
hash: hash.to_owned(),
datetime: *datetime,
};
write!(f, "{v}")
}
}
}
}
impl From<SemVer> for Version { impl From<SemVer> for Version {
fn from(value: SemVer) -> Self { fn from(value: SemVer) -> Self {
Self::SemVer { Self::SemVer {
@ -45,7 +72,10 @@ impl From<SemVer> for Version {
impl From<GitRev> for Version { impl From<GitRev> for Version {
fn from(value: GitRev) -> Self { fn from(value: GitRev) -> Self {
Self::Git { hash: value.hash, datetime: value.datetime } Self::Git {
hash: value.hash,
datetime: value.datetime,
}
} }
} }
@ -56,6 +86,7 @@ impl From<u32> for Version {
} }
impl PartialOrd for Version { impl PartialOrd for Version {
#[allow(clippy::many_single_char_names)]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) { match (self, other) {
(Self::Number(s), Self::Number(o)) => s.partial_cmp(o), (Self::Number(s), Self::Number(o)) => s.partial_cmp(o),

View File

@ -70,7 +70,7 @@ impl FromStr for SemVer {
let split = s.split('.').collect::<Vec<_>>(); let split = s.split('.').collect::<Vec<_>>();
match split.len() { match split.len() {
3 => { 3 => {
let major = split.get(0).unwrap().parse::<u8>()?; let major = split.first().unwrap().parse::<u8>()?;
let minor = split.get(1).unwrap().parse::<u8>()?; let minor = split.get(1).unwrap().parse::<u8>()?;
let patch = split.get(2).unwrap().parse::<u8>()?; let patch = split.get(2).unwrap().parse::<u8>()?;
Ok(Self { Ok(Self {

View File

@ -5,6 +5,6 @@ edition = "2021"
license = "GPL-3.0-only" license = "GPL-3.0-only"
[dependencies] [dependencies]
deku = "0.16" deku = { workspace = true }
libc = "0.2" libc = "0.2"
thiserror = "1.0.40" thiserror = "1.0.40"

View File

@ -1,6 +1,6 @@
use std::{ use std::{
fs::File, fs::File,
io::{self, BufReader}, io::{self, BufReader, Write},
}; };
mod error; mod error;
@ -12,11 +12,21 @@ pub use {
node::Node, node::Node,
}; };
#[derive(Default)]
pub struct Archive { pub struct Archive {
pub nodes: Vec<Node>, pub nodes: Vec<Node>,
} }
impl Archive { impl Archive {
pub fn to_vec(self) -> Result<Vec<u8>, Error> {
let mut buf = vec![];
for node in self.nodes {
buf.extend(node.to_vec()?);
}
buf.write_all(&[0; 9216])?;
Ok(buf)
}
/// Write out a vector of `TarNodes` to a file or something that implements ``std::io::Write`` and ``std::io::Copy``. /// Write out a vector of `TarNodes` to a file or something that implements ``std::io::Write`` and ``std::io::Copy``.
/// ///
/// # Example /// # Example

View File

@ -13,6 +13,14 @@ pub struct Node {
} }
impl Node { impl Node {
pub fn to_vec(self) -> Result<Vec<u8>, DekuError> {
let mut buf = self.header.to_bytes()?;
for block in self.data {
buf.extend(block.to_vec());
}
Ok(buf)
}
/// Write out a single file within the tar to a file or something with a ``std::io::Write`` trait. /// Write out a single file within the tar to a file or something with a ``std::io::Write`` trait.
pub fn write<T: io::Write>(self, mut input: T) -> Result<usize, Error> { pub fn write<T: io::Write>(self, mut input: T) -> Result<usize, Error> {
input.write_all(&self.header.to_bytes()?)?; input.write_all(&self.header.to_bytes()?)?;
@ -26,7 +34,7 @@ impl Node {
} }
/// Read a TarNode in from a file or something with a ``std::io::Read`` trait. /// Read a TarNode in from a file or something with a ``std::io::Read`` trait.
pub fn read<T: io::Read>(mut input: T) -> Result<Node, Error> { pub fn read<T: io::Read>(mut input: T) -> Result<Self, Error> {
let mut h = vec![0u8; 512]; let mut h = vec![0u8; 512];
input.read_exact(&mut h)?; input.read_exact(&mut h)?;
@ -49,7 +57,7 @@ impl Node {
} }
/// Open and read a file from the ``filename`` argument to a TarNode. /// Open and read a file from the ``filename`` argument to a TarNode.
pub fn read_file_to_tar(filename: String) -> Result<Node, Error> { pub fn read_file_to_tar(filename: String) -> Result<Self, Error> {
let header = Header::new(&filename)?; let header = Header::new(&filename)?;
if header.link_indicator[0] != FileType::Normal as u8 { if header.link_indicator[0] != FileType::Normal as u8 {
return Ok(Node { return Ok(Node {
@ -72,7 +80,7 @@ impl Node {
filename: &str, filename: &str,
meta: &Metadata, meta: &Metadata,
owner: Option<Owner>, owner: Option<Owner>,
) -> Result<Node, Error> { ) -> Result<Self, Error> {
let header = Header::new_from_meta(filename, meta, owner)?; let header = Header::new_from_meta(filename, meta, owner)?;
if header.link_indicator[0] != FileType::Normal as u8 { if header.link_indicator[0] != FileType::Normal as u8 {
return Ok(Node { return Ok(Node {