|
13 | 13 | //! You should have received a copy of the GNU General Public License |
14 | 14 | //! along with this program. If not, see <https://www.gnu.org/licenses/>. |
15 | 15 | use anyhow::{Context, Result}; |
16 | | -use std::{ |
17 | | - collections::{hash_map::Entry, HashMap}, |
18 | | - io::Write, |
19 | | - os::unix::{ |
20 | | - fs::MetadataExt, |
21 | | - prelude::{FileTypeExt, OsStrExt}, |
22 | | - }, |
23 | | - path::{Path, PathBuf}, |
24 | | -}; |
25 | | -use walkdir::WalkDir; |
| 16 | +use std::{io::Write, os::unix::prelude::OsStrExt, path::Path}; |
26 | 17 |
|
27 | 18 | // https://mgorny.pl/articles/portability-of-tar-features.html#id25 |
28 | 19 | const PAX_SCHILY_XATTR: &[u8; 13] = b"SCHILY.xattr."; |
29 | 20 |
|
30 | | -/// custom implementation of tar-rs's append_dir_all that: |
31 | | -/// - works around https://github.com/alexcrichton/tar-rs/issues/102 so that security capabilities are preserved |
32 | | -/// - emulates tar's `--clamp-mtime` option so that any file/dir/symlink mtimes are no later than a specific value |
33 | | -/// - supports hardlinks |
34 | | -pub(super) fn append_dir_all_with_xattrs( |
35 | | - builder: &mut tar::Builder<impl Write>, |
36 | | - src_path: impl AsRef<Path>, |
37 | | - clamp_mtime: i64, |
38 | | -) -> Result<()> { |
39 | | - let src_path = src_path.as_ref(); |
40 | | - // Map (dev, inode) -> path for hardlinks |
41 | | - let mut hardlinks: HashMap<(u64, u64), PathBuf> = HashMap::new(); |
42 | | - |
43 | | - for entry in WalkDir::new(src_path) |
44 | | - .follow_links(false) |
45 | | - .sort_by_file_name() |
46 | | - .into_iter() |
47 | | - { |
48 | | - let entry = entry?; |
49 | | - let meta = entry.metadata()?; |
50 | | - // skip sockets as tar-rs errors when trying to archive them. |
51 | | - // For comparison, umoci also errors, whereas docker skips them |
52 | | - if meta.file_type().is_socket() { |
53 | | - continue; |
54 | | - } |
55 | | - |
56 | | - let rel_path = pathdiff::diff_paths(entry.path(), src_path) |
57 | | - .expect("walkdir returns path inside of search root"); |
58 | | - if rel_path == Path::new("") { |
59 | | - continue; |
60 | | - } |
61 | | - |
62 | | - if entry.file_type().is_symlink() { |
63 | | - if meta.mtime() > clamp_mtime { |
64 | | - // Setting the mtime on a symlink is fiddly with tar-rs, so we use filetime to change |
65 | | - // the mtime before adding the symlink to the tar archive |
66 | | - let mtime = filetime::FileTime::from_unix_time(clamp_mtime, 0); |
67 | | - filetime::set_symlink_file_times(entry.path(), mtime, mtime)?; |
68 | | - } |
69 | | - add_pax_extension_header(entry.path(), builder)?; |
70 | | - builder.append_path_with_name(entry.path(), rel_path)?; |
71 | | - } else if entry.file_type().is_file() || entry.file_type().is_dir() { |
72 | | - add_pax_extension_header(entry.path(), builder)?; |
73 | | - |
74 | | - // If this is a hardlink, add a link header instead of the file |
75 | | - // if this isn't the first time we've seen this inode |
76 | | - if meta.nlink() > 1 { |
77 | | - match hardlinks.entry((meta.dev(), meta.ino())) { |
78 | | - Entry::Occupied(e) => { |
79 | | - // Add link header and continue to next entry |
80 | | - let mut header = tar::Header::new_gnu(); |
81 | | - header.set_metadata(&meta); |
82 | | - if meta.mtime() > clamp_mtime { |
83 | | - header.set_mtime(clamp_mtime as u64); |
84 | | - } |
85 | | - header.set_entry_type(tar::EntryType::Link); |
86 | | - header.set_cksum(); |
87 | | - builder.append_link(&mut header, &rel_path, e.get())?; |
88 | | - continue; |
89 | | - } |
90 | | - Entry::Vacant(e) => { |
91 | | - // This is the first time we've seen this inode |
92 | | - e.insert(rel_path.clone()); |
93 | | - } |
94 | | - } |
95 | | - } |
96 | | - |
97 | | - let mut header = tar::Header::new_gnu(); |
98 | | - header.set_size(meta.len()); |
99 | | - header.set_metadata(&meta); |
100 | | - if meta.mtime() > clamp_mtime { |
101 | | - header.set_mtime(clamp_mtime as u64); |
102 | | - } |
103 | | - if entry.file_type().is_file() { |
104 | | - builder.append_data( |
105 | | - &mut header, |
106 | | - rel_path, |
107 | | - &mut std::fs::File::open(entry.path())?, |
108 | | - )?; |
109 | | - } else { |
110 | | - builder.append_data(&mut header, rel_path, &mut std::io::empty())?; |
111 | | - }; |
112 | | - } |
113 | | - } |
114 | | - |
115 | | - Ok(()) |
116 | | -} |
117 | | - |
118 | 21 | // Convert any extended attributes on the specified path to a tar PAX extension header, and add it to the tar archive |
119 | | -fn add_pax_extension_header( |
| 22 | +pub(crate) fn add_pax_extension_header( |
120 | 23 | path: impl AsRef<Path>, |
121 | 24 | builder: &mut tar::Builder<impl Write>, |
122 | 25 | ) -> Result<(), anyhow::Error> { |
|
0 commit comments