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#[derive(Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15pub struct FileAttr(u32);
16
17#[derive(Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub struct FileMode(u32);
20
21#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum FileType {
61 Dir,
62 File,
63 Symlink,
64 Other,
65}
66
67impl FileType {
68 pub fn is_dir(&self) -> bool {
70 matches!(self, Self::Dir)
71 }
72
73 pub fn is_file(&self) -> bool {
75 matches!(self, Self::File)
76 }
77
78 pub fn is_symlink(&self) -> bool {
80 matches!(self, Self::Symlink)
81 }
82
83 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 pub fn is_readonly(&self) -> bool {
125 !self.other_write && !self.group_write && !self.owner_write
126 }
127
128 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#[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 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 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 pub fn file_type(&self) -> FileType {
248 FileMode::from_bits_truncate(self.permissions.unwrap_or_default()).into()
249 }
250
251 pub fn is_empty(&self) -> bool {
253 self.len() == 0
254 }
255
256 pub fn len(&self) -> u64 {
258 self.size.unwrap_or(0)
259 }
260
261 pub fn permissions(&self) -> FilePermissions {
263 FilePermissionFlags::from_bits_truncate(self.permissions.unwrap_or_default()).into()
264 }
265
266 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 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 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
297impl 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
313impl 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 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}