Skip to main content

loadsmith_install/ops/
extract.rs

1use std::{
2    fs::File,
3    io::{self, Read, Seek},
4    path::{Path, PathBuf},
5};
6
7use tracing::{trace, warn};
8
9use crate::{
10    error::Result,
11    zip::{Zip, ZipFile},
12};
13
14pub fn extract<R: Read + Seek>(reader: R, target: impl AsRef<Path>) -> Result<Vec<PathBuf>> {
15    let mut zip = zip::ZipArchive::new(reader)?;
16    extract_zip(&mut zip, target.as_ref())
17}
18
19fn extract_zip<Z: Zip>(zip: &mut Z, target: impl AsRef<Path>) -> Result<Vec<PathBuf>> {
20    let t = crate::zip::private::Token;
21
22    let target = target.as_ref();
23    let mut files = Vec::new();
24
25    for i in 0..zip.len(t) {
26        let mut source_file = zip.by_index(i, t)?;
27
28        if source_file.is_dir(t) {
29            continue; // we create the necessary dirs when creating files instead
30        }
31
32        let relative_path = source_file.path(t)?;
33
34        let target_path = target.join(&relative_path);
35
36        if target_path.exists() {
37            warn!(%relative_path, "file already exists, skipping extraction");
38            continue;
39        }
40
41        trace!(%relative_path, "extract file");
42
43        loadsmith_util::create_parent_dirs(&target_path)?;
44
45        let mut target_file = File::create(&target_path)?;
46
47        io::copy(&mut source_file, &mut target_file)?;
48
49        #[cfg(unix)]
50        set_unix_mode(&source_file, &target_path)?;
51
52        files.push(target_path);
53    }
54
55    Ok(files)
56}
57
58#[cfg(unix)]
59fn set_unix_mode<F: ZipFile>(file: &F, path: &Path) -> Result<()> {
60    use std::os::unix::fs::PermissionsExt;
61
62    if let Some(mode) = file.unix_mode(crate::zip::private::Token) {
63        std::fs::set_permissions(path, PermissionsExt::from_mode(mode))?;
64    }
65
66    Ok(())
67}
68
69#[cfg(test)]
70mod tests {
71    use crate::zip::mock::MockZip;
72
73    use super::*;
74
75    #[test]
76    fn simple_glob() {
77        let included_files = &["file1", "nested/file2", "nested/../file3", "./././file4"];
78
79        let mut zip = MockZip::default();
80
81        for file in included_files {
82            zip = zip.with_empty_file(file);
83        }
84
85        let dir = tempfile::tempdir().unwrap();
86
87        extract_zip(&mut zip, &dir).unwrap();
88
89        for file in included_files {
90            assert!(dir.path().join(file).exists());
91        }
92    }
93}