1#[bitfields::bitfield(u8)]
4#[derive(PartialEq, Eq)]
5struct AttrsFlags {
6 #[bits(default = true)]
7 follow: bool,
8 #[bits(7)]
9 _reserved: u8,
10}
11
12#[derive(Clone, Copy, Debug)]
19#[must_use = "configuration and requests have no effect until used"]
20pub struct AttrsOptions {
21 fields: crate::api::Attributes,
22 flags: AttrsFlags,
23}
24impl Default for AttrsOptions {
25 fn default() -> Self {
26 Self {
27 fields: crate::api::Attributes::stat(),
28 flags: AttrsFlags::new(),
29 }
30 }
31}
32impl AttrsOptions {
33 pub fn new() -> Self {
35 Self::default()
36 }
37 pub fn with_fields(mut self, fields: crate::api::Attributes) -> Self {
39 self.fields = fields;
40 self
41 }
42 pub fn with_follow_final_symlink(mut self, follow: bool) -> Self {
44 self.flags.set_follow(follow);
45 self
46 }
47 pub fn fields(self) -> crate::api::Attributes {
49 self.fields
50 }
51 pub fn follow_final_symlink(self) -> bool {
53 self.flags.follow()
54 }
55}
56
57#[derive(Clone, Copy)]
63#[must_use = "configuration and requests have no effect until used"]
64pub struct SetAttrsOp<T> {
65 target: T,
66 permissions: crate::Permissions,
67 uid: u32,
68 gid: u32,
69 len: u64,
70 accessed: Option<std::time::SystemTime>,
71 modified: Option<std::time::SystemTime>,
72 flags: AttrsFlags,
73 present: u8,
74}
75
76impl<T> SetAttrsOp<T> {
77 pub fn new(target: T) -> Self {
79 Self {
80 target,
81 permissions: crate::Permissions::from_mode(0),
82 uid: 0,
83 gid: 0,
84 len: 0,
85 accessed: None,
86 modified: None,
87 flags: AttrsFlags::new(),
88 present: 0,
89 }
90 }
91
92 pub fn with_follow_final_symlink(mut self, follow: bool) -> Self {
95 self.flags.set_follow(follow);
96 self
97 }
98
99 pub fn target(&self) -> &T {
101 &self.target
102 }
103
104 pub fn permissions(mut self, permissions: crate::Permissions) -> Self {
106 self.permissions = permissions;
107 self.present |= 1 << 0;
108 self
109 }
110
111 pub fn uid(mut self, uid: u32) -> Self {
113 self.uid = uid;
114 self.present |= 1 << 1;
115 self
116 }
117
118 pub fn gid(mut self, gid: u32) -> Self {
120 self.gid = gid;
121 self.present |= 1 << 2;
122 self
123 }
124
125 pub fn len(mut self, len: u64) -> Self {
127 self.len = len;
128 self.present |= 1 << 3;
129 self
130 }
131
132 pub fn accessed(mut self, accessed: std::time::SystemTime) -> Self {
134 self.accessed = Some(accessed);
135 self
136 }
137
138 pub fn modified(mut self, modified: std::time::SystemTime) -> Self {
140 self.modified = Some(modified);
141 self
142 }
143
144 pub fn with_target<U>(&self, target: U) -> SetAttrsOp<U> {
147 SetAttrsOp {
148 target,
149 permissions: self.permissions,
150 uid: self.uid,
151 gid: self.gid,
152 len: self.len,
153 accessed: self.accessed,
154 modified: self.modified,
155 flags: self.flags,
156 present: self.present,
157 }
158 }
159
160 pub fn requested_permissions(&self) -> Option<crate::Permissions> {
162 (self.present & (1 << 0) != 0).then_some(self.permissions)
163 }
164 pub fn requested_uid(&self) -> Option<u32> {
166 (self.present & (1 << 1) != 0).then_some(self.uid)
167 }
168 pub fn requested_gid(&self) -> Option<u32> {
170 (self.present & (1 << 2) != 0).then_some(self.gid)
171 }
172 pub fn requested_len(&self) -> Option<u64> {
174 (self.present & (1 << 3) != 0).then_some(self.len)
175 }
176 pub fn requested_accessed(&self) -> Option<std::time::SystemTime> {
178 self.accessed
179 }
180 pub fn requested_modified(&self) -> Option<std::time::SystemTime> {
182 self.modified
183 }
184
185 pub fn follow_final_symlink(&self) -> bool {
187 self.flags.follow()
188 }
189}
190
191impl<T: std::fmt::Debug> std::fmt::Debug for SetAttrsOp<T> {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 f.debug_struct("SetAttrsOp")
194 .field("target", self.target())
195 .field("permissions", &self.requested_permissions())
196 .field("uid", &self.requested_uid())
197 .field("gid", &self.requested_gid())
198 .field("len", &self.requested_len())
199 .field("accessed", &self.requested_accessed())
200 .field("modified", &self.requested_modified())
201 .field("flags", &self.flags)
202 .finish()
203 }
204}
205impl<'a, F> SetAttrsOp<super::Target<'a, F>> {
206 pub fn file(file: &'a F) -> Self {
217 Self::new(super::Target::File(file))
218 }
219}
220
221#[cfg(test)]
222mod option_layout_tests {
223 use super::*;
224 #[test]
225 fn retargeting_preserves_all_changes_without_cloning_the_target() {
226 struct NonClone;
227 let permissions = crate::Permissions::from_mode(0o640);
228 let accessed = std::time::UNIX_EPOCH + std::time::Duration::from_secs(123);
229 let modified = std::time::UNIX_EPOCH - std::time::Duration::from_secs(456);
230 let empty = SetAttrsOp::new(NonClone);
231 assert!(empty.follow_final_symlink());
232 assert_eq!(empty.requested_permissions(), None);
233 assert_eq!(empty.requested_len(), None);
234 assert_eq!(empty.requested_uid(), None);
235 assert_eq!(empty.requested_gid(), None);
236 assert_eq!(empty.requested_accessed(), None);
237 assert_eq!(empty.requested_modified(), None);
238 let op = empty
239 .permissions(permissions)
240 .len(0)
241 .uid(42)
242 .gid(43)
243 .accessed(accessed)
244 .modified(modified)
245 .with_follow_final_symlink(false);
246 for path in ["/first", "/second"] {
247 let rebound = op.with_target(path);
248 assert_eq!(*rebound.target(), path);
249 assert_eq!(rebound.requested_permissions(), Some(permissions));
250 assert_eq!(rebound.requested_len(), Some(0));
251 assert_eq!(rebound.requested_uid(), Some(42));
252 assert_eq!(rebound.requested_gid(), Some(43));
253 assert_eq!(rebound.requested_accessed(), Some(accessed));
254 assert_eq!(rebound.requested_modified(), Some(modified));
255 assert!(!rebound.follow_final_symlink());
256 }
257 let replaced = op
258 .with_target("/third")
259 .len(10)
260 .uid(44)
261 .with_follow_final_symlink(true);
262 assert_eq!(replaced.requested_len(), Some(10));
263 assert_eq!(replaced.requested_uid(), Some(44));
264 assert!(replaced.follow_final_symlink());
265 assert_eq!(op.requested_len(), Some(0));
266 assert_eq!(op.requested_uid(), Some(42));
267 assert!(!op.follow_final_symlink());
268 }
269 #[test]
270 fn packed_follow_flag_preserves_selected_fields() {
271 assert_eq!(std::mem::size_of::<AttrsFlags>(), 1);
272 assert!(AttrsOptions::default().follow_final_symlink());
273 let op = SetAttrsOp::new("/file").len(0).uid(42);
274 assert_eq!(*op.target(), "/file");
275 assert!(op.follow_final_symlink());
276 let op = op.with_follow_final_symlink(false);
277 assert!(!op.follow_final_symlink());
278 assert_eq!(op.requested_len(), Some(0));
279 assert_eq!(op.requested_uid(), Some(42));
280 let fields = crate::api::Attributes::SIZE | crate::api::Attributes::CHANGE;
281 let options = AttrsOptions::new().with_fields(fields);
282 for follow in [false, true, false] {
283 let changed = options.with_follow_final_symlink(follow);
284 assert_eq!(changed.follow_final_symlink(), follow);
285 assert_eq!(changed.fields(), fields);
286 assert!(options.follow_final_symlink());
287 }
288 }
289}
290
291#[cfg(test)]
292mod compact_update_tests {
293 use super::*;
294 #[test]
295 fn integer_presence_is_independent_and_keeps_zero_and_maximum_values() {
296 if cfg!(target_pointer_width = "64") {
297 assert_eq!(std::mem::size_of::<SetAttrsOp<&std::path::Path>>(), 72);
298 }
299 for bits in 0u8..16 {
300 for maximum in [false, true] {
301 let integer = if maximum { u32::MAX } else { 0 };
302 let length = if maximum { u64::MAX } else { 0 };
303 let permissions = crate::Permissions::from_mode(integer);
304 let mut op = SetAttrsOp::new("/file");
305 if bits & 1 != 0 {
306 op = op.permissions(permissions);
307 }
308 if bits & 2 != 0 {
309 op = op.uid(integer);
310 }
311 if bits & 4 != 0 {
312 op = op.gid(integer);
313 }
314 if bits & 8 != 0 {
315 op = op.len(length);
316 }
317 for op in [
318 op,
319 op.with_target("/other").with_follow_final_symlink(false),
320 ] {
321 assert_eq!(
322 op.requested_permissions(),
323 (bits & 1 != 0).then_some(permissions)
324 );
325 assert_eq!(op.requested_uid(), (bits & 2 != 0).then_some(integer));
326 assert_eq!(op.requested_gid(), (bits & 4 != 0).then_some(integer));
327 assert_eq!(op.requested_len(), (bits & 8 != 0).then_some(length));
328 assert_eq!(op.requested_accessed(), None);
329 assert_eq!(op.requested_modified(), None);
330 }
331 }
332 }
333 }
334}