openssh_sftp_client/
metadata.rs

1use super::{
2    lowlevel::{FileAttrs, FileType as SftpFileType, Permissions as SftpPermissions},
3    UnixTimeStamp,
4};
5
6/// Builder of [`MetaData`].
7#[derive(Debug, Default, Copy, Clone)]
8pub struct MetaDataBuilder(FileAttrs);
9
10impl MetaDataBuilder {
11    /// Create a builder.
12    pub const fn new() -> Self {
13        Self(FileAttrs::new())
14    }
15
16    /// Reset builder back to default.
17    pub fn reset(&mut self) -> &mut Self {
18        self.0 = FileAttrs::new();
19        self
20    }
21
22    /// Set id of the metadata to be built.
23    pub fn id(&mut self, (uid, gid): (u32, u32)) -> &mut Self {
24        self.0.set_id(uid, gid);
25        self
26    }
27
28    /// Set permissions of the metadata to be built.
29    pub fn permissions(&mut self, perm: Permissions) -> &mut Self {
30        self.0.set_permissions(perm.0);
31        self
32    }
33
34    /// Set size of the metadata to built.
35    pub fn len(&mut self, len: u64) -> &mut Self {
36        self.0.set_size(len);
37        self
38    }
39
40    /// Set accessed and modified time of the metadata to be built.
41    pub fn time(&mut self, accessed: UnixTimeStamp, modified: UnixTimeStamp) -> &mut Self {
42        self.0.set_time(accessed.0, modified.0);
43        self
44    }
45
46    /// Create a [`MetaData`].
47    pub fn create(&self) -> MetaData {
48        MetaData::new(self.0)
49    }
50}
51
52/// Metadata information about a file.
53#[repr(transparent)]
54#[derive(Debug, Clone, Copy, Eq, PartialEq)]
55pub struct MetaData(FileAttrs);
56
57#[allow(clippy::len_without_is_empty)]
58impl MetaData {
59    pub(super) fn new(attrs: FileAttrs) -> Self {
60        Self(attrs)
61    }
62
63    pub(super) fn into_inner(self) -> FileAttrs {
64        self.0
65    }
66
67    /// Returns the size of the file in bytes.
68    ///
69    /// Return `None` if the server did not return
70    /// the size.
71    pub fn len(&self) -> Option<u64> {
72        self.0.get_size()
73    }
74
75    /// Returns the user ID of the owner.
76    ///
77    /// Return `None` if the server did not return
78    /// the uid.
79    pub fn uid(&self) -> Option<u32> {
80        self.0.get_id().map(|(uid, _gid)| uid)
81    }
82
83    /// Returns the group ID of the owner.
84    ///
85    /// Return `None` if the server did not return
86    /// the gid.
87    pub fn gid(&self) -> Option<u32> {
88        self.0.get_id().map(|(_uid, gid)| gid)
89    }
90
91    /// Returns the permissions.
92    ///
93    /// Return `None` if the server did not return
94    /// the permissions.
95    pub fn permissions(&self) -> Option<Permissions> {
96        self.0.get_permissions().map(Permissions)
97    }
98
99    /// Returns the file type.
100    ///
101    /// Return `None` if the server did not return
102    /// the file type.
103    pub fn file_type(&self) -> Option<FileType> {
104        self.0.get_filetype().map(FileType)
105    }
106
107    /// Returns the last access time.
108    ///
109    /// Return `None` if the server did not return
110    /// the last access time.
111    pub fn accessed(&self) -> Option<UnixTimeStamp> {
112        self.0
113            .get_time()
114            .map(|(atime, _mtime)| atime)
115            .map(UnixTimeStamp)
116    }
117
118    /// Returns the last modification time.
119    ///
120    /// Return `None` if the server did not return
121    /// the last modification time.
122    pub fn modified(&self) -> Option<UnixTimeStamp> {
123        self.0
124            .get_time()
125            .map(|(_atime, mtime)| mtime)
126            .map(UnixTimeStamp)
127    }
128}
129
130/// A structure representing a type of file with accessors for each file type.
131/// It is returned by [`MetaData::file_type`] method.
132#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
133pub struct FileType(SftpFileType);
134
135impl FileType {
136    /// Tests whether this file type represents a directory.
137    pub fn is_dir(&self) -> bool {
138        self.0 == SftpFileType::Directory
139    }
140
141    /// Tests whether this file type represents a regular file.
142    pub fn is_file(&self) -> bool {
143        self.0 == SftpFileType::RegularFile
144    }
145
146    /// Tests whether this file type represents a symbolic link.
147    pub fn is_symlink(&self) -> bool {
148        self.0 == SftpFileType::Symlink
149    }
150
151    /// Tests whether this file type represents a fifo.
152    pub fn is_fifo(&self) -> bool {
153        self.0 == SftpFileType::FIFO
154    }
155
156    /// Tests whether this file type represents a socket.
157    pub fn is_socket(&self) -> bool {
158        self.0 == SftpFileType::Socket
159    }
160
161    /// Tests whether this file type represents a block device.
162    pub fn is_block_device(&self) -> bool {
163        self.0 == SftpFileType::BlockDevice
164    }
165
166    /// Tests whether this file type represents a character device.
167    pub fn is_char_device(&self) -> bool {
168        self.0 == SftpFileType::CharacterDevice
169    }
170}
171
172/// Representation of the various permissions on a file.
173#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
174pub struct Permissions(SftpPermissions);
175
176macro_rules! impl_getter_setter {
177    ($getter_name:ident, $setter_name:ident, $variant:ident, $variant_name:expr) => {
178        #[doc = "Tests whether "]
179        #[doc = $variant_name]
180        #[doc = " bit is set."]
181        pub fn $getter_name(&self) -> bool {
182            self.0.intersects(SftpPermissions::$variant)
183        }
184
185        #[doc = "Modify the "]
186        #[doc = $variant_name]
187        #[doc = " bit."]
188        pub fn $setter_name(&mut self, value: bool) -> &mut Self {
189            self.0.set(SftpPermissions::$variant, value);
190            self
191        }
192    };
193}
194
195impl Permissions {
196    /// Create a new permissions object with zero permissions
197    /// set.
198    pub const fn new() -> Self {
199        Self(SftpPermissions::empty())
200    }
201
202    impl_getter_setter!(suid, set_suid, SET_UID, "set-user-id");
203    impl_getter_setter!(sgid, set_sgid, SET_GID, "set-group-id");
204    impl_getter_setter!(svtx, set_vtx, SET_VTX, "set-sticky-bit");
205
206    impl_getter_setter!(
207        read_by_owner,
208        set_read_by_owner,
209        READ_BY_OWNER,
210        "read by owner"
211    );
212    impl_getter_setter!(
213        write_by_owner,
214        set_write_by_owner,
215        WRITE_BY_OWNER,
216        "write by owner"
217    );
218    impl_getter_setter!(
219        execute_by_owner,
220        set_execute_by_owner,
221        EXECUTE_BY_OWNER,
222        "execute by owner"
223    );
224
225    impl_getter_setter!(
226        read_by_group,
227        set_read_by_group,
228        READ_BY_GROUP,
229        "read by group"
230    );
231    impl_getter_setter!(
232        write_by_group,
233        set_write_by_group,
234        WRITE_BY_GROUP,
235        "write by group"
236    );
237    impl_getter_setter!(
238        execute_by_group,
239        set_execute_by_group,
240        EXECUTE_BY_GROUP,
241        "execute by group"
242    );
243
244    impl_getter_setter!(
245        read_by_other,
246        set_read_by_other,
247        READ_BY_OTHER,
248        "read by other"
249    );
250    impl_getter_setter!(
251        write_by_other,
252        set_write_by_other,
253        WRITE_BY_OTHER,
254        "write by other"
255    );
256    impl_getter_setter!(
257        execute_by_other,
258        set_execute_by_other,
259        EXECUTE_BY_OTHER,
260        "execute by other"
261    );
262
263    /// Returns `true` if these permissions describe an unwritable file
264    /// that no one can write to.
265    pub fn readonly(&self) -> bool {
266        !self.write_by_owner() && !self.write_by_group() && !self.write_by_other()
267    }
268
269    /// Modifies the readonly flag for this set of permissions.
270    ///
271    /// If the readonly argument is true, it will remove write permissions
272    /// from all parties.
273    ///
274    /// Conversely, if it’s false, it will permit writing from all parties.
275    ///
276    /// This operation does not modify the filesystem.
277    ///
278    /// To modify the filesystem use the [`super::fs::Fs::set_permissions`] or
279    /// the [`super::file::File::set_permissions`] function.
280    pub fn set_readonly(&mut self, readonly: bool) {
281        let writable = !readonly;
282
283        self.set_write_by_owner(writable);
284        self.set_write_by_group(writable);
285        self.set_write_by_other(writable);
286    }
287}
288
289impl From<u16> for Permissions {
290    /// Converts numeric file mode bits permission into a [`Permissions`] object.
291    ///
292    /// The [numerical file mode bits](https://www.gnu.org/software/coreutils/manual/html_node/Numeric-Modes.html) are defined as follows:
293    ///
294    /// Special mode bits:
295    /// 4000      Set user ID
296    /// 2000      Set group ID
297    /// 1000      Restricted deletion flag or sticky bit
298    ///
299    /// The file's owner:
300    ///  400      Read
301    ///  200      Write
302    ///  100      Execute/search
303    ///
304    /// Other users in the file's group:
305    ///   40      Read
306    ///   20      Write
307    ///   10      Execute/search
308    ///
309    /// Other users not in the file's group:
310    ///    4      Read
311    ///    2      Write
312    ///    1      Execute/search
313    ///
314    fn from(octet: u16) -> Self {
315        let mut result = Permissions::new();
316
317        // Lowest three bits, other
318        result.set_execute_by_other(octet & 0o1 != 0);
319        result.set_write_by_other(octet & 0o2 != 0);
320        result.set_read_by_other(octet & 0o4 != 0);
321
322        // Middle three bits, group
323        result.set_execute_by_group(octet & 0o10 != 0);
324        result.set_write_by_group(octet & 0o20 != 0);
325        result.set_read_by_group(octet & 0o40 != 0);
326
327        // Highest three bits, owner
328        result.set_execute_by_owner(octet & 0o100 != 0);
329        result.set_write_by_owner(octet & 0o200 != 0);
330        result.set_read_by_owner(octet & 0o400 != 0);
331
332        // Extra bits, sticky and setuid/setgid
333        result.set_vtx(octet & 0o1000 != 0);
334        result.set_sgid(octet & 0o2000 != 0);
335        result.set_sgid(octet & 0o4000 != 0);
336
337        result
338    }
339}
340
341impl Default for Permissions {
342    fn default() -> Self {
343        Self::new()
344    }
345}