1use crate::errors::*;
2use std::env;
3use std::io::ErrorKind;
4use std::path::{Component, Path, PathBuf};
5use tokio::fs;
6
7static SHARD_SIZE: usize = 2;
8
9pub fn repro_env_dir() -> Result<PathBuf> {
10 if let Some(path) = env::var_os("REPRO_ENV_HOME") {
11 Ok(path.into())
12 } else {
13 let mut cache = dirs::cache_dir().context("Failed to detect cache directory")?;
14 cache.push("repro-env");
15 Ok(cache)
16 }
17}
18
19pub fn cache_dir() -> Result<PathBuf> {
20 if let Some(path) = env::var_os("REPRO_ENV_CACHE") {
21 Ok(path.into())
22 } else {
23 repro_env_dir()
24 }
25}
26
27pub fn pkgs_cache_dir() -> Result<PkgsCacheDir> {
28 let mut path = cache_dir()?;
29 path.push("pkgs");
30 Ok(PkgsCacheDir { path })
31}
32
33pub fn alpine_cache_dir() -> Result<PkgsCacheDir> {
34 let mut path = cache_dir()?;
35 path.push("alpine");
36 Ok(PkgsCacheDir { path })
37}
38
39#[derive(Debug)]
40pub struct PkgsCacheDir {
41 path: PathBuf,
42}
43
44impl PkgsCacheDir {
45 fn shard<'a>(hash: &'a str, algo: &'static str, len: usize) -> Result<(&'a str, &'a str)> {
46 if hash.len() != len {
47 bail!("Unexpected {algo} checksum length: {:?}", hash.len());
48 }
49 if !hash.chars().all(char::is_alphanumeric) {
50 bail!("Unexpected characters in {algo}: {hash:?}");
51 }
52
53 let shard = &hash[..SHARD_SIZE];
54 let suffix = &hash[SHARD_SIZE..];
55 Ok((shard, suffix))
56 }
57
58 fn shard_sha256(sha256: &str) -> Result<(&str, &str)> {
59 Self::shard(sha256, "sha256", 64)
60 }
61
62 fn shard_sha1(sha1: &str) -> Result<(&str, &str)> {
63 Self::shard(sha1, "sha1", 40)
64 }
65
66 pub fn sha256_path(&self, sha256: &str) -> Result<PathBuf> {
67 let (shard, suffix) = Self::shard_sha256(sha256)?;
68
69 let mut path = self.path.clone();
70 path.push(shard);
71 path.push(suffix);
72
73 Ok(path)
74 }
75
76 fn sha1_path(&self, sha1: &str) -> Result<PathBuf> {
77 let (shard, suffix) = Self::shard_sha1(sha1)?;
78
79 let mut path = self.path.clone();
80 path.push(shard);
81 path.push(suffix);
82
83 Ok(path)
84 }
85
86 pub async fn sha1_read_link(&self, sha1: &str) -> Result<Option<String>> {
87 let path = self.sha1_path(sha1)?;
88 match fs::read_link(&path).await {
89 Ok(path) => {
90 trace!("Found symlink in cache: {path:?}");
91 let sha256 = Self::link_to_sha256(&path)?;
92 Ok(Some(sha256))
93 }
94 Err(err) if err.kind() == ErrorKind::NotFound => {
95 trace!("Did not find symlink in cache: {path:?}");
96 Ok(None)
97 }
98 Err(err) => Err(err.into()),
99 }
100 }
101
102 pub fn sha1_to_sha256(&self, sha1: &str, sha256: &str) -> Result<(PathBuf, PathBuf)> {
103 let sha1_path = self.sha1_path(sha1)?;
104 let (shard, suffix) = Self::shard_sha256(sha256)?;
105
106 let mut sha256_path = PathBuf::from("../../pkgs");
107 sha256_path.push(shard);
108 sha256_path.push(suffix);
109
110 Ok((sha1_path, sha256_path))
111 }
112
113 fn link_to_sha256(path: &Path) -> Result<String> {
114 let mut components = path.components().rev();
115
116 let tail = components.next().context("Link is missing filename")?;
117 let shard = components.next().context("Link is missing shard")?;
118
119 let tail = Self::component_to_name(&tail)?;
120 let shard = Self::component_to_name(&shard)?;
121
122 Ok(format!("{shard}{tail}"))
123 }
124
125 fn component_to_name<'a>(comp: &'a Component) -> Result<&'a str> {
126 let Component::Normal(comp) = comp else {
127 bail!("Component has reserved name")
128 };
129 let Some(comp) = comp.to_str() else {
130 bail!("Component is invalid utf8")
131 };
132 Ok(comp)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use std::path::Path;
140
141 #[test]
142 fn test_sha256_path() {
143 let dir = PkgsCacheDir {
144 path: PathBuf::from("/cache"),
145 };
146 assert!(dir.sha256_path("").is_err());
147 assert!(dir.sha256_path("ffff").is_err());
148 assert!(dir
149 .sha256_path("////////////////////////////////////////////////////////////////")
150 .is_err());
151
152 let path = dir
153 .sha256_path("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
154 .unwrap();
155 assert_eq!(
156 path,
157 Path::new("/cache/ff/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
158 );
159 }
160
161 #[test]
162 fn test_sha1_read_link() -> Result<()> {
163 let path = PkgsCacheDir::link_to_sha256(Path::new(
164 "../../../pkgs/ff/7951b5950a3a0319e86988041db4438b31a6ee4c7a36c64bd6c0c4607e40c9",
165 ))?;
166 assert_eq!(
167 path,
168 "ff7951b5950a3a0319e86988041db4438b31a6ee4c7a36c64bd6c0c4607e40c9"
169 );
170 Ok(())
171 }
172
173 #[test]
174 fn test_sha1_to_sha256() -> Result<()> {
175 let dir = PkgsCacheDir {
176 path: PathBuf::from("/cache"),
177 };
178
179 let (sha1, sha256) = dir.sha1_to_sha256(
180 "83d8ab27f4fd4725a147245f89d076aa96b52262",
181 "ff7951b5950a3a0319e86988041db4438b31a6ee4c7a36c64bd6c0c4607e40c9",
182 )?;
183 assert_eq!(
184 sha1,
185 Path::new("/cache/83/d8ab27f4fd4725a147245f89d076aa96b52262")
186 );
187 assert_eq!(
188 sha256,
189 Path::new(
190 "../../pkgs/ff/7951b5950a3a0319e86988041db4438b31a6ee4c7a36c64bd6c0c4607e40c9"
191 )
192 );
193
194 Ok(())
195 }
196}