russh_sftp/protocol/
file_attrs.rs

1use serde::{de::Visitor, ser::SerializeStruct, Deserialize, Deserializer, Serialize};
2#[cfg(unix)]
3use std::os::unix::fs::MetadataExt;
4use std::{
5    fmt,
6    fs::Metadata,
7    io::ErrorKind,
8    time::{Duration, SystemTime, UNIX_EPOCH},
9};
10
11use crate::utils;
12
13/// Attributes flags according to the specification
14#[derive(Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15pub struct FileAttr(u32);
16
17/// Type according to mode unix
18#[derive(Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub struct FileMode(u32);
20
21/// Type describing permission flags
22#[derive(Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23pub struct FilePermissionFlags(u32);
24
25bitflags! {
26    impl FileAttr: u32 {
27        const SIZE = 0x00000001;
28        const UIDGID = 0x00000002;
29        const PERMISSIONS = 0x00000004;
30        const ACMODTIME = 0x00000008;
31        const EXTENDED = 0x80000000;
32    }
33
34    impl FileMode: u32 {
35        const FIFO = 0x1000;
36        const CHR = 0x2000;
37        const DIR = 0x4000;
38        const NAM = 0x5000;
39        const BLK = 0x6000;
40        const REG = 0x8000;
41        const LNK = 0xA000;
42        const SOCK = 0xC000;
43    }
44
45    impl FilePermissionFlags: u32 {
46        const OTHER_READ = 0o4;
47        const OTHER_WRITE = 0o2;
48        const OTHER_EXEC = 0o1;
49        const GROUP_READ = 0o40;
50        const GROUP_WRITE = 0o20;
51        const GROUP_EXEC = 0o10;
52        const OWNER_READ = 0o400;
53        const OWNER_WRITE = 0o200;
54        const OWNER_EXEC = 0o100;
55    }
56}
57
58/// Represents a simplified version of the [`FileMode`]
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum FileType {
61    Dir,
62    File,
63    Symlink,
64    Other,
65}
66
67impl FileType {
68    /// Returns `true` if the file is a directory
69    pub fn is_dir(&self) -> bool {
70        matches!(self, Self::Dir)
71    }
72
73    /// Returns `true` if the file is a file
74    pub fn is_file(&self) -> bool {
75        matches!(self, Self::File)
76    }
77
78    /// Returns `true` if the file is a symlink
79    pub fn is_symlink(&self) -> bool {
80        matches!(self, Self::Symlink)
81    }
82
83    /// Returns `true` if the file has a distinctive type
84    pub fn is_other(&self) -> bool {
85        matches!(self, Self::Other)
86    }
87}
88
89impl From<FileMode> for FileType {
90    fn from(mode: FileMode) -> Self {
91        if mode == FileMode::DIR {
92            FileType::Dir
93        } else if mode == FileMode::LNK {
94            FileType::Symlink
95        } else if mode == FileMode::REG {
96            FileType::File
97        } else {
98            FileType::Other
99        }
100    }
101}
102
103impl From<u32> for FileType {
104    fn from(mode: u32) -> Self {
105        FileMode::from_bits_truncate(mode).into()
106    }
107}
108
109#[derive(Default, Clone, Copy, PartialEq, Eq)]
110pub struct FilePermissions {
111    pub other_exec: bool,
112    pub other_read: bool,
113    pub other_write: bool,
114    pub group_exec: bool,
115    pub group_read: bool,
116    pub group_write: bool,
117    pub owner_exec: bool,
118    pub owner_read: bool,
119    pub owner_write: bool,
120}
121
122impl FilePermissions {
123    /// Returns `true` if the file is read-only.
124    pub fn is_readonly(&self) -> bool {
125        !self.other_write && !self.group_write && !self.owner_write
126    }
127
128    /// Clears all flags or sets them to a positive value. Works for unix.
129    pub fn set_readonly(&mut self, readonly: bool) {
130        self.other_exec = !readonly;
131        self.other_write = !readonly;
132        self.group_exec = !readonly;
133        self.group_write = !readonly;
134        self.owner_exec = !readonly;
135        self.owner_write = !readonly;
136
137        if readonly {
138            self.other_read = true;
139            self.group_read = true;
140            self.owner_read = true;
141        }
142    }
143}
144
145impl fmt::Display for FilePermissions {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        write!(
148            f,
149            "{}{}{}{}{}{}{}{}{}",
150            if self.owner_read { "r" } else { "-" },
151            if self.owner_write { "w" } else { "-" },
152            if self.owner_exec { "x" } else { "-" },
153            if self.group_read { "r" } else { "-" },
154            if self.group_write { "w" } else { "-" },
155            if self.group_exec { "x" } else { "-" },
156            if self.other_read { "r" } else { "-" },
157            if self.other_write { "w" } else { "-" },
158            if self.other_exec { "x" } else { "-" },
159        )
160    }
161}
162
163impl From<FilePermissionFlags> for FilePermissions {
164    fn from(flags: FilePermissionFlags) -> Self {
165        Self {
166            other_read: flags.contains(FilePermissionFlags::OTHER_READ),
167            other_write: flags.contains(FilePermissionFlags::OTHER_WRITE),
168            other_exec: flags.contains(FilePermissionFlags::OTHER_EXEC),
169            group_read: flags.contains(FilePermissionFlags::GROUP_READ),
170            group_write: flags.contains(FilePermissionFlags::GROUP_WRITE),
171            group_exec: flags.contains(FilePermissionFlags::GROUP_EXEC),
172            owner_read: flags.contains(FilePermissionFlags::OWNER_READ),
173            owner_write: flags.contains(FilePermissionFlags::OWNER_WRITE),
174            owner_exec: flags.contains(FilePermissionFlags::OWNER_EXEC),
175        }
176    }
177}
178
179impl From<u32> for FilePermissions {
180    fn from(mode: u32) -> Self {
181        FilePermissionFlags::from_bits_truncate(mode).into()
182    }
183}
184
185/// Used in the implementation of other packets.
186/// Implements most [`Metadata`] methods
187///
188/// The fields `user` and `group` are string names of users and groups for
189/// clients that can be displayed in longname. Can be omitted.
190///
191/// The `flags` field is omitted because it is set by itself depending on the fields
192#[derive(Debug, Clone)]
193pub struct FileAttributes {
194    pub size: Option<u64>,
195    pub uid: Option<u32>,
196    pub user: Option<String>,
197    pub gid: Option<u32>,
198    pub group: Option<String>,
199    pub permissions: Option<u32>,
200    pub atime: Option<u32>,
201    pub mtime: Option<u32>,
202}
203
204macro_rules! impl_fn_type {
205    ($get_name:ident, $set_name:ident, $doc_name:expr, $flag:ident) => {
206        #[doc = "Returns `true` if is a "]
207        #[doc = $doc_name]
208        pub fn $get_name(&self) -> bool {
209            self.permissions.map_or(false, |b| {
210                FileMode::from_bits_truncate(b).contains(FileMode::$flag)
211            })
212        }
213
214        #[doc = "Set flag if is a "]
215        #[doc = $doc_name]
216        #[doc = " or not"]
217        pub fn $set_name(&mut self, $get_name: bool) {
218            match $get_name {
219                true => self.set_type(FileMode::$flag),
220                false => self.remove_type(FileMode::$flag),
221            }
222        }
223    };
224}
225
226impl FileAttributes {
227    impl_fn_type!(is_dir, set_dir, "dir", DIR);
228    impl_fn_type!(is_regular, set_regular, "regular", REG);
229    impl_fn_type!(is_symlink, set_symlink, "symlink", LNK);
230    impl_fn_type!(is_character, set_character, "character", CHR);
231    impl_fn_type!(is_block, set_block, "block", BLK);
232    impl_fn_type!(is_fifo, set_fifo, "fifo", FIFO);
233
234    /// Set mode flag
235    pub fn set_type(&mut self, mode: FileMode) {
236        let perms = self.permissions.unwrap_or(0);
237        self.permissions = Some(perms | mode.bits());
238    }
239
240    /// Remove mode flag
241    pub fn remove_type(&mut self, mode: FileMode) {
242        let perms = self.permissions.unwrap_or(0);
243        self.permissions = Some(perms & !mode.bits());
244    }
245
246    /// Returns the file type
247    pub fn file_type(&self) -> FileType {
248        FileMode::from_bits_truncate(self.permissions.unwrap_or_default()).into()
249    }
250
251    /// Returns `true` if is empty
252    pub fn is_empty(&self) -> bool {
253        self.len() == 0
254    }
255
256    /// Returns the size of the file
257    pub fn len(&self) -> u64 {
258        self.size.unwrap_or(0)
259    }
260
261    /// Returns the permissions of the file this metadata is for.
262    pub fn permissions(&self) -> FilePermissions {
263        FilePermissionFlags::from_bits_truncate(self.permissions.unwrap_or_default()).into()
264    }
265
266    /// Returns the last access time
267    pub fn accessed(&self) -> std::io::Result<SystemTime> {
268        match self.atime {
269            Some(time) => Ok(UNIX_EPOCH + Duration::from_secs(time as u64)),
270            None => Err(ErrorKind::InvalidData.into()),
271        }
272    }
273
274    /// Returns the last modification time
275    pub fn modified(&self) -> std::io::Result<SystemTime> {
276        match self.mtime {
277            Some(time) => Ok(UNIX_EPOCH + Duration::from_secs(time as u64)),
278            None => Err(ErrorKind::InvalidData.into()),
279        }
280    }
281
282    /// Creates a structure with omitted attributes
283    pub fn empty() -> Self {
284        Self {
285            size: None,
286            uid: None,
287            user: None,
288            gid: None,
289            group: None,
290            permissions: None,
291            atime: None,
292            mtime: None,
293        }
294    }
295}
296
297/// For packets which require dummy attributes
298impl Default for FileAttributes {
299    fn default() -> Self {
300        Self {
301            size: Some(0),
302            uid: Some(0),
303            user: None,
304            gid: Some(0),
305            group: None,
306            permissions: Some(0o777 | FileMode::DIR.bits()),
307            atime: Some(0),
308            mtime: Some(0),
309        }
310    }
311}
312
313/// For simple conversion of [`Metadata`] into [`FileAttributes`]
314impl From<&Metadata> for FileAttributes {
315    fn from(metadata: &Metadata) -> Self {
316        let mut attrs = Self {
317            size: Some(metadata.len()),
318            #[cfg(unix)]
319            uid: Some(metadata.uid()),
320            #[cfg(unix)]
321            gid: Some(metadata.gid()),
322            #[cfg(windows)]
323            permissions: Some(if metadata.permissions().readonly() {
324                0o555
325            } else {
326                0o777
327            }),
328            #[cfg(unix)]
329            permissions: Some(metadata.mode()),
330            atime: Some(utils::unix(metadata.accessed().unwrap_or(UNIX_EPOCH))),
331            mtime: Some(utils::unix(metadata.modified().unwrap_or(UNIX_EPOCH))),
332            ..Default::default()
333        };
334
335        attrs.set_dir(metadata.is_dir());
336        attrs.set_regular(!metadata.is_dir());
337
338        attrs
339    }
340}
341
342impl Serialize for FileAttributes {
343    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
344    where
345        S: serde::Serializer,
346    {
347        let mut attrs = FileAttr::default();
348        let mut field_count = 1;
349
350        if self.size.is_some() {
351            attrs |= FileAttr::SIZE;
352            field_count += 1;
353        }
354
355        if self.uid.is_some() || self.gid.is_some() {
356            attrs |= FileAttr::UIDGID;
357            field_count += 2;
358        }
359
360        if self.permissions.is_some() {
361            attrs |= FileAttr::PERMISSIONS;
362            field_count += 1;
363        }
364
365        if self.atime.is_some() || self.mtime.is_some() {
366            attrs |= FileAttr::ACMODTIME;
367            field_count += 2;
368        }
369
370        let mut s = serializer.serialize_struct("FileAttributes", field_count)?;
371        s.serialize_field("attrs", &attrs)?;
372
373        if let Some(size) = self.size {
374            s.serialize_field("size", &size)?;
375        }
376
377        if self.uid.is_some() || self.gid.is_some() {
378            s.serialize_field("uid", &self.uid.unwrap_or(0))?;
379            s.serialize_field("gid", &self.gid.unwrap_or(0))?;
380        }
381
382        if let Some(permissions) = self.permissions {
383            s.serialize_field("permissions", &permissions)?;
384        }
385
386        if self.atime.is_some() || self.mtime.is_some() {
387            s.serialize_field("atime", &self.atime.unwrap_or(0))?;
388            s.serialize_field("mtime", &self.mtime.unwrap_or(0))?;
389        }
390
391        // todo: extended implementation
392
393        s.end()
394    }
395}
396
397impl<'de> Deserialize<'de> for FileAttributes {
398    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
399    where
400        D: Deserializer<'de>,
401    {
402        struct FileAttributesVisitor;
403
404        impl<'de> Visitor<'de> for FileAttributesVisitor {
405            type Value = FileAttributes;
406
407            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
408                formatter.write_str("file attributes")
409            }
410
411            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
412            where
413                A: serde::de::SeqAccess<'de>,
414            {
415                let attrs = FileAttr::from_bits_truncate(seq.next_element::<u32>()?.unwrap_or(0));
416
417                Ok(FileAttributes {
418                    size: if attrs.contains(FileAttr::SIZE) {
419                        seq.next_element::<u64>()?
420                    } else {
421                        None
422                    },
423                    uid: if attrs.contains(FileAttr::UIDGID) {
424                        seq.next_element::<u32>()?
425                    } else {
426                        None
427                    },
428                    user: None,
429                    gid: if attrs.contains(FileAttr::UIDGID) {
430                        seq.next_element::<u32>()?
431                    } else {
432                        None
433                    },
434                    group: None,
435                    permissions: if attrs.contains(FileAttr::PERMISSIONS) {
436                        seq.next_element::<u32>()?
437                    } else {
438                        None
439                    },
440                    atime: if attrs.contains(FileAttr::ACMODTIME) {
441                        seq.next_element::<u32>()?
442                    } else {
443                        None
444                    },
445                    mtime: if attrs.contains(FileAttr::ACMODTIME) {
446                        seq.next_element::<u32>()?
447                    } else {
448                        None
449                    },
450                })
451            }
452        }
453
454        deserializer.deserialize_any(FileAttributesVisitor)
455    }
456}