1use serde::{Deserialize, Serialize};
2use std::os::unix::fs::MetadataExt;
3use std::os::unix::prelude::PermissionsExt;
4
5pub trait Metadata {
6 fn uid(&self) -> u32;
7 fn gid(&self) -> u32;
8 fn atime(&self) -> i64;
9 fn atime_nsec(&self) -> i64;
10 fn mtime(&self) -> i64;
11 fn mtime_nsec(&self) -> i64;
12 fn permissions(&self) -> std::fs::Permissions;
13 fn ctime(&self) -> i64 {
16 0
17 }
18 fn ctime_nsec(&self) -> i64 {
19 0
20 }
21 fn size(&self) -> u64 {
24 0
25 }
26}
27
28impl Metadata for std::fs::Metadata {
29 fn uid(&self) -> u32 {
30 MetadataExt::uid(self)
31 }
32 fn gid(&self) -> u32 {
33 MetadataExt::gid(self)
34 }
35 fn atime(&self) -> i64 {
36 MetadataExt::atime(self)
37 }
38 fn atime_nsec(&self) -> i64 {
39 MetadataExt::atime_nsec(self)
40 }
41 fn mtime(&self) -> i64 {
42 MetadataExt::mtime(self)
43 }
44 fn mtime_nsec(&self) -> i64 {
45 MetadataExt::mtime_nsec(self)
46 }
47 fn permissions(&self) -> std::fs::Permissions {
48 self.permissions()
49 }
50 fn ctime(&self) -> i64 {
51 MetadataExt::ctime(self)
52 }
53 fn ctime_nsec(&self) -> i64 {
54 MetadataExt::ctime_nsec(self)
55 }
56 fn size(&self) -> u64 {
57 self.len()
58 }
59}
60
61#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
62pub struct UserAndTimeSettings {
63 pub uid: bool,
64 pub gid: bool,
65 pub time: bool,
66}
67
68impl UserAndTimeSettings {
69 #[must_use]
70 pub fn any(&self) -> bool {
71 self.uid || self.gid || self.time
72 }
73}
74
75pub type ModeMask = u32;
76
77#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
78pub struct FileSettings {
79 pub user_and_time: UserAndTimeSettings,
80 pub mode_mask: ModeMask,
81}
82
83impl Default for FileSettings {
84 fn default() -> Self {
85 Self {
86 user_and_time: UserAndTimeSettings::default(),
87 mode_mask: 0o0777, }
89 }
90}
91
92#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
93pub struct DirSettings {
94 pub user_and_time: UserAndTimeSettings,
95 pub mode_mask: ModeMask,
96}
97
98impl Default for DirSettings {
99 fn default() -> Self {
100 Self {
101 user_and_time: UserAndTimeSettings::default(),
102 mode_mask: 0o0777,
103 }
104 }
105}
106
107#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
108pub struct SymlinkSettings {
109 pub user_and_time: UserAndTimeSettings,
110}
111
112impl SymlinkSettings {
113 #[must_use]
114 pub fn any(&self) -> bool {
115 self.user_and_time.any()
116 }
117}
118
119#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
120pub struct Settings {
121 pub file: FileSettings,
122 pub dir: DirSettings,
123 pub symlink: SymlinkSettings,
124}
125
126#[must_use]
138pub fn masked_mode<Meta: Metadata>(mode_mask: ModeMask, metadata: &Meta) -> u32 {
139 let mode = metadata.permissions().mode() & 0o7777;
143 if mode_mask == 0o7777 {
144 mode
146 } else {
147 mode & mode_mask
148 }
149}
150
151#[must_use]
152pub fn preserve_all() -> Settings {
153 let user_and_time = UserAndTimeSettings {
154 uid: true,
155 gid: true,
156 time: true,
157 };
158
159 Settings {
160 file: FileSettings {
161 user_and_time,
162 mode_mask: 0o7777,
163 },
164 dir: DirSettings {
165 user_and_time,
166 mode_mask: 0o7777,
167 },
168 symlink: SymlinkSettings { user_and_time },
169 }
170}
171
172#[must_use]
173pub fn preserve_none() -> Settings {
174 Settings::default()
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use std::os::unix::fs::PermissionsExt;
181 struct FakeMeta {
182 mode: u32,
183 }
184 impl Metadata for FakeMeta {
185 fn uid(&self) -> u32 {
186 0
187 }
188 fn gid(&self) -> u32 {
189 0
190 }
191 fn atime(&self) -> i64 {
192 0
193 }
194 fn atime_nsec(&self) -> i64 {
195 0
196 }
197 fn mtime(&self) -> i64 {
198 0
199 }
200 fn mtime_nsec(&self) -> i64 {
201 0
202 }
203 fn permissions(&self) -> std::fs::Permissions {
204 std::fs::Permissions::from_mode(self.mode)
205 }
206 }
207 #[test]
208 fn default_mask_strips_setuid_setgid_sticky() {
209 let meta = FakeMeta { mode: 0o7755 };
211 assert_eq!(masked_mode(0o0777, &meta), 0o0755);
212 }
213 #[test]
214 fn full_mask_preserves_special_bits_verbatim() {
215 let meta = FakeMeta { mode: 0o4755 };
217 assert_eq!(masked_mode(0o7777, &meta), 0o4755);
218 }
219 #[test]
220 fn masking_strips_file_type_bits() {
221 let meta = FakeMeta { mode: 0o100644 };
223 assert_eq!(masked_mode(0o7777, &meta), 0o0644);
224 assert_eq!(masked_mode(0o0777, &meta), 0o0644);
225 }
226 #[test]
227 fn out_of_range_mask_cannot_leak_file_type_bits() {
228 let meta = FakeMeta { mode: 0o100644 };
231 assert_eq!(masked_mode(0o100777, &meta), 0o0644);
232 }
233 #[test]
234 fn custom_mask_applies() {
235 let meta = FakeMeta { mode: 0o0777 };
236 assert_eq!(masked_mode(0o0700, &meta), 0o0700);
237 }
238 #[test]
239 fn shipped_defaults_strip_special_bits_while_preserve_all_keeps_them() {
240 assert_eq!(FileSettings::default().mode_mask, 0o0777);
242 assert_eq!(DirSettings::default().mode_mask, 0o0777);
243 let all = preserve_all();
244 assert_eq!(all.file.mode_mask, 0o7777);
245 assert_eq!(all.dir.mode_mask, 0o7777);
246 }
247}