tugger_rust_toolchain/
tar.rs1use {
6 anyhow::{anyhow, Context, Result},
7 sha2::Digest,
8 simple_file_manifest::{FileEntry, FileManifest},
9 std::{
10 io::{BufRead, Read, Write},
11 path::{Path, PathBuf},
12 },
13};
14
15#[derive(Clone, Copy, Debug)]
16pub enum CompressionFormat {
17 Gzip,
18 Xz,
19 Zstd,
20}
21
22fn get_decompression_stream(format: CompressionFormat, data: Vec<u8>) -> Result<Box<dyn Read>> {
23 let reader = std::io::Cursor::new(data);
24
25 match format {
26 CompressionFormat::Zstd => Ok(Box::new(zstd::stream::read::Decoder::new(reader)?)),
27 CompressionFormat::Xz => Ok(Box::new(xz2::read::XzDecoder::new(reader))),
28 CompressionFormat::Gzip => Ok(Box::new(flate2::read::GzDecoder::new(reader))),
29 }
30}
31
32pub struct PackageArchive {
36 manifest: FileManifest,
37 components: Vec<String>,
38}
39
40impl PackageArchive {
41 pub fn new(format: CompressionFormat, data: Vec<u8>) -> Result<Self> {
43 let mut archive = tar::Archive::new(
44 get_decompression_stream(format, data).context("obtaining decompression stream")?,
45 );
46
47 let mut manifest = FileManifest::default();
48
49 for entry in archive.entries().context("obtaining tar archive entries")? {
50 let mut entry = entry.context("resolving tar archive entry")?;
51
52 let path = entry.path().context("resolving entry path")?;
53
54 let first_component = path
55 .components()
56 .next()
57 .ok_or_else(|| anyhow!("unable to get first path component"))?;
58
59 let path = path
60 .strip_prefix(first_component)
61 .context("stripping path prefix")?
62 .to_path_buf();
63
64 let mut entry_data = Vec::new();
65 entry.read_to_end(&mut entry_data)?;
66
67 manifest.add_file_entry(
68 path,
69 FileEntry::new_from_data(entry_data, entry.header().mode()? & 0o111 != 0),
70 )?;
71 }
72
73 if manifest
74 .get("rust-installer-version")
75 .ok_or_else(|| anyhow!("archive does not contain rust-installer-version"))?
76 .resolve_content()?
77 != b"3\n"
78 {
79 return Err(anyhow!("rust-installer-version has unsupported version"));
80 }
81
82 let components = manifest
83 .get("components")
84 .ok_or_else(|| anyhow!("archive does not contain components file"))?
85 .resolve_content()?;
86 let components =
87 String::from_utf8(components).context("converting components file to string")?;
88 let components = components
89 .lines()
90 .map(|l| l.to_string())
91 .collect::<Vec<_>>();
92
93 Ok(Self {
94 manifest,
95 components,
96 })
97 }
98
99 pub fn resolve_installs(&self) -> Result<Vec<(PathBuf, &FileEntry)>> {
104 let mut res = Vec::new();
105
106 for component in &self.components {
107 let component_path = PathBuf::from(component);
108 let manifest_path = component_path.join("manifest.in");
109
110 let manifest = self
111 .manifest
112 .get(&manifest_path)
113 .ok_or_else(|| anyhow!("{} not found", manifest_path.display()))?;
114
115 let (dirs, files) = Self::parse_manifest(manifest.resolve_content()?)?;
116
117 if !dirs.is_empty() {
118 return Err(anyhow!("support for copying directories not implemented"));
119 }
120
121 for file in files {
122 let manifest_path = component_path.join(&file);
123 let entry = self.manifest.get(&manifest_path).ok_or_else(|| {
124 anyhow!(
125 "could not locate file {} in manifest",
126 manifest_path.display()
127 )
128 })?;
129
130 res.push((PathBuf::from(file), entry));
131 }
132 }
133
134 Ok(res)
135 }
136
137 pub fn write_installs_manifest(&self, fh: &mut impl Write) -> Result<()> {
139 for (path, entry) in self.resolve_installs().context("resolving installs")? {
140 let mut hasher = sha2::Sha256::new();
141 hasher.update(entry.resolve_content()?);
142
143 let line = format!(
144 "{}\t{}\n",
145 hex::encode(hasher.finalize().as_slice()),
146 path.display()
147 );
148
149 fh.write_all(line.as_bytes())?;
150 }
151
152 Ok(())
153 }
154
155 pub fn install(&self, dest_dir: &Path) -> Result<()> {
157 for (dest_path, entry) in self.resolve_installs().context("resolving installs")? {
158 let dest_path = dest_dir.join(dest_path);
159
160 entry
161 .write_to_path(&dest_path)
162 .with_context(|| format!("writing {}", dest_path.display(),))?;
163 }
164
165 Ok(())
166 }
167
168 fn parse_manifest(data: Vec<u8>) -> Result<(Vec<String>, Vec<String>)> {
169 let mut files = vec![];
170 let mut dirs = vec![];
171
172 let data = String::from_utf8(data)?;
173
174 for line in data.lines() {
175 if let Some(pos) = line.find(':') {
176 let action = &line[0..pos];
177 let path = &line[pos + 1..];
178
179 match action {
180 "file" => {
181 files.push(path.to_string());
182 }
183 "dir" => {
184 dirs.push(path.to_string());
185 }
186 _ => return Err(anyhow!("unhandled action in manifest.in: {}", action)),
187 }
188 }
189 }
190
191 Ok((dirs, files))
192 }
193}
194
195pub fn read_installs_manifest(fh: &mut impl Read) -> Result<Vec<(PathBuf, String)>> {
199 let mut res = vec![];
200
201 let reader = std::io::BufReader::new(fh);
202
203 for line in reader.lines() {
204 let line = line?;
205
206 if line.is_empty() {
207 break;
208 }
209
210 let mut parts = line.splitn(2, '\t');
211
212 let digest = parts
213 .next()
214 .ok_or_else(|| anyhow!("could not read digest"))?;
215 let filename = parts
216 .next()
217 .ok_or_else(|| anyhow!("could not read filename"))?;
218
219 res.push((PathBuf::from(filename), digest.to_string()));
220 }
221
222 Ok(res)
223}