Skip to main content

vfsi_core/api/
metadata.rs

1//! Options shared by scalar convenience and vector metadata queries.
2
3#[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/// Select metadata fields and whether to follow the final symlink.
13/// Ancestor symlinks use ordinary backend path-resolution semantics.
14#[derive(Clone, Copy, Debug)]
15pub struct AttrsOptions {
16    fields: crate::api::Attributes,
17    flags: AttrsFlags,
18}
19impl Default for AttrsOptions {
20    fn default() -> Self {
21        Self {
22            fields: crate::api::Attributes::stat(),
23            flags: AttrsFlags::new(),
24        }
25    }
26}
27impl AttrsOptions {
28    /// Standard stat fields, following the final symlink.
29    pub fn new() -> Self {
30        Self::default()
31    }
32    /// Select requested attributes. Object type may additionally be fetched.
33    pub fn fields(mut self, fields: crate::api::Attributes) -> Self {
34        self.fields = fields;
35        self
36    }
37    /// Follow the final symlink when true; otherwise describe the link itself.
38    pub fn follow_symlinks(mut self, follow: bool) -> Self {
39        self.flags.set_follow(follow);
40        self
41    }
42    /// Requested attributes; unavailable optional attributes remain absent.
43    pub fn requested_attributes(self) -> crate::api::Attributes {
44        self.fields
45    }
46    /// Whether to follow the final component if it is a symlink.
47    pub fn follows_symlinks(self) -> bool {
48        self.flags.follow()
49    }
50}
51
52/// One attribute update for a path or opened object.
53///
54/// Paths can be supplied directly. Use [`super::Target`] for handles
55/// or mixed path/handle batches. Unspecified fields are unchanged; construction
56/// performs no I/O. Final symlinks are followed by default.
57#[derive(Clone, Copy, Debug)]
58pub struct SetAttrsOp<T> {
59    target: T,
60    permissions: Option<crate::Permissions>,
61    uid: Option<u32>,
62    gid: Option<u32>,
63    len: Option<u64>,
64    accessed: Option<std::time::SystemTime>,
65    modified: Option<std::time::SystemTime>,
66    flags: AttrsFlags,
67}
68
69impl<T> SetAttrsOp<T> {
70    /// Prepare an update, following the final symlink for path targets.
71    pub fn new(target: T) -> Self {
72        Self {
73            target,
74            permissions: None,
75            uid: None,
76            gid: None,
77            len: None,
78            accessed: None,
79            modified: None,
80            flags: AttrsFlags::new(),
81        }
82    }
83
84    /// Follow the final path symlink when true, or modify the link itself when
85    /// false. Opened objects retain their identity regardless of this setting.
86    pub fn follow_symlinks(mut self, follow: bool) -> Self {
87        self.flags.set_follow(follow);
88        self
89    }
90
91    /// The path or opened-object operand.
92    pub fn target(&self) -> &T {
93        &self.target
94    }
95
96    /// Change permissions, leaving other unspecified attributes unchanged.
97    pub fn permissions(mut self, permissions: crate::Permissions) -> Self {
98        self.permissions = Some(permissions);
99        self
100    }
101
102    /// Change the owner. `u32::MAX` is reserved and rejected before dispatch.
103    pub fn uid(mut self, uid: u32) -> Self {
104        self.uid = Some(uid);
105        self
106    }
107
108    /// Change the group. `u32::MAX` is reserved and rejected before dispatch.
109    pub fn gid(mut self, gid: u32) -> Self {
110        self.gid = Some(gid);
111        self
112    }
113
114    /// Truncate or extend the file. Zero explicitly truncates it to an empty file.
115    pub fn len(mut self, len: u64) -> Self {
116        self.len = Some(len);
117        self
118    }
119
120    /// Set the access timestamp.
121    pub fn accessed(mut self, accessed: std::time::SystemTime) -> Self {
122        self.accessed = Some(accessed);
123        self
124    }
125
126    /// Set the modification timestamp.
127    pub fn modified(mut self, modified: std::time::SystemTime) -> Self {
128        self.modified = Some(modified);
129        self
130    }
131
132    /// Reuse all requested changes and the symlink policy with another target.
133    /// This constructs an operation without I/O or cloning the original target.
134    pub fn with_target<U>(&self, target: U) -> SetAttrsOp<U> {
135        SetAttrsOp {
136            target,
137            permissions: self.permissions,
138            uid: self.uid,
139            gid: self.gid,
140            len: self.len,
141            accessed: self.accessed,
142            modified: self.modified,
143            flags: self.flags,
144        }
145    }
146
147    /// Requested permission change, or `None` to leave permissions unchanged.
148    pub fn requested_permissions(&self) -> Option<crate::Permissions> {
149        self.permissions
150    }
151    /// Requested owner change, or `None` to leave the owner unchanged.
152    pub fn requested_uid(&self) -> Option<u32> {
153        self.uid
154    }
155    /// Requested group change, or `None` to leave the group unchanged.
156    pub fn requested_gid(&self) -> Option<u32> {
157        self.gid
158    }
159    /// Requested length, including zero, or `None` to leave the length unchanged.
160    pub fn requested_len(&self) -> Option<u64> {
161        self.len
162    }
163    /// Requested access timestamp, or `None` to leave it unchanged.
164    pub fn requested_accessed(&self) -> Option<std::time::SystemTime> {
165        self.accessed
166    }
167    /// Requested modification timestamp, or `None` to leave it unchanged.
168    pub fn requested_modified(&self) -> Option<std::time::SystemTime> {
169        self.modified
170    }
171
172    /// Whether the final path symlink is followed.
173    pub fn follows_symlinks(&self) -> bool {
174        self.flags.follow()
175    }
176}
177
178impl<'a, F> SetAttrsOp<super::Target<'a, F>> {
179    /// Prepare an update for an opened object without borrowing its pathname.
180    /// The handle remains owned by the caller and is checked at dispatch.
181    ///
182    /// ```no_run
183    /// use vfsi_core::api::{SetAttrsOp, Vfsi};
184    /// # fn example<F: Vfsi>(fs: &F, file: &F::File) -> vfsi_core::api::Result<()> {
185    /// fs.vsetattrs(&[SetAttrsOp::file(file).len(1024)])?;
186    /// # Ok(())
187    /// # }
188    /// ```
189    pub fn file(file: &'a F) -> Self {
190        Self::new(super::Target::File(file))
191    }
192}
193
194#[cfg(test)]
195mod option_layout_tests {
196    use super::*;
197    #[test]
198    fn retargeting_preserves_all_changes_without_cloning_the_target() {
199        struct NonClone;
200        let permissions = crate::Permissions::from_mode(0o640);
201        let accessed = std::time::UNIX_EPOCH + std::time::Duration::from_secs(123);
202        let modified = std::time::UNIX_EPOCH - std::time::Duration::from_secs(456);
203        let empty = SetAttrsOp::new(NonClone);
204        assert!(empty.follows_symlinks());
205        assert_eq!(empty.requested_permissions(), None);
206        assert_eq!(empty.requested_len(), None);
207        assert_eq!(empty.requested_uid(), None);
208        assert_eq!(empty.requested_gid(), None);
209        assert_eq!(empty.requested_accessed(), None);
210        assert_eq!(empty.requested_modified(), None);
211        let op = empty
212            .permissions(permissions)
213            .len(0)
214            .uid(42)
215            .gid(43)
216            .accessed(accessed)
217            .modified(modified)
218            .follow_symlinks(false);
219        for path in ["/first", "/second"] {
220            let rebound = op.with_target(path);
221            assert_eq!(*rebound.target(), path);
222            assert_eq!(rebound.requested_permissions(), Some(permissions));
223            assert_eq!(rebound.requested_len(), Some(0));
224            assert_eq!(rebound.requested_uid(), Some(42));
225            assert_eq!(rebound.requested_gid(), Some(43));
226            assert_eq!(rebound.requested_accessed(), Some(accessed));
227            assert_eq!(rebound.requested_modified(), Some(modified));
228            assert!(!rebound.follows_symlinks());
229        }
230        let replaced = op
231            .with_target("/third")
232            .len(10)
233            .uid(44)
234            .follow_symlinks(true);
235        assert_eq!(replaced.requested_len(), Some(10));
236        assert_eq!(replaced.requested_uid(), Some(44));
237        assert!(replaced.follows_symlinks());
238        assert_eq!(op.requested_len(), Some(0));
239        assert_eq!(op.requested_uid(), Some(42));
240        assert!(!op.follows_symlinks());
241    }
242    #[test]
243    fn packed_follow_flag_preserves_selected_fields() {
244        assert_eq!(std::mem::size_of::<AttrsFlags>(), 1);
245        assert!(AttrsOptions::default().follows_symlinks());
246        let op = SetAttrsOp::new("/file").len(0).uid(42);
247        assert_eq!(*op.target(), "/file");
248        assert!(op.follows_symlinks());
249        let op = op.follow_symlinks(false);
250        assert!(!op.follows_symlinks());
251        assert_eq!(op.requested_len(), Some(0));
252        assert_eq!(op.requested_uid(), Some(42));
253        let fields = crate::api::Attributes::SIZE | crate::api::Attributes::CHANGE;
254        let options = AttrsOptions::new().fields(fields);
255        for follow in [false, true, false] {
256            let changed = options.follow_symlinks(follow);
257            assert_eq!(changed.follows_symlinks(), follow);
258            assert_eq!(changed.requested_attributes(), fields);
259            assert!(options.follows_symlinks());
260        }
261    }
262}