use crate::container::Container;
use crate::errors::*;
use crate::pkgs::Pkg;
use peekread::{BufPeekReader, PeekRead};
use std::fmt::Write;
use std::io::{BufRead, BufReader, Read};
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
pub const GPG_CONF_DIR: &str = "/etc/pacman.d/gnupg/";
pub const GPG_CONF_FILENAME: &str = "gpg.conf";
pub enum Compression {
Xz,
Zstd,
None,
}
pub fn detect_compression<R: Read>(mut reader: R) -> Result<Compression> {
let mut buf = [0u8; 6];
reader
.read_exact(&mut buf)
.context("Failed to read magic bytes from archive")?;
if buf.starts_with(&[0x28, 0xB5, 0x2F, 0xFD]) {
Ok(Compression::Zstd)
} else if buf.starts_with(&[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00]) {
Ok(Compression::Xz)
} else {
Ok(Compression::None)
}
}
pub fn parse_pkginfo<R: Read>(reader: R) -> Result<Pkg> {
let reader = BufReader::new(reader);
let mut name = None;
let mut version = None;
for line in reader.lines() {
let line = line?;
if let Some(value) = line.strip_prefix("pkgname = ") {
name = Some(value.to_string());
} else if let Some(value) = line.strip_prefix("pkgver = ") {
version = Some(value.to_string());
}
}
Ok(Pkg {
name: name.context("Could not find pkgname in .PKGINFO")?,
version: version.context("Could not find pkgver in .PKGINFO")?,
})
}
pub fn parse_tar<R: Read>(reader: R) -> Result<Pkg> {
let mut tar = tar::Archive::new(reader);
for entry in tar.entries()? {
let entry = entry?;
let path = entry.path()?;
if path.to_str() == Some(".PKGINFO") {
return parse_pkginfo(entry);
}
}
bail!("Failed to find .PKGINFO in package file")
}
pub fn parse<R: Read>(reader: R) -> Result<Pkg> {
let mut reader = BufPeekReader::new(reader);
match detect_compression(reader.peek())? {
Compression::Xz => {
let mut buf = Vec::new();
lzma_rs::xz_decompress(&mut reader, &mut buf)?;
parse_tar(&buf[..])
}
Compression::Zstd => {
let decoder = ruzstd::StreamingDecoder::new(reader)?;
parse_tar(decoder)
}
Compression::None => parse_tar(reader),
}
}
pub async fn set_pacman_verification_datetime(
container: &Container,
time: SystemTime,
) -> Result<()> {
let path = format!("{GPG_CONF_DIR}{GPG_CONF_FILENAME}");
let gpg_conf = container.cat(&path).await?;
let mut gpg_conf = String::from_utf8(gpg_conf).context("Failed to parse gpg.conf as utf-8")?;
if !gpg_conf.ends_with('\n') {
gpg_conf.push('\n');
}
if let Some(line) = gpg_conf
.lines()
.find(|line| line.starts_with("faked-system-time"))
{
warn!("Container already defines a verification datetime: {line:?}");
return Ok(());
}
let epoch = time
.duration_since(UNIX_EPOCH)
.with_context(|| anyhow!("Failed to derive unix epoch from time {time:?}"))?;
writeln!(gpg_conf, "faked-system-time {}", epoch.as_secs())?;
container
.write_file(GPG_CONF_DIR, GPG_CONF_FILENAME, gpg_conf.as_bytes())
.await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_pkg() -> Result<()> {
let archive = {
let data = br#"# Generated by makepkg 6.0.2
# using fakeroot version 1.31
pkgname = gcc
pkgbase = gcc
pkgver = 13.1.1-1
pkgdesc = The GNU Compiler Collection - C and C++ frontends
url = https://gcc.gnu.org
builddate = 1682849478
packager = Frederik Schwan <freswa@archlinux.org>
size = 190564290
arch = x86_64
license = GPL3
license = LGPL
license = FDL
license = custom
replaces = gcc-multilib
provides = gcc-multilib
depend = gcc-libs=13.1.1-1
depend = binutils>=2.28
depend = libmpc
depend = zstd
depend = libisl.so=23-64
optdepend = lib32-gcc-libs: for generating code for 32-bit ABI
makedepend = binutils
makedepend = doxygen
makedepend = gcc-ada
makedepend = gcc-d
makedepend = git
makedepend = lib32-glibc
makedepend = lib32-gcc-libs
makedepend = libisl
makedepend = libmpc
makedepend = python
makedepend = zstd
checkdepend = dejagnu
checkdepend = expect
checkdepend = inetutils
checkdepend = python-pytest
checkdepend = tcl
"#;
let mut tar = tar::Builder::new(Vec::new());
let mut header = tar::Header::new_gnu();
header.set_path(".PKGINFO")?;
header.set_size(data.len() as u64);
header.set_cksum();
tar.append(&header, &data[..])?;
tar.into_inner()?
};
let mut buf = Vec::new();
lzma_rs::xz_compress(&mut &archive[..], &mut buf)?;
let pkg = parse(&buf[..]).context("Failed to parse package")?;
assert_eq!(
pkg,
Pkg {
name: "gcc".to_string(),
version: "13.1.1-1".to_string(),
}
);
Ok(())
}
}