Split out hpk-package into separate crate; Implement Repository::build()

This commit is contained in:
Nathan Fisher 2023-04-03 19:02:15 -04:00
parent 9126e1da93
commit 082c215b44
22 changed files with 108 additions and 1878 deletions

105
Cargo.lock generated
View File

@ -127,9 +127,9 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cpufeatures"
@ -226,7 +226,7 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
"syn 2.0.12",
"syn 2.0.13",
]
[[package]]
@ -243,7 +243,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
"syn 2.0.13",
]
[[package]]
@ -380,24 +380,36 @@ dependencies = [
name = "hpk"
version = "0.1.0"
dependencies = [
"chrono",
"deku",
"hpk-package",
"rayon",
"ron",
"serde",
"sha2",
"tar",
"ureq",
"url",
"walkdir",
"zstd",
]
[[package]]
name = "hpk-package"
version = "0.1.0"
source = "git+https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git#90b163eb1b7373085d3c5eb93919eb8ecfedb219"
dependencies = [
"chrono",
"deku",
"libc",
"rayon",
"ron",
"serde",
"sha2",
"thiserror",
"walkdir",
]
[[package]]
name = "iana-time-zone"
version = "0.1.55"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "716f12fbcfac6ffab0a5e9ec51d0a0ff70503742bb2dc7b99396394c9dc323f0"
checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@ -574,9 +586,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.54"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
@ -757,7 +769,7 @@ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
"syn 2.0.13",
]
[[package]]
@ -807,9 +819,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.12"
version = "2.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
dependencies = [
"proc-macro2",
"quote",
@ -822,15 +834,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.1.0"
dependencies = [
"deku",
"libc",
"thiserror",
]
[[package]]
name = "termcolor"
version = "1.2.0"
@ -857,7 +860,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
"syn 2.0.13",
]
[[package]]
@ -1110,9 +1113,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.47.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2649ff315bee4c98757f15dac226efe3d81927adbb6e882084bb1ee3e0c330a7"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets",
]
@ -1134,17 +1137,17 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.47.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f8996d3f43b4b2d44327cd71b7b0efd1284ab60e6e9d0e8b630e18555d87d3e"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.47.0",
"windows_aarch64_msvc 0.47.0",
"windows_i686_gnu 0.47.0",
"windows_i686_msvc 0.47.0",
"windows_x86_64_gnu 0.47.0",
"windows_x86_64_gnullvm 0.47.0",
"windows_x86_64_msvc 0.47.0",
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
@ -1155,9 +1158,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.47.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831d567d53d4f3cb1db332b68e6e2b6260228eb4d99a777d8b2e8ed794027c90"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
@ -1167,9 +1170,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.47.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a42d54a417c60ce4f0e31661eed628f0fa5aca73448c093ec4d45fab4c51cdf"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
@ -1179,9 +1182,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.47.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1925beafdbb22201a53a483db861a5644123157c1c3cee83323a2ed565d71e3"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
@ -1191,9 +1194,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.47.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8ef8f2f1711b223947d9b69b596cf5a4e452c930fb58b6fc3fdae7d0ec6b31"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
@ -1203,9 +1206,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.47.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acaa0c2cf0d2ef99b61c308a0c3dbae430a51b7345dedec470bd8f53f5a3642"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
@ -1215,9 +1218,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.47.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a0628f71be1d11e17ca4a0e9e15b3a5180f6fbf1c2d55e3ba3f850378052c1"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
@ -1227,9 +1230,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.47.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6e62c256dc6d40b8c8707df17df8d774e60e39db723675241e7c15e910bce7"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"

View File

@ -6,25 +6,12 @@ license = "GPL-3.0-only"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = [ "tar" ]
[workspace.dependencies]
deku = "0.16"
[dependencies]
deku.workspace = true
hpk-package = { git = "https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git" }
rayon = "1.7"
ron = "0.8"
sha2 = "0.10"
tar = { path = "tar" }
walkdir = "2.3"
zstd = "0.12"
[dependencies.chrono]
version = "0.4"
features = ["serde"]
[dependencies.serde]
version = "1.0"
features = ["derive"]

View File

