1use crate::args;
2use crate::container;
3use crate::errors::*;
4use crate::http;
5use crate::lockfile::{Lockfile, PackageLock};
6use crate::paths;
7use crate::pkgs;
8use sha2::{Digest, Sha256};
9use std::path::Path;
10use tokio::fs;
11use tokio::io::{AsyncSeekExt, AsyncWriteExt};
12
13pub async fn download_dependencies(dependencies: &[PackageLock]) -> Result<()> {
14 let client = http::Client::new()?;
15 let pkgs_cache_dir = paths::pkgs_cache_dir()?;
16
17 for package in dependencies {
18 trace!("Found dependencies: {package:?}");
19 let path = pkgs_cache_dir.sha256_path(&package.sha256)?;
20 if path.exists() {
21 debug!(
22 "Package already in cache: {:?} {:?}",
23 package.name, package.version
24 );
25 } else {
26 let parent = path
27 .parent()
28 .context("Failed to determine parent directory")?;
29 fs::create_dir_all(parent).await.with_context(|| {
30 anyhow!("Failed to create parent directories for file: {path:?}")
31 })?;
32
33 let mut dl_path = path.clone();
34 dl_path.as_mut_os_string().push(".tmp");
35
36 let file = fs::OpenOptions::new()
37 .write(true)
38 .create(true)
39 .truncate(false)
40 .open(&dl_path)
41 .await?;
42
43 let mut lock = fd_lock::RwLock::new(file);
44 debug!("Trying to acquire write lock for file: {path:?}");
45 let mut lock = lock
46 .write()
47 .with_context(|| anyhow!("Failed to acquire lock for {dl_path:?}"))?;
48
49 if path.exists() {
51 debug!("File became available in the meantime, nothing to do");
52 } else {
53 debug!(
54 "Downloading package into cache: {:?} {:?}",
55 package.name, package.version
56 );
57 lock.set_len(0).await.context("Failed to truncate file")?;
58 lock.rewind()
59 .await
60 .context("Failed to rewind file to beginning")?;
61
62 let mut response = client.request(&package.url).await.with_context(|| {
63 anyhow!("Failed to download package from url: {:?}", package.url)
64 })?;
65
66 let mut hasher = Sha256::new();
67 while let Some(chunk) = response
68 .chunk()
69 .await
70 .context("Failed to read from download stream")?
71 {
72 lock.write_all(&chunk)
73 .await
74 .context("Failed to write to downloaded data to disk")?;
75 hasher.update(&chunk);
76 }
77 let result = hex::encode(hasher.finalize());
78
79 if package.sha256 != result {
80 lock.set_len(0)
81 .await
82 .context("Mismatch of sha256, failed to truncate file")?;
83 bail!(
84 "Mismatch of sha256, expected={:?}, downloaded={:?}",
85 package.sha256,
86 result
87 );
88 }
89
90 lock.sync_all()
91 .await
92 .context("Failed to sync downloaded data to disk")?;
93 fs::rename(&dl_path, &path)
94 .await
95 .with_context(|| anyhow!("Failed to rename {dl_path:?} to {path:?}"))?;
96 }
97 }
98 }
99
100 Ok(())
101}
102
103pub fn verify_pin_metadata(pkg: &[u8], pin: &PackageLock) -> Result<()> {
104 let pkg = match pin.system.as_str() {
105 "alpine" => pkgs::alpine::parse(pkg).context("Failed to parse data as alpine package")?,
106 "archlinux" => {
107 pkgs::archlinux::parse(pkg).context("Failed to parse data as archlinux package")?
108 }
109 "debian" => pkgs::debian::parse(pkg).context("Failed to parse data as debian package")?,
110 system => bail!("Unknown package system: {system:?}"),
111 };
112
113 debug!("Parsed embedded metadata from package: {pkg:?}");
114
115 if pin.name != pkg.name {
116 bail!(
117 "Package name in metadata doesn't match lockfile: expected={:?}, embedded={:?}",
118 pin.name,
119 pkg.name
120 );
121 }
122
123 if pin.version != pkg.version {
124 bail!(
125 "Package version in metadata doesn't match lockfile: expected={:?}, embedded={:?}",
126 pin.version,
127 pkg.version
128 );
129 }
130
131 Ok(())
132}
133
134pub async fn fetch(fetch: &args::Fetch) -> Result<()> {
135 let path = fetch.file.as_deref().unwrap_or(Path::new("repro-env.lock"));
137 let buf = fs::read_to_string(path)
138 .await
139 .with_context(|| anyhow!("Failed to read dependency lockfile: {path:?}"))?;
140
141 let lockfile = Lockfile::deserialize(&buf)?;
142 trace!("Loaded dependency lockfile from file: {lockfile:?}");
143
144 if !fetch.no_pull {
145 let image = &lockfile.container.image;
146 if let Err(err) = container::inspect(image).await {
147 debug!("Could not find image in cache: {err:#}");
148 container::pull(image).await?;
149 } else {
150 info!("Found container image in local cache: {image:?}");
151 }
152 }
153
154 let dependencies = lockfile
156 .packages
157 .into_iter()
158 .filter(|p| !p.installed)
159 .collect::<Vec<_>>();
160
161 if !dependencies.is_empty() {
162 download_dependencies(&dependencies).await?;
163 }
164
165 Ok(())
166}