use {
crate::{
control::{
ControlField, ControlParser, ControlStanza, Field, FindFields, MutableControlFile,
MutableControlStanza, ParseError,
},
hash::Hash,
indexfile::IndexFile,
version::{
Constraint, Dependency, ParsedConstraintIterator, ParsedDependencyIterator,
ParsedProvidedNameIterator, ProvidedName, Version,
},
SafeStoreFile,
},
futures::AsyncWriteExt,
ouroboros::self_referencing,
serde::{Deserialize, Serialize},
smol::io::{AsyncRead, AsyncReadExt},
std::{fmt, io, sync::Arc},
};
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum PackageOrigin {
#[default]
Unknown,
Local {
manifest_id: u32,
},
Archive {
manifest_id: u32,
archive_id: u32,
},
}
impl PackageOrigin {
pub const fn manifest(&self) -> Option<u32> {
match self {
Self::Unknown => None,
Self::Local { manifest_id } | Self::Archive { manifest_id, .. } => Some(*manifest_id),
}
}
pub const fn archive(&self) -> Option<u32> {
match self {
Self::Archive { archive_id, .. } => Some(*archive_id),
Self::Unknown | Self::Local { .. } => None,
}
}
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown)
}
pub const fn legacy_local() -> Self {
Self::Local { manifest_id: 0 }
}
}
impl Serialize for PackageOrigin {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Unknown => serializer.serialize_str(""),
Self::Local { manifest_id } => serializer.serialize_str(&format!(":{manifest_id}")),
Self::Archive {
manifest_id,
archive_id,
} => serializer.serialize_str(&format!(":{manifest_id}:{archive_id}")),
}
}
}
impl<'de> Deserialize<'de> for PackageOrigin {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct PackageOriginVisitor;
impl PackageOriginVisitor {
fn parse_u32<E: serde::de::Error>(value: &str, field: &str) -> Result<u32, E> {
value
.parse()
.map_err(|err| E::custom(format!("invalid {field} {value:?}: {err}")))
}
fn parse_str<E: serde::de::Error>(value: &str) -> Result<PackageOrigin, E> {
if value.is_empty() {
return Ok(PackageOrigin::Unknown);
}
let rest = value.strip_prefix(':').ok_or_else(|| {
E::custom(
"package origin string must be empty or begin with ':' \
(expected ':<manifest>' or ':<manifest>:<archive>')",
)
})?;
let mut parts = rest.split(':');
let manifest_id = Self::parse_u32(
parts
.next()
.ok_or_else(|| E::custom("missing manifest id"))?,
"manifest id",
)?;
match (parts.next(), parts.next()) {
(None, None) => Ok(PackageOrigin::Local { manifest_id }),
(Some(archive_id), None) => Ok(PackageOrigin::Archive {
manifest_id,
archive_id: Self::parse_u32(archive_id, "archive id")?,
}),
_ => Err(E::custom(
"package origin string must be ':<manifest>' or ':<manifest>:<archive>'",
)),
}
}
}
impl<'de> serde::de::Visitor<'de> for PackageOriginVisitor {
type Value = PackageOrigin;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"an origin integer, an empty string, ':<manifest>', or ':<manifest>:<archive>'"
)
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(PackageOrigin::Archive {
manifest_id: 0,
archive_id: value.try_into().map_err(|_| {
E::custom(format!("archive id {value} does not fit into u32"))
})?,
})
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let archive_id: u64 = value
.try_into()
.map_err(|_| E::custom("archive id must be non-negative"))?;
self.visit_u64(archive_id)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::parse_str(value)
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::parse_str(value.as_str())
}
}
deserializer.deserialize_any(PackageOriginVisitor)
}
}
pub struct MemoryMappedUniverseFile {
mmap: Arc<memmap2::Mmap>,
begin: usize,
end: usize,
}
impl AsRef<str> for MemoryMappedUniverseFile {
fn as_ref(&self) -> &str {
let slice = &self.mmap[self.begin..self.end];
unsafe { std::str::from_utf8_unchecked(slice) }
}
}
impl MemoryMappedUniverseFile {
pub async fn store<P: AsRef<std::path::Path>>(
path: P,
arch: &str,
packages: &[Packages],
) -> io::Result<()> {
let count = packages.len() as u32;
let mut index_off = 4;
let mut off = index_off + (count as usize) * 12 + 1 + arch.len();
let mut header = vec![0u8; off];
header[0..4].copy_from_slice(&count.to_le_bytes());
for pkg in packages.iter() {
let begin = off as u32;
let end = begin + (pkg.inner.with_data(|d| d.as_str().len()) as u32);
let prio = pkg.prio;
header[index_off..index_off + 4].copy_from_slice(&begin.to_le_bytes());
header[index_off + 4..index_off + 8].copy_from_slice(&end.to_le_bytes());
header[index_off + 8..index_off + 12].copy_from_slice(&prio.to_le_bytes());
index_off += 12;
off = end as usize;
}
header[index_off] = arch.len() as u8;
header[index_off + 1..index_off + 1 + arch.len()].copy_from_slice(arch.as_bytes());
let mut file = SafeStoreFile::new(path).await?;
file.set_len(off as u64).await?;
file.as_mut().write_all(&header).await?;
for pkg in packages.iter() {
let data = pkg.inner.with_data(|d| d.as_str().as_bytes());
file.as_mut().write_all(data).await?;
}
Ok(())
}
pub fn open<P: AsRef<std::path::Path>>(path: P) -> io::Result<(String, Vec<Packages>)> {
let file = std::fs::File::open(path)?;
let mmap = Arc::new(unsafe { memmap2::MmapOptions::new().map(&file)? });
if mmap.len() < 4 {
return Err(io::Error::other(
"Universe file is too small to contain header",
));
}
let count = u32::from_le_bytes([mmap[0], mmap[1], mmap[2], mmap[3]]);
let mut index_off = 4;
let mut off = 4 + (count as usize) * 12;
if mmap.len() < off {
return Err(io::Error::other(
"Universe file is too small to contain data",
));
}
let arch_len = mmap[off] as usize;
if mmap.len() < off + 1 + arch_len {
return Err(io::Error::other(
"Universe file is too small to contain architecture",
));
}
let arch = std::str::from_utf8(&mmap[off + 1..off + 1 + arch_len])
.map_err(|err| {
io::Error::other(format!(
"Universe file architecture is not valid UTF-8: {}",
err
))
})?
.to_string();
off += 1 + arch_len;
std::str::from_utf8(&mmap[off..]).map_err(|err| {
io::Error::other(format!("Packages file is not valid UTF-8: {}", err))
})?;
let mut files = Vec::with_capacity(count as usize);
for i in 0..count {
let begin = u32::from_le_bytes([
mmap[index_off],
mmap[index_off + 1],
mmap[index_off + 2],
mmap[index_off + 3],
]) as usize;
if begin != off {
return Err(io::Error::other(format!("Universe file has invalid index ({i}: count={count}, begin={begin}, off={off}, len={}", mmap.len())));
}
let end = u32::from_le_bytes([
mmap[index_off + 4],
mmap[index_off + 5],
mmap[index_off + 6],
mmap[index_off + 7],
]) as usize;
if begin > end || end > mmap.len() {
return Err(io::Error::other(format!("Universe file has invalid index ({i}: count={count}, begin={begin}, end={end}, len={}", mmap.len())));
}
let prio = u32::from_le_bytes([
mmap[index_off + 8],
mmap[index_off + 9],
mmap[index_off + 10],
mmap[index_off + 11],
]);
off = end;
files.push(
Packages::new(
IndexFile::mmap_region(Arc::clone(&mmap), begin, end)?,
PackageOrigin::Unknown,
Some(prio),
)
.map_err(io::Error::other)?,
);
index_off += 12;
}
Ok((arch, files))
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum Priority {
#[default]
Unknown,
Optional,
Standard,
Important,
Required,
}
impl std::fmt::Display for Priority {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Priority::Required => write!(f, "required"),
Priority::Important => write!(f, "important"),
Priority::Standard => write!(f, "standard"),
Priority::Optional => write!(f, "optional"),
Priority::Unknown => write!(f, "unknown"),
}
}
}
impl From<&str> for Priority {
fn from(value: &str) -> Self {
if value.eq_ignore_ascii_case("required") {
Priority::Required
} else if value.eq_ignore_ascii_case("important") {
Priority::Important
} else if value.eq_ignore_ascii_case("standard") {
Priority::Standard
} else if value.eq_ignore_ascii_case("optional") || value.eq_ignore_ascii_case("extra") {
Priority::Optional
} else {
Priority::Unknown
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstallPriority {
Essential,
Required,
Other,
}
impl InstallPriority {
pub fn rank(&self) -> u8 {
match self {
InstallPriority::Essential => 0,
InstallPriority::Required => 1,
InstallPriority::Other => 2,
}
}
}
impl AsRef<str> for InstallPriority {
fn as_ref(&self) -> &str {
match self {
InstallPriority::Essential => "essential",
InstallPriority::Required => "required",
InstallPriority::Other => "other",
}
}
}
impl std::fmt::Display for InstallPriority {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
InstallPriority::Essential => write!(f, "essential"),
InstallPriority::Required => write!(f, "required"),
InstallPriority::Other => write!(f, "other"),
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum MultiArch {
#[default]
Same,
Foreign,
Allowed,
}
impl From<&str> for MultiArch {
fn from(value: &str) -> Self {
if value.eq_ignore_ascii_case("foreign") {
Self::Foreign
} else if value.eq_ignore_ascii_case("allowed") {
Self::Allowed
} else {
Self::Same
}
}
}
#[derive(Default, Clone, Debug)]
pub struct Package<'a> {
src: &'a str,
name: &'a str,
version: &'a str,
arch: &'a str,
provides: Option<&'a str>,
depends: Option<&'a str>,
pre_depends: Option<&'a str>,
conflicts: Option<&'a str>,
breaks: Option<&'a str>,
essential: bool,
priority: Priority,
multi_arch: MultiArch,
}
impl std::fmt::Display for Package<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}={}", self.name, self.arch, self.version)
}
}
impl<'a> Package<'a> {
pub fn repo_file(&self, hash_field_name: &'static str) -> io::Result<(&'a str, u64, Hash)> {
let (path, size, digest) = self
.fields()
.find_fields(("Filename", "Size", hash_field_name))
.map_err(|err| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("package {} lacks field {}", self, err),
)
})?;
Ok((
path,
crate::parse_size(size.as_bytes())?,
Hash::from_hex(hash_field_name, digest)?,
))
}
pub fn filename(&self) -> io::Result<&'a str> {
self.field("Filename").ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("package {} lacks field Filename", self),
)
})
}
pub fn src(&self) -> &'a str {
self.src
}
pub fn name(&self) -> &'a str {
self.name
}
pub fn arch(&self) -> &'a str {
self.arch
}
pub fn raw_full_name(&self) -> ProvidedName<&'a str> {
ProvidedName::Exact(self.name, Version::new(self.version))
}
pub fn full_name(&self) -> std::result::Result<ProvidedName<&'a str>, ParseError> {
Ok(ProvidedName::Exact(
self.name,
Version::try_from(self.version)?,
))
}
pub fn provides(
&self,
) -> impl Iterator<Item = std::result::Result<ProvidedName<&'a str>, ParseError>> {
ParsedProvidedNameIterator::new(self.provides.unwrap_or(""))
}
pub fn provides_name(&self, name: &str) -> bool {
self.name == name
|| self.provides.is_some_and(|provides| {
ParsedProvidedNameIterator::new(provides)
.filter_map(|n| n.ok())
.any(|pv| *pv.name() == name)
})
}
pub fn essential(&self) -> bool {
self.essential
}
pub fn priority(&self) -> Priority {
self.priority
}
pub fn required(&self) -> bool {
self.priority == Priority::Required
}
pub fn install_priority(&self) -> InstallPriority {
if self.essential {
InstallPriority::Essential
} else if self.priority == Priority::Required {
InstallPriority::Required
} else {
InstallPriority::Other
}
}
pub fn multi_arch(&self) -> MultiArch {
self.multi_arch
}
pub fn architecture(&self) -> &'a str {
self.arch
}
pub fn raw_version(&self) -> Version<&'a str> {
Version::new(self.version)
}
pub fn version(&self) -> std::result::Result<Version<&'a str>, ParseError> {
Version::try_from(self.version)
}
pub fn depends(
&self,
) -> impl Iterator<Item = std::result::Result<Dependency<&'a str>, ParseError>> {
ParsedDependencyIterator::new(self.depends.unwrap_or(""))
}
pub fn pre_depends(
&self,
) -> impl Iterator<Item = std::result::Result<Dependency<&'a str>, ParseError>> {
ParsedDependencyIterator::new(self.pre_depends.unwrap_or(""))
}
pub fn breaks(
&self,
) -> impl Iterator<Item = std::result::Result<Constraint<&'a str>, ParseError>> {
ParsedConstraintIterator::new(self.breaks.unwrap_or(""), false)
}
pub fn conflicts(
&self,
) -> impl Iterator<Item = std::result::Result<Constraint<&'a str>, ParseError>> {
ParsedConstraintIterator::new(self.conflicts.unwrap_or(""), false)
}
pub fn control(&self) -> Result<ControlStanza<'a>, ParseError> {
ControlStanza::parse(self.src)
}
pub fn field(&self, name: &str) -> Option<&'a str> {
ControlParser::new(self.src)
.map(|f| f.unwrap())
.find(|f| f.is_a(name))
.map(|f| f.value())
}
pub fn ensure_field(&self, name: &str) -> Result<&'a str, ParseError> {
ControlParser::new(self.src)
.map(|f| f.unwrap())
.find(|f| f.is_a(name))
.map(|f| f.value())
.ok_or_else(|| {
ParseError::from(format!(
"Package {} description lacks field {}",
&self, name
))
})
}
pub fn fields(&self) -> impl Iterator<Item = ControlField<'a>> {
ControlParser::new(self.src).map(|f| f.unwrap())
}
pub fn equals_to<'b>(
&self,
other: &Package<'b>,
) -> std::result::Result<bool, (&'static str, &'a str, &'b str)> {
if self.name != other.name || self.version != other.version || self.arch != other.arch {
return Ok(false);
}
if let Some((this, that)) = self
.field("SHA512")
.and_then(|this| other.field("SHA512").map(|that| (this, that)))
{
if this != that {
return Err(("SHA512 digest", this, that));
} else {
return Ok(true);
}
}
if let Some((this, that)) = self
.field("SHA256")
.and_then(|this| other.field("SHA256").map(|that| (this, that)))
{
if this != that {
return Err(("SHA256 digiest", this, that));
} else {
return Ok(true);
}
}
if self.src == other.src {
return Ok(true);
}
Err(("package description", self.src, other.src))
}
pub fn try_parse_from(
parser: &mut ControlParser<'a>,
) -> Result<Option<Package<'a>>, ParseError> {
let mut parsed = false;
let snap = unsafe { parser.snap() };
let mut package = parser.try_fold(
Package::<'a>::default(),
|mut pkg,
field: Result<ControlField<'a>, ParseError>|
-> Result<Package<'a>, ParseError> {
let field = field?;
if !parsed {
parsed = true;
}
if field.is_a("Package") {
pkg.name = field.value().trim();
} else if field.is_a("Architecture") {
pkg.arch = field.value().trim();
} else if field.is_a("Version") {
pkg.version = field.value().trim();
} else if field.is_a("Provides") {
pkg.provides.replace(field.value());
} else if field.is_a("Depends") {
pkg.depends.replace(field.value());
} else if field.is_a("Pre-Depends") {
pkg.pre_depends.replace(field.value());
} else if field.is_a("Conflicts") {
pkg.conflicts.replace(field.value());
} else if field.is_a("Breaks") {
pkg.breaks.replace(field.value());
} else if field.is_a("Essential") {
if field.value().eq_ignore_ascii_case("yes") {
pkg.essential = true;
}
} else if field.is_a("Priority") {
pkg.priority = Priority::from(field.value());
} else if field.is_a("Multi-Arch") {
pkg.multi_arch = MultiArch::from(field.value());
}
Ok(pkg)
},
)?;
if !parsed {
Ok(None)
} else if package.name.is_empty() {
Err(ParseError::from("Field Package not found"))
} else if package.arch.is_empty() {
Err(ParseError::from("Field Architecture not found"))
} else if package.version.is_empty() {
Err(ParseError::from("Field Version not found"))
} else {
package.src = unsafe { snap.into_slice(parser) };
Ok(Some(package))
}
}
}
impl<'a> From<&Package<'a>> for MutableControlStanza {
fn from(stanza: &Package<'a>) -> Self {
MutableControlStanza::parse(stanza.src).unwrap()
}
}
pub struct Packages {
prio: u32,
origin: PackageOrigin,
inner: Arc<PackagesInner>,
}
impl Default for Packages {
fn default() -> Self {
Packages {
origin: PackageOrigin::Unknown,
prio: 500,
inner: Arc::new(
PackagesInnerTryBuilder {
data: IndexFile::from(""),
packages_builder: |_: &'_ IndexFile| -> Result<Vec<Package<'_>>, ParseError> {
Ok(vec![])
},
}
.try_build()
.unwrap(),
),
}
}
}
impl Clone for Packages {
fn clone(&self) -> Self {
Packages {
origin: self.origin,
prio: self.prio,
inner: Arc::clone(&self.inner),
}
}
}
impl Packages {
pub fn get(&self, index: usize) -> Option<&Package<'_>> {
self.inner.with_packages(|packages| packages.get(index))
}
pub fn len(&self) -> usize {
self.inner.with_packages(|packages| packages.len())
}
pub fn is_empty(&self) -> bool {
self.inner.with_packages(|packages| packages.is_empty())
}
pub fn repo_file(
&self,
index: usize,
hash_field_name: &'static str,
) -> io::Result<(&str, u64, Hash)> {
self.get(index)
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("package index {} is out of range", index),
)
})
.and_then(|p| p.repo_file(hash_field_name))
}
pub fn src(&self) -> &str {
self.inner.with_data(|d| d.as_str())
}
pub fn package_by_name(&self, name: &str) -> Option<&Package<'_>> {
self.inner
.with_packages(|packages| packages.iter().find(|package| package.name() == name))
}
pub fn packages(&self) -> impl Iterator<Item = &Package<'_>> {
self.inner.with_packages(|packages| packages.iter())
}
pub fn prio(&self) -> u32 {
self.prio
}
pub fn with_prio(self, prio: u32) -> Self {
Self {
prio,
origin: self.origin,
inner: self.inner,
}
}
pub fn origin(&self) -> PackageOrigin {
self.origin
}
pub fn archive_id(&self) -> Option<usize> {
self.origin.archive().map(|id| id as usize)
}
pub fn with_origin(self, origin: PackageOrigin) -> Self {
Self {
origin,
prio: self.prio,
inner: self.inner,
}
}
pub fn new(
data: IndexFile,
origin: PackageOrigin,
prio: Option<u32>,
) -> Result<Self, ParseError> {
Ok(Packages {
origin,
prio: prio.unwrap_or(500),
inner: Arc::new(
PackagesInnerTryBuilder {
data,
packages_builder:
|data: &'_ IndexFile| -> Result<Vec<Package<'_>>, ParseError> {
let mut parser = ControlParser::new(data.as_str());
let mut packages: Vec<Package<'_>> = vec![];
while let Some(package) = Package::try_parse_from(&mut parser)? {
packages.push(package)
}
Ok(packages)
},
}
.try_build()?,
),
})
}
pub async fn read<R: AsyncRead + Unpin + Send>(r: &mut R) -> io::Result<Self> {
let mut buf = String::new();
r.read_to_string(&mut buf).await?;
buf.try_into().map_err(|err| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Error parsing packages file: {}", err),
)
})
}
}
impl From<&Packages> for MutableControlFile {
fn from(pkgs: &Packages) -> Self {
pkgs.inner
.with_packages(|pkgs| pkgs.iter())
.map(MutableControlStanza::from)
.collect()
}
}
impl Serialize for Packages {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = self.inner.with_data(|d| d.as_str());
serializer.serialize_str(s)
}
}
impl<'de> Deserialize<'de> for Packages {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Packages::try_from(s).map_err(serde::de::Error::custom)
}
}
impl TryFrom<&str> for Packages {
type Error = ParseError;
fn try_from(inp: &str) -> Result<Self, Self::Error> {
Self::new(inp.to_owned().into(), PackageOrigin::Unknown, None)
}
}
impl TryFrom<String> for Packages {
type Error = ParseError;
fn try_from(inp: String) -> Result<Self, Self::Error> {
Self::new(inp.into(), PackageOrigin::Unknown, None)
}
}
impl TryFrom<Vec<u8>> for Packages {
type Error = ParseError;
fn try_from(inp: Vec<u8>) -> Result<Self, Self::Error> {
Self::new(
String::from_utf8(inp)
.map_err(|err| ParseError::from(format!("{}", err)))?
.into(),
PackageOrigin::Unknown,
None,
)
}
}
#[self_referencing]
struct PackagesInner {
data: IndexFile,
#[borrows(data)]
#[covariant]
packages: Vec<Package<'this>>,
}