1use std::{
12 fs::read_dir,
13 path::{MAIN_SEPARATOR_STR, Path, PathBuf},
14};
15
16use alpm_types::{INSTALL_SCRIPTLET_FILE_NAME, MetadataFileName};
17
18#[derive(Clone, Copy, Debug)]
22pub struct InputPath<'a, 'b> {
23 base_dir: &'a Path,
24 path: &'b Path,
25}
26
27impl<'a, 'b> InputPath<'a, 'b> {
28 pub fn new(base_dir: &'a Path, path: &'b Path) -> Result<Self, crate::Error> {
37 if !base_dir.is_absolute() {
38 return Err(crate::Error::NonAbsolutePaths {
39 paths: vec![base_dir.to_path_buf()],
40 });
41 }
42 if !base_dir.is_dir() {
43 return Err(crate::Error::NotADirectory {
44 path: base_dir.to_path_buf(),
45 });
46 }
47
48 if !path.is_relative() {
49 return Err(crate::Error::NonRelativePaths {
50 paths: vec![path.to_path_buf()],
51 });
52 }
53
54 Ok(Self { base_dir, path })
55 }
56
57 pub fn base_dir(&self) -> &Path {
59 self.base_dir
60 }
61
62 pub fn path(&self) -> &Path {
64 self.path
65 }
66
67 pub fn to_path_buf(&self) -> PathBuf {
69 self.base_dir.join(self.path)
70 }
71}
72
73#[derive(Clone, Copy, Debug)]
77pub struct InputPaths<'a, 'b> {
78 base_dir: &'a Path,
79 paths: &'b [PathBuf],
80}
81
82impl<'a, 'b> InputPaths<'a, 'b> {
83 pub fn new(base_dir: &'a Path, paths: &'b [PathBuf]) -> Result<Self, crate::Error> {
92 if !base_dir.is_absolute() {
93 return Err(crate::Error::NonAbsolutePaths {
94 paths: vec![base_dir.to_path_buf()],
95 });
96 }
97 if !base_dir.is_dir() {
98 return Err(crate::Error::NotADirectory {
99 path: base_dir.to_path_buf(),
100 });
101 }
102
103 let mut non_relative = Vec::new();
104 for path in paths {
105 if !path.is_relative() {
106 non_relative.push(path.clone())
107 }
108 }
109 if !non_relative.is_empty() {
110 return Err(crate::Error::NonRelativePaths {
111 paths: non_relative,
112 });
113 }
114
115 Ok(Self { base_dir, paths })
116 }
117
118 pub fn base_dir(&self) -> &Path {
120 self.base_dir
121 }
122
123 pub fn paths(&self) -> &[PathBuf] {
125 self.paths
126 }
127}
128
129pub fn relative_data_files(path: impl AsRef<Path>) -> Result<Vec<PathBuf>, crate::Error> {
139 relative_files(
140 path,
141 &[
142 MetadataFileName::BuildInfo.as_ref(),
143 MetadataFileName::Mtree.as_ref(),
144 MetadataFileName::PackageInfo.as_ref(),
145 INSTALL_SCRIPTLET_FILE_NAME,
146 ],
147 )
148}
149
150pub fn relative_files(
172 path: impl AsRef<Path>,
173 filter: &[&str],
174) -> Result<Vec<PathBuf>, crate::Error> {
175 let path = path.as_ref();
176 let init_path = path;
177
178 fn collect_files(
193 path: &Path,
194 init_path: &Path,
195 filter: &[&str],
196 ) -> Result<Vec<PathBuf>, crate::Error> {
197 let mut paths = Vec::new();
198 let entries = read_dir(path).map_err(|source| crate::Error::IoPath {
199 path: path.to_path_buf(),
200 context: "reading entries of directory",
201 source,
202 })?;
203 for entry in entries {
204 let entry = entry.map_err(|source| crate::Error::IoPath {
205 path: path.to_path_buf(),
206 context: "reading entry in directory",
207 source,
208 })?;
209 let meta = entry.metadata().map_err(|source| crate::Error::IoPath {
210 path: entry.path(),
211 context: "getting metadata of file",
212 source,
213 })?;
214
215 if filter.iter().any(|filter| entry.path().ends_with(filter)) {
217 continue;
218 }
219
220 paths.push({
221 let mut path = entry
222 .path()
223 .strip_prefix(init_path)
224 .map_err(|source| crate::Error::PathStripPrefix {
225 prefix: path.to_path_buf(),
226 path: entry.path(),
227 source,
228 })?
229 .to_path_buf();
230
231 if meta.is_dir()
233 && path
234 .as_os_str()
235 .to_str()
236 .is_some_and(|path| !path.ends_with(MAIN_SEPARATOR_STR))
237 {
238 path.as_mut_os_string().push(MAIN_SEPARATOR_STR);
239 }
240
241 path
242 });
243
244 if meta.is_dir() {
247 let mut subdir_paths = collect_files(entry.path().as_path(), init_path, filter)?;
248 paths.append(&mut subdir_paths);
249 }
250 }
251
252 paths.sort();
254
255 Ok(paths)
256 }
257
258 collect_files(path, init_path, filter)
259}
260
261#[cfg(test)]
262mod test {
263 use std::{
264 fs::{File, create_dir_all},
265 io::Write,
266 os::unix::fs::symlink,
267 };
268
269 use rstest::rstest;
270 use tempfile::{NamedTempFile, TempDir, tempdir};
271 use testresult::TestResult;
272
273 use super::*;
274
275 pub const VALID_BUILDINFO_V2_DATA: &str = r#"
276format = 2
277builddate = 1
278builddir = /build
279startdir = /startdir/
280buildtool = devtools
281buildtoolver = 1:1.2.1-1-any
282buildenv = ccache
283buildenv = color
284installed = bar-1.2.3-1-any
285installed = beh-2.2.3-4-any
286options = lto
287options = !strip
288packager = Foobar McFooface <foobar@mcfooface.org>
289pkgarch = any
290pkgbase = example
291pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
292pkgname = example
293pkgver = 1:1.0.0-1
294"#;
295
296 pub const VALID_PKGINFO_V2_DATA: &str = r#"
297pkgname = example
298pkgbase = example
299xdata = pkgtype=pkg
300pkgver = 1:1.0.0-1
301pkgdesc = A project that does something
302url = https://example.org/
303builddate = 1729181726
304packager = John Doe <john@example.org>
305size = 181849963
306arch = any
307license = GPL-3.0-or-later
308replaces = other-package>0.9.0-3
309group = package-group
310conflict = conflicting-package<1.0.0
311provides = some-component
312backup = etc/example/config.toml
313depend = glibc
314optdepend = python: for special-python-script.py
315makedepend = cmake
316checkdepend = extra-test-tool
317"#;
318
319 const VALID_INSTALL_SCRIPTLET: &str = r#"
320pre_install() {
321 echo "Preparing to install package version $1"
322}
323
324post_install() {
325 echo "Package version $1 installed"
326}
327
328pre_upgrade() {
329 echo "Preparing to upgrade from version $2 to $1"
330}
331
332post_upgrade() {
333 echo "Upgraded from version $2 to $1"
334}
335
336pre_remove() {
337 echo "Preparing to remove package version $1"
338}
339
340post_remove() {
341 echo "Package version $1 removed"
342}
343"#;
344
345 fn create_data_files(path: impl AsRef<Path>) -> TestResult {
346 let path = path.as_ref();
347 create_dir_all(path.join("usr/share/foo/bar/baz"))?;
349 File::create(path.join("usr/share/foo/beh.txt"))?.write_all(b"test")?;
351 symlink("../../beh.txt", path.join("usr/share/foo/bar/baz/beh.txt"))?;
353 Ok(())
354 }
355
356 fn create_metadata_files(path: impl AsRef<Path>) -> TestResult {
357 let path = path.as_ref();
358 for (input_type, input) in [
359 (MetadataFileName::BuildInfo, VALID_BUILDINFO_V2_DATA),
360 (MetadataFileName::PackageInfo, VALID_PKGINFO_V2_DATA),
361 ] {
362 File::create(path.join(input_type.as_ref()))?.write_all(input.as_bytes())?;
363 }
364 Ok(())
365 }
366
367 fn create_scriptlet_file(path: impl AsRef<Path>) -> TestResult {
368 let path = path.as_ref();
369 let mut output = File::create(path.join(INSTALL_SCRIPTLET_FILE_NAME))?;
370 write!(output, "{VALID_INSTALL_SCRIPTLET}")?;
371 Ok(())
372 }
373
374 #[rstest]
376 fn relative_data_files_collect_successfully() -> TestResult {
377 let tempdir = tempdir()?;
378
379 create_data_files(tempdir.path())?;
380 create_metadata_files(tempdir.path())?;
381 create_scriptlet_file(tempdir.path())?;
382
383 let expected_paths = vec![
384 PathBuf::from("usr"),
385 PathBuf::from("usr/share"),
386 PathBuf::from("usr/share/foo"),
387 PathBuf::from("usr/share/foo/bar"),
388 PathBuf::from("usr/share/foo/bar/baz"),
389 PathBuf::from("usr/share/foo/bar/baz/beh.txt"),
390 PathBuf::from("usr/share/foo/beh.txt"),
391 ];
392
393 let collected_files = relative_data_files(tempdir)?;
394 assert_eq!(expected_paths.as_slice(), collected_files.as_slice());
395
396 Ok(())
397 }
398
399 #[rstest]
401 fn relative_files_are_collected_successfully_without_filter() -> TestResult {
402 let tempdir = tempdir()?;
403
404 create_data_files(tempdir.path())?;
405 create_metadata_files(tempdir.path())?;
406 create_scriptlet_file(tempdir.path())?;
407
408 let expected_paths = vec![
409 PathBuf::from(MetadataFileName::BuildInfo.as_ref()),
410 PathBuf::from(INSTALL_SCRIPTLET_FILE_NAME),
411 PathBuf::from(MetadataFileName::PackageInfo.as_ref()),
412 PathBuf::from("usr"),
413 PathBuf::from("usr/share"),
414 PathBuf::from("usr/share/foo"),
415 PathBuf::from("usr/share/foo/bar"),
416 PathBuf::from("usr/share/foo/bar/baz"),
417 PathBuf::from("usr/share/foo/bar/baz/beh.txt"),
418 PathBuf::from("usr/share/foo/beh.txt"),
419 ];
420
421 let collected_files = relative_files(tempdir, &[])?;
422 assert_eq!(expected_paths.as_slice(), collected_files.as_slice());
423
424 Ok(())
425 }
426
427 #[test]
429 fn input_path_new() -> TestResult {
430 let temp_dir = TempDir::new()?;
431 let temp_dir_path = temp_dir.path();
432 let temp_file = NamedTempFile::new()?;
433 let temp_file_path = temp_file.path();
434
435 let relative_path = PathBuf::from("some_file.txt");
436 let absolute_path = PathBuf::from("/some_file.txt");
437
438 assert!(InputPath::new(temp_dir_path, &relative_path).is_ok());
439 assert!(matches!(
440 InputPath::new(temp_dir_path, &absolute_path),
441 Err(crate::Error::NonRelativePaths { .. })
442 ));
443 assert!(matches!(
444 InputPath::new(temp_file_path, &relative_path),
445 Err(crate::Error::NotADirectory { .. })
446 ));
447 assert!(matches!(
448 InputPath::new(&relative_path, &relative_path),
449 Err(crate::Error::NonAbsolutePaths { .. })
450 ));
451
452 Ok(())
453 }
454
455 #[test]
457 fn input_paths_new() -> TestResult {
458 let temp_dir = TempDir::new()?;
459 let temp_dir_path = temp_dir.path();
460 let temp_file = NamedTempFile::new()?;
461 let temp_file_path = temp_file.path();
462
463 let relative_path_a = PathBuf::from("some_file.txt");
464 let relative_path_b = PathBuf::from("some_other_file.txt");
465 let absolute_path_a = PathBuf::from("/some_file.txt");
466 let absolute_path_b = PathBuf::from("/some_other_file.txt");
467
468 assert!(
469 InputPaths::new(
470 temp_dir_path,
471 &[relative_path_a.clone(), relative_path_b.clone()]
472 )
473 .is_ok()
474 );
475 assert!(matches!(
476 InputPaths::new(temp_dir_path, &[absolute_path_a, absolute_path_b]),
477 Err(crate::Error::NonRelativePaths { .. })
478 ));
479 assert!(matches!(
480 InputPaths::new(temp_file_path, std::slice::from_ref(&relative_path_a)),
481 Err(crate::Error::NotADirectory { .. })
482 ));
483 assert!(matches!(
484 InputPaths::new(&relative_path_a, std::slice::from_ref(&relative_path_a)),
485 Err(crate::Error::NonAbsolutePaths { .. })
486 ));
487
488 Ok(())
489 }
490}