@ -1,5 +1,6 @@
use {
crate::{Entry, Item, Package, Plist, Specs},
crate::{Item, Package, Plist, Specs},
hpk_package::Entry,
rayon::prelude::{IntoParallelRefIterator, ParallelIterator},
std::{
borrow::BorrowMut,

View File

@ -1,7 +1,7 @@
use {
crate::{Package, Repository, Version},
hpk_package::ron::{self, ser::PrettyConfig},
rayon::prelude::*,
ron::ser::PrettyConfig,
serde::{Deserialize, Serialize},
std::{
collections::HashMap,

View File

@ -1,5 +1,8 @@
use crate::Entry;
use sha2::{Digest, Sha256};
use hpk_package::{
sha2::{Digest, Sha256},
tar::{Node, Owner},
Entry,
};
use std::{
error::Error,
ffi::OsStr,
@ -9,7 +12,6 @@ use std::{
os::unix::fs::MetadataExt,
path::{Path, PathBuf},
};
use tar::{Node, Owner};
#[derive(Clone, Debug)]
pub struct Item {

View File

@ -3,10 +3,7 @@ mod creator;
mod db;
mod hooks;
mod item;
mod package;
mod plist;
mod repository;
mod version;
use std::path::PathBuf;
@ -14,12 +11,9 @@ pub use {
creator::{Creator, Message},
db::Database,
hooks::Hooks,
hpk_package::{tar, Dependency, GitRev, Package, Plist, Rapid, SemVer, Specs, Version},
item::Item,
package::{Dependency, Package, Specs},
plist::*,
repository::Repository,
tar,
version::*,
};
const DB: [&str; 4] = ["var", "db", "hpk", "db.ron.zstd"];

View File

@ -1,28 +0,0 @@
use {
super::Package,
crate::Version,
serde::{Deserialize, Serialize},
};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Dependency {
pub name: String,
pub version: (Option<Version>, Option<Version>),
}
impl Dependency {
#[allow(clippy::must_use_candidate)]
pub fn satisfied(&self, package: &Package) -> bool {
if self.name.as_str() == package.name.as_str() {
match &self.version {
(Some(low), Some(high)) => &package.version >= low && &package.version < high,
(Some(low), None) => &package.version >= low,
(None, Some(high)) => &package.version < high,
// no version requirements
_ => true,
}
} else {
false
}
}
}

View File

@ -1,122 +0,0 @@
mod dependency;
mod specs;
use {
crate::{Plist, Version},
ron::ser::{to_string_pretty, PrettyConfig},
serde::{Deserialize, Serialize},
std::{
error::Error,
fs,
fs::File,
io::{BufWriter, Write},
path::Path,
},
tar::{Node, Owner},
};
pub use {dependency::Dependency, specs::Specs};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct User {
pub name: String,
pub uid: Option<u32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Group {
pub name: String,
pub gid: Option<u32>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
/// the metadata associated with a package
pub struct Package {
/// The name of the package minus all version information
pub name: String,
/// The `Version` of the package
pub version: Version,
/// The release number for this package
pub release: u8,
/// a single line description of the package
pub description: String,
/// a more verbose description of the package
pub long_description: String,
/// an optional link to an
/// [AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/)
/// metadata file
pub appstream_data: Option<String>,
/// a listing of all files, directories and symlinks which are a part of
/// this package
pub plist: Plist,
/// the total size of this package
pub size: usize,
/// all of this package's runtime dependencies
pub dependencies: Vec<Dependency>,
/// an optional list of users to be created upon installation
pub users: Option<Vec<User>>,
/// an optional list of groups to be created upon installation
pub groups: Option<Vec<Group>>,
/// an optional post installation shell script to be run
pub post_install: Option<String>,
}
impl From<Specs> for Package {
fn from(value: Specs) -> Self {
Package {
name: value.name,
version: value.version,
release: value.release,
description: value.description,
long_description: value.long_description,
appstream_data: value.appstream_data,
dependencies: value.dependencies,
users: value.users,
groups: value.groups,
post_install: value.post_install,
..Default::default()
}
}
}
impl Package {
fn as_ron(&self) -> Result<String, ron::Error> {
let cfg = PrettyConfig::new().struct_names(true);
to_string_pretty(self, cfg)
}
pub(crate) fn save_ron_and_create_tar_node(
&self,
outdir: &Path,
) -> Result<Node, Box<dyn Error>> {
if !outdir.exists() {
fs::create_dir_all(outdir)?;
}
let mut outfile = outdir.to_path_buf();
outfile.push("package.ron");
let fd = File::create(&outfile)?;
let s = self.as_ron()?;
let mut writer = BufWriter::new(&fd);
writer.write_all(s.as_bytes())?;
writer.flush()?;
let node = Node::read_data_to_tar(
s.as_bytes(),
"package.ron",
&outfile.metadata()?,
Some(Owner::default()),
)?;
Ok(node)
}
/// Returns the formatted full package name including version and release strings
pub fn fullname(&self) -> String {
format!("{}-{}_{}", self.name, self.version, self.release)
}
/// Tests whether this package is an update for another
pub fn is_upgrade(&self, other: &Self) -> bool {
self.name == other.name
&& (self.version > other.version
|| (self.version == other.version && self.release > other.release))
}
}

View File

@ -1,31 +0,0 @@
use {
super::{Group, User},
crate::{Dependency, Version},
serde::{Deserialize, Serialize},
};
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Specs {
/// The name of the package minus all version information
pub name: String,
/// The `Version` of the package
pub version: Version,
/// The release number for this package
pub release: u8,
/// a single line description of the package
pub description: String,
/// a more verbose description of the package
pub long_description: String,
/// an optional link to an
/// [AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/)
/// metadata file
pub appstream_data: Option<String>,
/// all of this package's runtime dependencies
pub dependencies: Vec<Dependency>,
/// an optional list of users to be created upon installation
pub users: Option<Vec<User>>,
/// an optional list of groups to be created upon installation
pub groups: Option<Vec<Group>>,
/// an optional post installation shell script to be run
pub post_install: Option<String>,
}

View File

@ -1,91 +0,0 @@
use {
rayon::prelude::*,
serde::{Deserialize, Serialize},
sha2::{digest::Digest, Sha256},
std::{
error::Error,
fmt::Write,
fs,
io::Read,
os::unix::fs::MetadataExt,
path::{Path, PathBuf},
},
walkdir::WalkDir,
};
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Plist {
pub entries: Vec<Entry>,
}
impl TryFrom<&Path> for Plist {
type Error = Box<dyn Error>;
fn try_from(value: &Path) -> Result<Self, Self::Error> {
let entries = WalkDir::new(value)
.into_iter()
.collect::<Vec<_>>()
.par_iter()
.filter(|x| x.is_ok())
.filter_map(|x| {
Entry::try_from(x.as_ref().unwrap().path().to_path_buf().as_path()).ok()
})
.collect();
Ok(Self { entries })
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Entry {
File {
path: PathBuf,
sha256sum: String,
mode: u32,
size: usize,
},
Directory {
path: PathBuf,
mode: u32,
},
Link {
path: PathBuf,
target: PathBuf,
},
}
impl TryFrom<&Path> for Entry {
type Error = Box<dyn Error>;
fn try_from(value: &Path) -> Result<Self, Self::Error> {
let mut path = PathBuf::from("/");
path.push(value);
let meta = fs::metadata(value)?;
if meta.is_file() {
let mut buf = vec![];
let mut fd = fs::File::open(value)?;
let size = fd.read_to_end(&mut buf)?;
let mut sha256sum = String::new();
let mut hasher = Sha256::new();
hasher.update(&buf);
let res = hasher.finalize();
for c in res {
write!(sha256sum, "{c:02x}")?;
}
let mode = meta.mode();
Ok(Self::File {
path,
sha256sum,
mode,
size,
})
} else if meta.is_dir() {
let mode = meta.mode();
Ok(Self::Directory { path, mode })
} else if meta.is_symlink() {
let target = fs::read_link(value)?;
Ok(Self::Link { path, target })
} else {
unreachable!();
}
}
}

View File

@ -1,10 +1,15 @@
use std::sync::PoisonError;
use {
crate::Package,
hpk_package::ron,
rayon::prelude::*,
serde::{Deserialize, Serialize},
std::{collections::HashMap, sync::Mutex},
std::{
collections::HashMap,
error::Error,
io::Read,
sync::{mpsc::Sender, Mutex, PoisonError},
time::Duration,
},
url::Url,
};
@ -15,7 +20,43 @@ pub struct Repository {
pub packages: HashMap<String, Package>,
}
#[derive(Clone, Debug)]
pub enum Message {
ContentLength(usize),
BytesRead(usize),
Failure(String),
Success(usize),
}
impl Repository {
pub fn build(name: &str, url: &Url, sender: Sender<Message>) -> Result<Self, Box<dyn Error>> {
let url = url.join("packages.ron.zstd")?;
let resp = ureq::get(&url.to_string())
.timeout(Duration::from_secs(10))
.call()?;
if let Some(val) = resp.header("Content-Length") {
if let Ok(num) = val.parse::<usize>() {
sender.send(Message::ContentLength(num))?;
}
}
let reader = resp.into_reader();
let mut reader = zstd::Decoder::new(reader)?;
let mut buf = vec![];
loop {
let bytes = reader.read(&mut buf)?;
if bytes == 0 {
break;
}
sender.send(Message::BytesRead(bytes))?;
}
let packages = ron::de::from_bytes(&buf)?;
Ok(Self {
name: name.to_string(),
base_url: url.clone(),
packages,
})
}
pub fn search_names(&self, query: &str) -> Result<Vec<&Package>, PoisonError<Vec<&Package>>> {
let mut results = vec![];
if let Some(p) = self.packages.get(query) {

View File

@ -1,79 +0,0 @@
use {
crate::Version,
chrono::{offset::Utc, DateTime},
serde::{Deserialize, Serialize},
std::{cmp::Ordering, error::Error, fmt, str::FromStr},
};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct GitRev {
/// the short revision hash
pub hash: String,
/// the time of the revision commit
pub datetime: DateTime<Utc>,
}
impl fmt::Display for GitRev {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "git_{}.{}", self.hash, self.datetime.format("%Y%m%d"))
}
}
impl PartialOrd for GitRev {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.datetime.partial_cmp(&other.datetime)
}
}
impl Ord for GitRev {
fn cmp(&self, other: &Self) -> Ordering {
self.datetime.cmp(&other.datetime)
}
}
#[derive(Debug)]
pub struct ParseGitRevError;
impl fmt::Display for ParseGitRevError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error parsing git version")
}
}
impl Error for ParseGitRevError {}
impl From<chrono::ParseError> for ParseGitRevError {
fn from(_value: chrono::ParseError) -> Self {
Self
}
}
impl FromStr for GitRev {
type Err = ParseGitRevError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(gitrev) = s.strip_prefix("git_") {
if let Some((hash, date)) = gitrev.split_once('_') {
if hash.len() == 7 {
let datetime = DateTime::parse_from_str(date, "%Y%m%d")?;
return Ok(Self {
hash: hash.to_string(),
datetime: datetime.into(),
});
}
}
}
Err(ParseGitRevError)
}
}
impl TryFrom<Version> for GitRev {
type Error = ParseGitRevError;
fn try_from(value: Version) -> Result<Self, Self::Error> {
match value {
Version::Git(g) => Ok(g),
_ => Err(ParseGitRevError),
}
}
}

View File

@ -1,130 +0,0 @@
use {
serde::{Deserialize, Serialize},
std::{error::Error, fmt, str::FromStr},
};
mod gitrev;
mod rapid;
mod semver;
pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Version {
Number(u32),
Rapid(Rapid),
SemVer(SemVer),
Git(GitRev),
}
impl Default for Version {
fn default() -> Self {
Self::SemVer(SemVer {
major: 0,
minor: 1,
patch: 0,
})
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Number(n) => write!(f, "{n}"),
Self::SemVer(s) => write!(f, "{s}"),
Self::Rapid(r) => write!(f, "{r}"),
Self::Git(g) => write!(f, "{g}"),
}
}
}
impl From<SemVer> for Version {
fn from(value: SemVer) -> Self {
Self::SemVer(value)
}
}
impl From<Rapid> for Version {
fn from(value: Rapid) -> Self {
Self::Rapid(value)
}
}
impl From<GitRev> for Version {
fn from(value: GitRev) -> Self {
Self::Git(value)
}
}
impl From<u32> for Version {
fn from(value: u32) -> Self {
Self::Number(value)
}
}
impl PartialOrd for Version {
#[allow(clippy::many_single_char_names)]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(Self::Number(s), Self::Number(o)) => s.partial_cmp(o),
(Self::Number(s), Self::Rapid(o)) => s.partial_cmp(o),
(Self::Number(s), Self::SemVer(o)) => s.partial_cmp(o),
(Self::Rapid(s), Self::Number(o)) => s.partial_cmp(o),
(Self::Rapid(s), Self::Rapid(o)) => s.partial_cmp(o),
(Self::Rapid(s), Self::SemVer(o)) => s.partial_cmp(o),
(Self::SemVer(s), Self::Number(o)) => s.partial_cmp(o),
(Self::SemVer(s), Self::Rapid(o)) => s.partial_cmp(o),
(Self::SemVer(s), Self::SemVer(o)) => s.partial_cmp(o),
(Self::Git(s), Self::Git(o)) => s.partial_cmp(o),
_ => None,
}
}
}
#[derive(Debug)]
pub struct ParseVersionError;
impl fmt::Display for ParseVersionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "error parsing version")
}
}
impl Error for ParseVersionError {}
impl FromStr for Version {
type Err = ParseVersionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(v) = s.parse::<SemVer>() {
Ok(v.into())
} else if let Ok(v) = s.parse::<Rapid>() {
Ok(v.into())
} else if let Ok(v) = s.parse::<GitRev>() {
Ok(v.into())
} else if let Ok(v) = s.parse::<u32>() {
Ok(v.into())
} else {
Err(ParseVersionError)
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn cmp_semver_rapid_gt() {
let sem = "1.42.1".parse::<SemVer>().unwrap();
let rpd = "1.42".parse::<Rapid>().unwrap();
assert!(sem > rpd);
}
#[test]
fn cmp_semver_rapid_eq() {
let sem = "1.42.0".parse::<SemVer>().unwrap();
let rpd = "1.42".parse::<Rapid>().unwrap();
assert!(sem == rpd);
}
}

View File

@ -1,206 +0,0 @@
use {
chrono::{offset::Utc, DateTime},
serde::{Deserialize, Serialize},
std::{error::Error, fmt, str::FromStr},
};
mod gitrev;
mod rapid;
mod semver;
pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Version {
Number(u32),
Rapid {
major: u32,
minor: u32,
},
SemVer {
major: u32,
minor: u32,
patch: u32,
},
Git {
hash: String,
datetime: DateTime<Utc>,
},
}
impl Default for Version {
fn default() -> Self {
Self::SemVer {
major: 0,
minor: 1,
patch: 0,
}
}
}
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::Rapid { major, minor } => {
let v = Rapid {
major: *major,
minor: *minor,
};
write!(f, "{v}")
}
Self::Git { hash, datetime } => {
let v = GitRev {
hash: hash.clone(),
datetime: *datetime,
};
write!(f, "{v}")
}
}
}
}
impl From<SemVer> for Version {
fn from(value: SemVer) -> Self {
Self::SemVer {
major: value.major,
minor: value.minor,
patch: value.patch,
}
}
}
impl From<Rapid> for Version {
fn from(value: Rapid) -> Self {
Self::Rapid {
major: value.major,
minor: value.minor,
}
}
}
impl From<GitRev> for Version {
fn from(value: GitRev) -> Self {
Self::Git {
hash: value.hash,
datetime: value.datetime,
}
}
}
impl From<u32> for Version {
fn from(value: u32) -> Self {
Self::Number(value)
}
}
impl PartialOrd for Version {
#[allow(clippy::many_single_char_names)]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(Self::Number(s), Self::Number(o)) => s.partial_cmp(o),
(
Self::SemVer {
major,
minor,
patch,
},
Self::SemVer {
major: a,
minor: b,
patch: c,
},
) => (major, minor, patch).partial_cmp(&(a, b, c)),
(Self::Rapid { major, minor }, Self::Rapid { major: a, minor: b }) => {
(major, minor).partial_cmp(&(a, b))
}
(
Self::Git {
hash: _a,
datetime: b,
},
Self::Git {
hash: _c,
datetime: d,
},
) => b.partial_cmp(&d),
(
Self::SemVer {
major,
minor,
patch,
},
Self::Rapid { major: a, minor: b },
) => SemVer {
major: *major,
minor: *minor,
patch: *patch,
}
.partial_cmp(&Rapid {
major: *a,
minor: *b,
}),
_ => None,
}
}
}
#[derive(Debug)]
pub struct ParseVersionError;
impl fmt::Display for ParseVersionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "error parsing version")
}
}
impl Error for ParseVersionError {}
impl FromStr for Version {
type Err = ParseVersionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(v) = s.parse::<SemVer>() {
Ok(v.into())
} else if let Ok(v) = s.parse::<Rapid>() {
Ok(v.into())
} else if let Ok(v) = s.parse::<GitRev>() {
Ok(v.into())
} else if let Ok(v) = s.parse::<u32>() {
Ok(v.into())
} else {
Err(ParseVersionError)
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn cmp_semver_rapid_gt() {
let sem = "1.42.1".parse::<SemVer>().unwrap();
let rpd = "1.42".parse::<Rapid>().unwrap();
assert!(sem > rpd);
}
#[test]
fn cmp_semver_rapid_eq() {
let sem = "1.42.0".parse::<SemVer>().unwrap();
let rpd = "1.42".parse::<Rapid>().unwrap();
assert!(sem == rpd);
}
}

View File

@ -1,168 +0,0 @@
use crate::SemVer;
use {
crate::Version,
serde::{Deserialize, Serialize},
std::{cmp::Ordering, error::Error, fmt, num::ParseIntError, str::FromStr},
};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Rapid {
pub major: u32,
pub minor: u32,
}
impl fmt::Display for Rapid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}
impl PartialOrd for Rapid {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.major.partial_cmp(&other.major) {
Some(Ordering::Greater) => Some(Ordering::Greater),
Some(Ordering::Less) => Some(Ordering::Less),
Some(Ordering::Equal) => self.minor.partial_cmp(&other.minor),
None => None,
}
}
}
impl Ord for Rapid {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.major.cmp(&other.major) {
Ordering::Greater => Ordering::Greater,
Ordering::Less => Ordering::Less,
Ordering::Equal => self.minor.cmp(&other.minor),
}
}
}
impl PartialEq<u32> for Rapid {
fn eq(&self, other: &u32) -> bool {
self.major == *other && self.minor == 0
}
}
impl PartialOrd<u32> for Rapid {
fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
match self.major.partial_cmp(other) {
Some(Ordering::Greater) => Some(Ordering::Greater),
Some(Ordering::Less) => Some(Ordering::Less),
None => None,
Some(Ordering::Equal) => {
if self.minor == 0 {
Some(Ordering::Equal)
} else {
Some(Ordering::Greater)
}
}
}
}
}
impl PartialEq<SemVer> for Rapid {
fn eq(&self, other: &SemVer) -> bool {
other.eq(self)
}
}
impl PartialOrd<SemVer> for Rapid {
fn partial_cmp(&self, other: &SemVer) -> Option<Ordering> {
match other.partial_cmp(self) {
Some(Ordering::Less) => Some(Ordering::Greater),
Some(Ordering::Greater) => Some(Ordering::Less),
Some(Ordering::Equal) => Some(Ordering::Equal),
None => None,
}
}
}
impl PartialEq<Rapid> for u32 {
fn eq(&self, other: &Rapid) -> bool {
other.eq(self)
}
}
impl PartialOrd<Rapid> for u32 {
fn partial_cmp(&self, other: &Rapid) -> Option<Ordering> {
match other.partial_cmp(self) {
Some(Ordering::Equal) => Some(Ordering::Equal),
Some(Ordering::Less) => Some(Ordering::Greater),
Some(Ordering::Greater) => Some(Ordering::Less),
None => None,
}
}
}
#[derive(Debug, PartialEq)]
pub struct ParseRapidError;
impl fmt::Display for ParseRapidError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error parsing Rapid version")
}
}
impl Error for ParseRapidError {}
impl From<ParseIntError> for ParseRapidError {
fn from(_value: ParseIntError) -> Self {
Self
}
}
impl FromStr for Rapid {
type Err = ParseRapidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let split = s.split('.').collect::<Vec<_>>();
match split.len() {
2 => {
let major = split.first().unwrap().parse::<u32>()?;
let minor = split.get(1).unwrap().parse::<u32>()?;
Ok(Self { major, minor })
}
_ => Err(ParseRapidError),
}
}
}
impl TryFrom<Version> for Rapid {
type Error = ParseRapidError;
fn try_from(value: Version) -> Result<Self, Self::Error> {
match value {
Version::SemVer(s) => {
if s.patch == 0 {
Ok(Self {
major: s.major,
minor: s.minor,
})
} else {
Err(ParseRapidError)
}
}
Version::Rapid(s) => Ok(s),
Version::Number(major) => Ok(Self { major, minor: 0 }),
Version::Git(_) => Err(ParseRapidError),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_semver() {
assert_eq!(
"93.0".parse::<Rapid>(),
Ok(Rapid {
major: 93,
minor: 0,
})
);
}
}

View File

@ -1,191 +0,0 @@
use {
crate::{Rapid, Version},
serde::{Deserialize, Serialize},
std::{cmp::Ordering, error::Error, fmt, num::ParseIntError, str::FromStr},
};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct SemVer {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl fmt::Display for SemVer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl PartialOrd for SemVer {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.major.partial_cmp(&other.major) {
Some(Ordering::Greater) => Some(Ordering::Greater),
Some(Ordering::Less) => Some(Ordering::Less),
None => None,
Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) {
Some(Ordering::Greater) => Some(Ordering::Greater),
Some(Ordering::Less) => Some(Ordering::Less),
None => None,
Some(Ordering::Equal) => self.patch.partial_cmp(&other.patch),
},
}
}
}
impl Ord for SemVer {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.major.cmp(&other.major) {
Ordering::Greater => Ordering::Greater,
Ordering::Less => Ordering::Less,
Ordering::Equal => match self.minor.cmp(&other.minor) {
Ordering::Greater => Ordering::Greater,
Ordering::Less => Ordering::Less,
Ordering::Equal => self.patch.cmp(&other.patch),
},
}
}
}
impl PartialEq<Rapid> for SemVer {
fn eq(&self, other: &Rapid) -> bool {
self.major == other.major && self.minor == other.minor && self.patch == 0
}
}
impl PartialOrd<Rapid> for SemVer {
fn partial_cmp(&self, other: &Rapid) -> Option<std::cmp::Ordering> {
match self.major.partial_cmp(&other.major) {
Some(Ordering::Greater) => Some(Ordering::Greater),
Some(Ordering::Less) => Some(Ordering::Less),
None => None,
Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) {
Some(Ordering::Greater) => Some(Ordering::Greater),
Some(Ordering::Less) => Some(Ordering::Less),
None => None,
Some(Ordering::Equal) => match self.patch {
0 => Some(Ordering::Equal),
_ => Some(Ordering::Greater),
},
},
}
}
}
impl PartialEq<u32> for SemVer {
fn eq(&self, other: &u32) -> bool {
self.major == *other && self.minor == 0 && self.patch == 0
}
}
impl PartialOrd<u32> for SemVer {
fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
match self.major.cmp(other) {
Ordering::Greater => Some(Ordering::Greater),
Ordering::Less => Some(Ordering::Less),
Ordering::Equal => {
if self.minor == 0 && self.patch == 0 {
Some(Ordering::Equal)
} else {
Some(Ordering::Greater)
}
}
}
}
}
impl PartialEq<SemVer> for u32 {
fn eq(&self, other: &SemVer) -> bool {
other.eq(self)
}
}
impl PartialOrd<SemVer> for u32 {
fn partial_cmp(&self, other: &SemVer) -> Option<Ordering> {
match other.partial_cmp(self) {
Some(Ordering::Less) => Some(Ordering::Greater),
Some(Ordering::Greater) => Some(Ordering::Less),
Some(Ordering::Equal) => Some(Ordering::Equal),
None => None,
}
}
}
#[derive(Debug, PartialEq)]
pub struct ParseSemVerError;
impl fmt::Display for ParseSemVerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error parsing SemVer")
}
}
impl Error for ParseSemVerError {}
impl From<ParseIntError> for ParseSemVerError {
fn from(_value: ParseIntError) -> Self {
Self
}
}
impl FromStr for SemVer {
type Err = ParseSemVerError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let split = s.split('.').collect::<Vec<_>>();
match split.len() {
3 => {
let major = split.first().unwrap().parse::<u32>()?;
let minor = split.get(1).unwrap().parse::<u32>()?;
let patch = split.get(2).unwrap().parse::<u32>()?;
Ok(Self {
major,
minor,
patch,
})
}
_ => Err(ParseSemVerError),
}
}
}
impl TryFrom<Version> for SemVer {
type Error = ParseSemVerError;
fn try_from(value: Version) -> Result<Self, Self::Error> {
match value {
Version::SemVer(s) => Ok(SemVer {
major: s.major,
minor: s.minor,
patch: s.patch,
}),
Version::Rapid(r) => Ok(SemVer {
major: r.major,
minor: r.minor,
patch: 0,
}),
Version::Number(n) => Ok(Self {
major: n,
minor: 0,
patch: 0,
}),
Version::Git(_) => Err(ParseSemVerError),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_semver() {
assert_eq!(
"1.0.3".parse::<SemVer>(),
Ok(SemVer {
major: 1,
minor: 0,
patch: 3
})
);
}
}

View File

@ -1,10 +0,0 @@
[package]
name = "tar"
version = "0.1.0"
edition = "2021"
license = "GPL-3.0-only"
[dependencies]
deku = { workspace = true }
libc = "0.2"
thiserror = "1.0.40"

View File

@ -1,8 +0,0 @@
## About
Derived originally from [minitar](https://github.com/genonullfree/minitar), this
crate implements basic functionality for creating and extracting tar archives. It
has been adapted to allow creation of a Node (Tar header + 512byte blocks of data)
from the raw data plus file metadata. This allows for better efficiency when it
is embedded into another application (such as a package manager), as the raw data
and metadata about each file can be extracted once and reused for purposes such
as generating checksums, getting file sizes and creating packing lists.

View File

@ -1,22 +0,0 @@
use std::{fmt, io, num::ParseIntError, str::Utf8Error};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("DekuError: {0}")]
Deku(#[from] deku::DekuError),
#[error("IoError: {0}")]
Io(#[from] io::Error),
#[error("Error in conversion of oct_to_dev")]
Utf8Error(#[from] Utf8Error),
#[error("Error in conversion of oct_to_dev")]
ParseIntError(#[from] ParseIntError),
#[error("End of tar")]
EndOfTar,
#[error("Invalid magic")]
InvalidMagic,
#[error("Invalid Checksum")]
InvalidChecksum,
#[error("Parse int failed")]
Parse(#[from] fmt::Error),
}

View File

@ -1,441 +0,0 @@
use crate::Error;
use deku::prelude::*;
use std::{
env,
ffi::CStr,
fmt::{self, Write},
fs::{self, Metadata},
io,
ops::Deref,
os::{linux::fs::MetadataExt, unix::fs::FileTypeExt},
path::PathBuf,
};
#[repr(u8)]
pub enum FileType {
Normal = 0x30,
Hardlink = 0x31,
Symlink = 0x32,
Char = 0x33,
Block = 0x34,
Dir = 0x35,
FIFO = 0x36,
Unknown = 0x00,
}
impl<T> From<T> for FileType
where
T: Deref<Target = Metadata>,
{
fn from(meta: T) -> Self {
if meta.is_dir() {
return FileType::Dir;
}
let file_type = meta.file_type();
if file_type.is_fifo() {
return FileType::FIFO;
} else if file_type.is_char_device() {
return FileType::Char;
} else if file_type.is_block_device() {
return FileType::Block;
} else if file_type.is_fifo() {
return FileType::FIFO;
} else if file_type.is_symlink() {
return FileType::Symlink;
} else if file_type.is_file() {
return FileType::Normal;
}
FileType::Unknown
}
}
#[derive(Clone)]
pub struct Owner {
pub uid: u32,
pub gid: u32,
pub username: String,
pub groupname: String,
}
impl Default for Owner {
fn default() -> Self {
Self {
uid: 0,
gid: 0,
username: "root".into(),
groupname: "root".into(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "little")]
pub struct Header {
pub(crate) fname: [u8; 100],
pub(crate) mode: [u8; 8],
pub(crate) uid: [u8; 8],
pub(crate) gid: [u8; 8],
pub(crate) size: [u8; 12],
pub(crate) mtime: [u8; 12],
pub(crate) header_checksum: [u8; 8],
pub(crate) link_indicator: [u8; 1],
pub(crate) link_name: [u8; 100],
pub(crate) ustar_magic: [u8; 6],
pub(crate) ustar_version: [u8; 2],
pub(crate) username: [u8; 32],
pub(crate) groupname: [u8; 32],
pub(crate) device_major: [u8; 8],
pub(crate) device_minor: [u8; 8],
pub(crate) file_prefix: [u8; 155],
pub(crate) reserved: [u8; 12],
}
impl Default for Header {
fn default() -> Self {
Self {
fname: [0; 100],
mode: [0; 8],
uid: [0; 8],
gid: [0; 8],
size: [0; 12],
mtime: [0; 12],
header_checksum: [0x20; 8],
link_indicator: [0; 1],
link_name: [0; 100],
ustar_magic: [0x75, 0x73, 0x74, 0x61, 0x72, 0x20],
ustar_version: [0x20, 0x00],
username: [0; 32],
groupname: [0; 32],
device_major: [0; 8],
device_minor: [0; 8],
file_prefix: [0; 155],
reserved: [0; 12],
}
}
}
impl Header {
pub fn filename(&self) -> Result<String, fmt::Error> {
let mut s = String::new();
for c in self.fname {
if c != b'\0' {
write!(s, "{c}")?;
} else {
break;
}
}
Ok(s)
}
pub fn mode(&self) -> Result<u32, Error> {
let mut s = String::new();
for c in self.mode {
if c != b'\0' {
write!(s, "{c}")?;
} else {
break;
}
}
let mode = u32::from_str_radix(&s, 8)?;
Ok(mode)
}
fn uid(&self) -> Result<u32, Error> {
let mut s = String::new();
for c in self.mode {
if c != b'\0' {
write!(s, "{c}")?;
} else {
break;
}
}
let uid = u32::from_str_radix(&s, 8)?;
Ok(uid)
}
fn gid(&self) -> Result<u32, Error> {
let mut s = String::new();
for c in self.mode {
if c != b'\0' {
write!(s, "{c}")?;
} else {
break;
}
}
let gid = u32::from_str_radix(&s, 8)?;
Ok(gid)
}
fn username(&self) -> Result<String, fmt::Error> {
let mut s = String::new();
for c in self.username {
if c != b'\0' {
write!(s, "{c}")?;
} else {
break;
}
}
Ok(s)
}
fn groupname(&self) -> Result<String, fmt::Error> {
let mut s = String::new();
for c in self.groupname {
if c != b'\0' {
write!(s, "{c}")?;
} else {
break;
}
}
Ok(s)
}
pub fn owner(&self) -> Result<Owner, Error> {
let uid = self.uid()?;
let gid = self.gid()?;
let username = self.username()?;
let groupname = self.groupname()?;
Ok(Owner {
uid,
gid,
username,
groupname,
})
}
pub fn prefix(&self) -> Result<String, fmt::Error> {
let mut s = String::new();
for c in self.file_prefix {
if c != b'\0' {
write!(s, "{c}")?;
} else {
break;
}
}
Ok(s)
}
pub fn new(filename: &str) -> Result<Self, Error> {
let mut header = Header::default();
let meta = fs::symlink_metadata(filename)?;
let (filename, prefix) = if filename.len() < 100 {
(filename.to_string(), None)
} else {
// Deal with file names longer than 100 bytes
let path = PathBuf::from(&filename);
let name = match path.file_name().and_then(|n| n.to_str()) {
Some(n) => n.to_string(),
None => {
return Err(Error::Io(io::Error::new(
io::ErrorKind::Other,
"Cannot get file name",
)))
}
};
let dir = match path.parent() {
Some(d) => d,
None => {
return Err(Error::Io(io::Error::new(
io::ErrorKind::Other,
"Cannot get path prefix",
)))
}
};
(name, Some(format!("{}", dir.display())))
};
/* Fill in metadata */
header.fname[..filename.len()].copy_from_slice(filename.as_bytes());
let mode = format!("{:07o}", (meta.st_mode() & 0o777));
header.mode[..mode.len()].copy_from_slice(mode.as_bytes());
let user = format!("{:07o}", meta.st_uid());
header.uid[..user.len()].copy_from_slice(user.as_bytes());
let group = format!("{:07o}", meta.st_gid());
header.gid[..group.len()].copy_from_slice(group.as_bytes());
let size = format!("{:011o}", meta.st_size());
header.size[..size.len()].copy_from_slice(size.as_bytes());
let mtime = format!("{:011o}", meta.st_mtime());
header.mtime[..mtime.len()].copy_from_slice(mtime.as_bytes());
if let Some(prefix) = prefix {
header.file_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes());
}
/* Get the file type and conditional metadata */
header.link_indicator[0] = FileType::from(&meta) as u8;
if header.link_indicator[0] == FileType::Symlink as u8 {
let link = fs::read_link(filename)?.to_str().unwrap().to_string();
header.link_name[..link.len()].copy_from_slice(link.as_bytes());
} else if header.link_indicator[0] == FileType::Block as u8 {
let major = format!("{:07o}", meta.st_dev());
header.device_major[..major.len()].copy_from_slice(major.as_bytes());
let minor = format!("{:07o}", meta.st_rdev());
header.device_minor[..minor.len()].copy_from_slice(minor.as_bytes());
}
/* TODO: Find better way to get username */
let key = "USER";
if let Ok(val) = env::var(key) {
header.username[..val.len()].copy_from_slice(val.as_bytes())
}
/* TODO: Find way to get groupname */
/* Update the header checksum value */
header.update_checksum()?;
Ok(header)
}
pub fn new_from_meta(
filename: &str,
meta: &Metadata,
owner: Option<Owner>,
) -> Result<Self, Error> {
let mut header = Header::default();
let (filename, prefix) = if filename.len() < 100 {
(filename.to_string(), None)
} else {
// Deal with file names longer than 100 bytes
let path = PathBuf::from(&filename);
let name = match path.file_name().and_then(|n| n.to_str()) {
Some(n) => n.to_string(),
None => {
return Err(Error::Io(io::Error::new(
io::ErrorKind::Other,
"Cannot get file name",
)))
}
};
let dir = match path.parent() {
Some(d) => d,
None => {
return Err(Error::Io(io::Error::new(
io::ErrorKind::Other,
"Cannot get path prefix",
)))
}
};
(name, Some(format!("{}", dir.display())))
};
header.fname[..filename.len()].copy_from_slice(filename.as_bytes());
let mode = format!("{:07o}", meta.st_mode());
header.mode[..mode.len()].copy_from_slice(mode.as_bytes());
let owner = match owner {
Some(o) => o,
None => Owner {
uid: meta.st_uid(),
gid: meta.st_gid(),
username: get_username_for_uid(meta.st_uid())?.into(),
groupname: get_groupname_for_gid(meta.st_gid())?.into(),
},
};
let uid = format!("{:07o}", owner.uid);
header.uid[..uid.len()].copy_from_slice(uid.as_bytes());
let gid = format!("{:07o}", owner.gid);
header.gid[..gid.len()].copy_from_slice(gid.as_bytes());
let size = format!("{:011o}", meta.len());
header.size[..size.len()].copy_from_slice(size.as_bytes());
let mtime = format!("{:011o}", meta.st_mtime());
header.mtime[..mtime.len()].copy_from_slice(mtime.as_bytes());
if let Some(prefix) = prefix {
header.file_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes());
}
header.link_indicator[0] = FileType::from(meta) as u8;
if header.link_indicator[0] == FileType::Symlink as u8 {
let link = fs::read_link(filename)?.to_str().unwrap().to_string();
header.link_name[..link.len()].copy_from_slice(link.as_bytes());
} else if header.link_indicator[0] == FileType::Block as u8 {
let major = format!("{:07o}", meta.st_dev());
header.device_major[..major.len()].copy_from_slice(major.as_bytes());
let minor = format!("{:07o}", meta.st_rdev());
header.device_minor[..minor.len()].copy_from_slice(minor.as_bytes());
}
header.username[..owner.username.len()].copy_from_slice(owner.username.as_bytes());
header.groupname[..owner.groupname.len()].copy_from_slice(owner.groupname.as_bytes());
header.update_checksum()?;
Ok(header)
}
/// Validates that the magic value received matches the magic value required in the Tar specification.
///
/// # Example
///
/// ```
/// use tar::tar::Header;
/// let header = Header::default();
/// if !header.validate_magic() {
/// println!("Magic value is invalid");
/// }
/// ```
pub fn validate_magic(self) -> bool {
self.ustar_magic == "ustar ".as_bytes()
}
/// Validates the header checksum computes to the expected value.
///
/// # Example
///
/// ```
/// use tar::tar::Header;
/// let header = Header::default();
/// if header.validate_checksum().unwrap() {
/// println!("Checksum is valid");
/// }
/// ```
pub fn validate_checksum(self) -> Result<bool, Error> {
let mut test = self;
let mut new = [0x20u8; 8];
test.header_checksum.copy_from_slice(&[0x20; 8]);
let tmp = format!("{:06o}\x00", test.calc_checksum()?);
new[..tmp.len()].copy_from_slice(tmp.as_bytes());
Ok(self.header_checksum == new)
}
/// Updates the header checksum value.
///
/// # Example
///
/// ```
/// use tar::tar::Header;
/// let mut header = Header::default();
///
/// /* Fill in header information */
///
/// header.update_checksum();
/// ```
pub fn update_checksum(&mut self) -> Result<(), Error> {
let checksum = format!("{:06o}\x00", self.calc_checksum()?);
self.header_checksum[..checksum.len()].copy_from_slice(checksum.as_bytes());
Ok(())
}
fn calc_checksum(self) -> Result<usize, Error> {
let out = self.to_bytes()?;
let mut checksum = 0;
for i in out {
checksum += i as usize;
}
Ok(checksum)
}
}
fn get_username_for_uid<'a>(uid: u32) -> Result<&'a str, std::str::Utf8Error> {
let user = unsafe {
let pw = libc::getpwuid(uid);
let name = (*pw).pw_name;
CStr::from_ptr(name)
};
user.to_str()
}
pub fn get_groupname_for_gid<'a>(gid: u32) -> Result<&'a str, std::str::Utf8Error> {
let group = unsafe {
let gr = libc::getgrgid(gid);
let name = (*gr).gr_name;
CStr::from_ptr(name)
};
group.to_str()
}

View File

@ -1,134 +0,0 @@
use std::{
fs::File,
io::{self, BufReader, Write},
};
mod error;
mod header;
mod node;
pub use {
error::Error,
header::{FileType, Header, Owner},
node::Node,
};
#[derive(Default)]
pub struct Archive {
pub nodes: Vec<Node>,
}
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``.
///
/// # Example
///
/// ```
/// use std::fs::File;
/// use tar::tar::TarFile;
///
/// let data = Archive::new("test/1.txt".to_string()).unwrap();
///
/// let out = File::create("test/2.tar".to_string()).unwrap();
/// data.write(&out).unwrap();
/// ```
pub fn write<T: io::Write + Copy>(self, mut input: T) -> Result<usize, Error> {
let mut written = 0;
for f in self.nodes.clone() {
written += f.write(input)?;
}
/* Complete the write with 18 blocks of 512 ``0x00`` bytes per the specification */
if !self.nodes.is_empty() {
input.write_all(&[0; 9216])?;
written += 9216;
}
Ok(written)
}
/// Create a new `TarFile` struct and initialize it with a `filename` file. This will read in the file to
/// the `TarFile` struct as a `TarNode`.
///
/// # Example
///
/// ```
/// use tar::Archive;
///
/// let data = Archive::new("test/1.txt".to_string()).unwrap();
/// ```
pub fn new(filename: &str) -> Result<Self, Error> {
Ok(Self {
nodes: vec![Node::read_file_to_tar(filename)?],
})
}
/// Append another file to the `TarFile.file` vector. This adds a file to the internal representation of the tar file.
///
/// # Example
///
/// ```
/// use tar::Archive;
///
/// let mut data = Archive::new("test/1.txt".to_string()).unwrap();
/// data.append("test/1.txt".to_string()).unwrap();
/// ```
pub fn append(&mut self, filename: &str) -> Result<(), Error> {
self.nodes.push(Node::read_file_to_tar(filename)?);
Ok(())
}
/// Open and load an external tar file into the internal `TarFile` struct. This parses and loads up all the files
/// contained within the external tar file.
///
/// # Example
///
/// ```
/// use tar::Archive;
///
/// Archive::open("test/1.tar".to_string()).unwrap();
/// ```
pub fn open(filename: String) -> Result<Self, Error> {
let file = File::open(&filename)?;
let mut reader = BufReader::new(file);
let mut out = Self {
nodes: Vec::<Node>::new(),
};
while let Ok(t) = Node::read(&mut reader) {
out.nodes.push(t);
}
Ok(out)
}
/// Remove the first file from the Tar that matches the filename and path.
///
/// # Example
///
/// ```
/// use minitar::tar::TarFile;
///
/// let mut data = TarFile::new("test/1.tar".to_string()).unwrap();
/// data.remove("test/1.tar".to_string()).unwrap();
/// ```
pub fn remove(&mut self, filename: String) -> Result<bool, Error> {
let mut name = [0u8; 100];
name[..filename.len()].copy_from_slice(filename.as_bytes());
if let Some(i) = &self.nodes.iter().position(|x| x.header.fname == name) {
self.nodes.remove(*i);
return Ok(true);
}
Ok(false)
}
}

View File

@ -1,137 +0,0 @@
use crate::{header::Owner, Error, FileType, Header};
use deku::prelude::*;
use std::{
fs::{File, Metadata},
io::{self, BufReader},
str,
};
#[derive(Clone, Debug, Default)]
pub struct Node {
pub header: Header,
pub data: Vec<[u8; 512]>,
}
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.
pub fn write<T: io::Write>(self, mut input: T) -> Result<usize, Error> {
input.write_all(&self.header.to_bytes()?)?;
let mut written = 512;
for d in self.data {
input.write_all(&d)?;
written += d.len();
}
Ok(written)
}
/// 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<Self, Error> {
let mut h = vec![0u8; 512];
input.read_exact(&mut h)?;
let (_, header) = Header::from_bytes((&h, 0))?;
if header == Header::default() {
return Err(Error::EndOfTar);
}
if !header.validate_magic() {
return Err(Error::InvalidMagic);
}
if !header.validate_checksum()? {
return Err(Error::InvalidChecksum);
}
let chunks = (oct_to_dec(&header.size)? / 512) + 1;
Ok(Node {
header,
data: Node::chunk_file(&mut input, Some(chunks))?,
})
}
/// Open and read a file from the ``filename`` argument to a TarNode.
pub fn read_file_to_tar(filename: &str) -> Result<Self, Error> {
let header = Header::new(filename)?;
if header.link_indicator[0] != FileType::Normal as u8 {
return Ok(Node {
header,
data: Vec::<[u8; 512]>::new(),
});
}
let file = File::open(filename)?;
let mut reader = BufReader::new(file);
Ok(Node {
header,
data: Node::chunk_file(&mut reader, None)?,
})
}
/// Create a Node from in memory data, given the filename and metadata
pub fn read_data_to_tar(
data: &[u8],
filename: &str,
meta: &Metadata,
owner: Option<Owner>,
) -> Result<Self, Error> {
let header = Header::new_from_meta(filename, meta, owner)?;
if header.link_indicator[0] != FileType::Normal as u8 {
return Ok(Node {
header,
data: Vec::<[u8; 512]>::new(),
});
}
let mut reader = BufReader::new(data);
Ok(Node {
header,
data: Node::chunk_file(&mut reader, None)?,
})
}
/// Read in and split a file into ``512`` byte chunks.
fn chunk_file<T: std::io::Read>(
file: &mut T,
max_chunks: Option<usize>,
) -> Result<Vec<[u8; 512]>, Error> {
/* Extract the file data from the tar file */
let mut out = Vec::<[u8; 512]>::new();
let mut n = if let Some(max) = max_chunks {
max
} else {
usize::MAX
};
/* Carve out 512 bytes at a time */
let mut buf: [u8; 512] = [0; 512];
loop {
let len = file.read(&mut buf)?;
n -= 1;
/* If read len == 0, we've hit the EOF */
if len == 0 || n == 0 {
break;
}
/* Save this chunk */
out.push(buf);
}
Ok(out)
}
}
fn oct_to_dec(input: &[u8]) -> Result<usize, Error> {
/* Convert the &[u8] to string and remove the null byte */
let mut s = str::from_utf8(input)?.to_string();
s.pop();
/* Convert to usize from octal */
Ok(usize::from_str_radix(&s, 8)?)
}