repro_env/pkgs/
archlinux.rs1use crate::container::Container;
2use crate::errors::*;
3use crate::pkgs::Pkg;
4use peekread::{BufPeekReader, PeekRead};
5use ruzstd::decoding::StreamingDecoder;
6use std::fmt::Write;
7use std::io::{BufRead, BufReader, Read};
8use std::time::SystemTime;
9use std::time::UNIX_EPOCH;
10
11pub const GPG_CONF_DIR: &str = "/etc/pacman.d/gnupg/";
12pub const GPG_CONF_FILENAME: &str = "gpg.conf";
13
14pub enum Compression {
15 Xz,
16 Zstd,
17 None,
18}
19
20pub fn detect_compression<R: Read>(mut reader: R) -> Result<Compression> {
21 let mut buf = [0u8; 6];
22
23 reader
24 .read_exact(&mut buf)
25 .context("Failed to read magic bytes from archive")?;
26
27 if buf.starts_with(&[0x28, 0xB5, 0x2F, 0xFD]) {
28 Ok(Compression::Zstd)
29 } else if buf.starts_with(&[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00]) {
30 Ok(Compression::Xz)
31 } else {
32 Ok(Compression::None)
33 }
34}
35
36pub fn parse_pkginfo<R: Read>(reader: R) -> Result<Pkg> {
37 let reader = BufReader::new(reader);
38
39 let mut name = None;
40 let mut version = None;
41
42 for line in reader.lines() {
43 let line = line?;
44
45 if let Some(value) = line.strip_prefix("pkgname = ") {
46 name = Some(value.to_string());
47 } else if let Some(value) = line.strip_prefix("pkgver = ") {
48 version = Some(value.to_string());
49 }
50 }
51
52 Ok(Pkg {
53 name: name.context("Could not find pkgname in .PKGINFO")?,
54 version: version.context("Could not find pkgver in .PKGINFO")?,
55 })
56}
57
58pub fn parse_tar<R: Read>(reader: R) -> Result<Pkg> {
59 let mut tar = tar::Archive::new(reader);
60 for entry in tar.entries()? {
61 let entry = entry?;
62 let path = entry.path()?;
63 if path.to_str() == Some(".PKGINFO") {
64 return parse_pkginfo(entry);
65 }
66 }
67 bail!("Failed to find .PKGINFO in package file")
68}
69
70pub fn parse<R: Read>(reader: R) -> Result<Pkg> {
71 let mut reader = BufPeekReader::new(reader);
72 match detect_compression(reader.peek())? {
73 Compression::Xz => {
74 let mut buf = Vec::new();
75 lzma_rs::xz_decompress(&mut reader, &mut buf)?;
76 parse_tar(&buf[..])
77 }
78 Compression::Zstd => {
79 let decoder = StreamingDecoder::new(reader)?;
80 parse_tar(decoder)
81 }
82 Compression::None => parse_tar(reader),
83 }
84}
85
86pub async fn set_pacman_verification_datetime(
87 container: &Container,
88 time: SystemTime,
89) -> Result<()> {
90 let path = format!("{GPG_CONF_DIR}{GPG_CONF_FILENAME}");
91 let gpg_conf = container.cat(&path).await?;
92 let mut gpg_conf = String::from_utf8(gpg_conf).context("Failed to parse gpg.conf as utf-8")?;
93 if !gpg_conf.ends_with('\n') {
94 gpg_conf.push('\n');
95 }
96
97 if let Some(line) = gpg_conf
99 .lines()
100 .find(|line| line.starts_with("faked-system-time"))
101 {
102 warn!("Container already defines a verification datetime: {line:?}");
103 return Ok(());
104 }
105
106 let epoch = time
107 .duration_since(UNIX_EPOCH)
108 .with_context(|| anyhow!("Failed to derive unix epoch from time {time:?}"))?;
109 writeln!(gpg_conf, "faked-system-time {}", epoch.as_secs())?;
110
111 container
112 .write_file(GPG_CONF_DIR, GPG_CONF_FILENAME, gpg_conf.as_bytes())
113 .await?;
114
115 Ok(())
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_parse_pkg() -> Result<()> {
124 let archive = {
125 let data = br#"# Generated by makepkg 6.0.2
126# using fakeroot version 1.31
127pkgname = gcc
128pkgbase = gcc
129pkgver = 13.1.1-1
130pkgdesc = The GNU Compiler Collection - C and C++ frontends
131url = https://gcc.gnu.org
132builddate = 1682849478
133packager = Frederik Schwan <freswa@archlinux.org>
134size = 190564290
135arch = x86_64
136license = GPL3
137license = LGPL
138license = FDL
139license = custom
140replaces = gcc-multilib
141provides = gcc-multilib
142depend = gcc-libs=13.1.1-1
143depend = binutils>=2.28
144depend = libmpc
145depend = zstd
146depend = libisl.so=23-64
147optdepend = lib32-gcc-libs: for generating code for 32-bit ABI
148makedepend = binutils
149makedepend = doxygen
150makedepend = gcc-ada
151makedepend = gcc-d
152makedepend = git
153makedepend = lib32-glibc
154makedepend = lib32-gcc-libs
155makedepend = libisl
156makedepend = libmpc
157makedepend = python
158makedepend = zstd
159checkdepend = dejagnu
160checkdepend = expect
161checkdepend = inetutils
162checkdepend = python-pytest
163checkdepend = tcl
164"#;
165
166 let mut tar = tar::Builder::new(Vec::new());
167 let mut header = tar::Header::new_gnu();
168 header.set_path(".PKGINFO")?;
169 header.set_size(data.len() as u64);
170 header.set_cksum();
171 tar.append(&header, &data[..])?;
172 tar.into_inner()?
173 };
174
175 let mut buf = Vec::new();
176 lzma_rs::xz_compress(&mut &archive[..], &mut buf)?;
177
178 let pkg = parse(&buf[..]).context("Failed to parse package")?;
179 assert_eq!(
180 pkg,
181 Pkg {
182 name: "gcc".to_string(),
183 version: "13.1.1-1".to_string(),
184 }
185 );
186
187 Ok(())
188 }
189}