1use dolang_winterop::security::Sid;
4use serde::{Deserialize, Serialize};
5
6use crate::security::{OwnershipIdentity, Permission};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum FileType {
11 File,
13 Dir,
15 Symlink,
17 Fifo,
19 CharacterDevice,
21 BlockDevice,
23 Socket,
25 Unknown,
27}
28
29bitflags::bitflags! {
30 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
38 pub struct Mode: u32 {
39 const SET_UID = 0o4000;
40 const SET_GID = 0o2000;
41 const STICKY = 0o1000;
42 const OWNER_READ = 0o400;
43 const OWNER_WRITE = 0o200;
44 const OWNER_EXECUTE = 0o100;
45 const GROUP_READ = 0o40;
46 const GROUP_WRITE = 0o20;
47 const GROUP_EXECUTE = 0o10;
48 const OTHER_READ = 0o4;
49 const OTHER_WRITE = 0o2;
50 const OTHER_EXECUTE = 0o1;
51 const IFIFO = 0o010000;
52 const IFCHR = 0o020000;
53 const IFDIR = 0o040000;
54 const IFBLK = 0o060000;
55 const IFREG = 0o100000;
56 const IFLNK = 0o120000;
57 const IFSOCK = 0o140000;
58 }
59}
60
61impl Mode {
62 pub fn owner(self) -> Permission {
64 Permission::from_bits_truncate((self.bits() >> 6) as u8 & 0o7)
65 }
66 pub fn group(self) -> Permission {
68 Permission::from_bits_truncate((self.bits() >> 3) as u8 & 0o7)
69 }
70 pub fn other(self) -> Permission {
72 Permission::from_bits_truncate(self.bits() as u8 & 0o7)
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct Metadata {
79 pub(crate) len: u64,
81 pub(crate) file_type: FileType,
83 pub(crate) atime: i64,
85 pub(crate) atime_nsec: i64,
87 pub(crate) mtime: i64,
89 pub(crate) mtime_nsec: i64,
91 pub(crate) ctime: i64,
93 pub(crate) ctime_nsec: i64,
95 pub(crate) family: MetadataFamily,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub(crate) enum MetadataFamily {
102 Unix(UnixMetadata),
104 Windows(WindowsMetadata),
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct UnixMetadata {
111 pub(crate) mode: Mode,
113 pub(crate) dev: u64,
115 pub(crate) ino: u64,
117 pub(crate) nlink: u64,
119 pub(crate) uid: u32,
121 pub(crate) gid: u32,
123 pub(crate) rdev: u64,
125 pub(crate) blksize: u64,
127 pub(crate) blocks: u64,
129 pub(crate) platform: UnixMetadataPlatform,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub(crate) enum UnixMetadataPlatform {
134 FreeBsd { attrs: u32 },
135 Linux { attrs: Option<u32> },
136 Macos { attrs: u32 },
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct WindowsMetadata {
142 pub(crate) attrs: u32,
143 pub(crate) user: Option<Sid>,
144 pub(crate) group: Option<Sid>,
145}
146
147impl Metadata {
148 pub const fn len(&self) -> u64 {
150 self.len
151 }
152 pub const fn is_empty(&self) -> bool {
154 self.len == 0
155 }
156 pub const fn file_type(&self) -> FileType {
158 self.file_type
159 }
160 pub const fn atime(&self) -> i64 {
162 self.atime
163 }
164 pub const fn atime_nsec(&self) -> i64 {
166 self.atime_nsec
167 }
168 pub const fn mtime(&self) -> i64 {
170 self.mtime
171 }
172 pub const fn mtime_nsec(&self) -> i64 {
174 self.mtime_nsec
175 }
176 pub const fn ctime(&self) -> i64 {
178 self.ctime
179 }
180 pub const fn ctime_nsec(&self) -> i64 {
182 self.ctime_nsec
183 }
184 pub fn unix(&self) -> Option<&UnixMetadata> {
186 if let MetadataFamily::Unix(metadata) = &self.family {
187 Some(metadata)
188 } else {
189 None
190 }
191 }
192 pub fn windows(&self) -> Option<&WindowsMetadata> {
194 if let MetadataFamily::Windows(metadata) = &self.family {
195 Some(metadata)
196 } else {
197 None
198 }
199 }
200 pub const fn linux_attrs(&self) -> Option<u32> {
202 match &self.family {
203 MetadataFamily::Unix(UnixMetadata {
204 platform: UnixMetadataPlatform::Linux { attrs },
205 ..
206 }) => *attrs,
207 _ => None,
208 }
209 }
210 pub const fn freebsd_attrs(&self) -> Option<u32> {
212 match &self.family {
213 MetadataFamily::Unix(UnixMetadata {
214 platform: UnixMetadataPlatform::FreeBsd { attrs },
215 ..
216 }) => Some(*attrs),
217 _ => None,
218 }
219 }
220 pub const fn macos_attrs(&self) -> Option<u32> {
222 match &self.family {
223 MetadataFamily::Unix(UnixMetadata {
224 platform: UnixMetadataPlatform::Macos { attrs },
225 ..
226 }) => Some(*attrs),
227 _ => None,
228 }
229 }
230 pub const fn win_attrs(&self) -> Option<u32> {
232 match &self.family {
233 MetadataFamily::Windows(metadata) => Some(metadata.attrs),
234 _ => None,
235 }
236 }
237}
238
239impl UnixMetadata {
240 pub const fn mode(&self) -> Mode {
242 self.mode
243 }
244 pub const fn dev(&self) -> u64 {
246 self.dev
247 }
248 pub const fn ino(&self) -> u64 {
250 self.ino
251 }
252 pub const fn nlink(&self) -> u64 {
254 self.nlink
255 }
256 pub const fn uid(&self) -> u32 {
258 self.uid
259 }
260 pub const fn gid(&self) -> u32 {
262 self.gid
263 }
264 pub const fn rdev(&self) -> u64 {
266 self.rdev
267 }
268 pub const fn block_size(&self) -> u64 {
270 self.blksize
271 }
272 pub const fn blocks(&self) -> u64 {
274 self.blocks
275 }
276 pub const fn linux_attrs(&self) -> Option<u32> {
278 match self.platform {
279 UnixMetadataPlatform::Linux { attrs } => attrs,
280 _ => None,
281 }
282 }
283 pub const fn freebsd_attrs(&self) -> Option<u32> {
285 match self.platform {
286 UnixMetadataPlatform::FreeBsd { attrs } => Some(attrs),
287 _ => None,
288 }
289 }
290 pub const fn macos_attrs(&self) -> Option<u32> {
292 match self.platform {
293 UnixMetadataPlatform::Macos { attrs } => Some(attrs),
294 _ => None,
295 }
296 }
297}
298
299impl WindowsMetadata {
300 pub const fn attrs(&self) -> u32 {
302 self.attrs
303 }
304 pub fn user(&self) -> Option<&Sid> {
306 self.user.as_ref()
307 }
308 pub fn group(&self) -> Option<&Sid> {
310 self.group.as_ref()
311 }
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct FsMetadata {
317 pub(crate) capacity: u64,
318 pub(crate) free: u64,
319 pub(crate) available: u64,
320 pub(crate) block_size: u32,
321 pub(crate) family: FsMetadataFamily,
322}
323#[derive(Debug, Clone, Serialize, Deserialize)]
324pub(crate) enum FsMetadataFamily {
325 Unix(UnixFsMetadata),
326 Windows(WindowsFsMetadata),
327}
328#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct UnixFsMetadata {
331 pub(crate) blocks: u64,
332 pub(crate) blocks_free: u64,
333 pub(crate) blocks_available: u64,
334 pub(crate) files: u64,
335 pub(crate) files_free: u64,
336 pub(crate) files_available: u64,
337 pub(crate) fragment_size: u32,
338 pub(crate) fsid: Option<u64>,
339 pub(crate) name_max: u32,
340 pub(crate) platform: UnixFsMetadataPlatform,
341}
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub(crate) enum UnixFsMetadataPlatform {
344 Linux { flags: u64 },
345 Macos { flags: u64 },
346 FreeBsd { flags: u64 },
347}
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct WindowsFsMetadata {
351 pub(crate) flags: u32,
352 pub(crate) volume_serial_number: u32,
353 pub(crate) component_length_max: u32,
354}
355
356impl FsMetadata {
357 pub const fn capacity(&self) -> u64 {
359 self.capacity
360 }
361 pub const fn free(&self) -> u64 {
363 self.free
364 }
365 pub const fn available(&self) -> u64 {
367 self.available
368 }
369 pub const fn block_size(&self) -> u32 {
371 self.block_size
372 }
373 pub fn unix(&self) -> Option<&UnixFsMetadata> {
375 if let FsMetadataFamily::Unix(metadata) = &self.family {
376 Some(metadata)
377 } else {
378 None
379 }
380 }
381 pub fn windows(&self) -> Option<&WindowsFsMetadata> {
383 if let FsMetadataFamily::Windows(metadata) = &self.family {
384 Some(metadata)
385 } else {
386 None
387 }
388 }
389 #[allow(clippy::unnecessary_cast)]
391 pub fn read_only(&self) -> bool {
392 match &self.family {
393 FsMetadataFamily::Unix(metadata) => metadata.platform.flags() & 1 != 0,
394 FsMetadataFamily::Windows(metadata) => metadata.flags & 0x0008_0000 != 0,
395 }
396 }
397 #[allow(clippy::unnecessary_cast)]
399 pub fn no_suid(&self) -> Option<bool> {
400 match &self.family {
401 FsMetadataFamily::Unix(metadata) => Some(metadata.platform.flags() & 2 != 0),
402 FsMetadataFamily::Windows(_) => None,
403 }
404 }
405 #[allow(clippy::unnecessary_cast)]
407 pub fn no_exec(&self) -> Option<bool> {
408 self.linux_flag(8)
409 }
410 #[allow(clippy::unnecessary_cast)]
412 pub fn synchronous(&self) -> Option<bool> {
413 self.linux_flag(16)
414 }
415 #[allow(clippy::unnecessary_cast)]
417 pub fn no_dev(&self) -> Option<bool> {
418 self.linux_flag(4)
419 }
420 #[allow(clippy::unnecessary_cast)]
422 pub fn no_atime(&self) -> Option<bool> {
423 self.linux_flag(1024)
424 }
425 #[allow(clippy::unnecessary_cast)]
427 pub fn no_dir_atime(&self) -> Option<bool> {
428 self.linux_flag(2048)
429 }
430 #[allow(clippy::unnecessary_cast)]
432 pub fn relatime(&self) -> Option<bool> {
433 self.linux_flag(1 << 21)
434 }
435 fn linux_flag(&self, flag: u64) -> Option<bool> {
436 match &self.family {
437 FsMetadataFamily::Unix(UnixFsMetadata {
438 platform: UnixFsMetadataPlatform::Linux { flags },
439 ..
440 }) => Some(flags & flag != 0),
441 _ => None,
442 }
443 }
444}
445impl UnixFsMetadata {
446 pub const fn blocks(&self) -> u64 {
448 self.blocks
449 }
450 pub const fn blocks_free(&self) -> u64 {
452 self.blocks_free
453 }
454 pub const fn blocks_available(&self) -> u64 {
456 self.blocks_available
457 }
458 pub const fn files(&self) -> u64 {
460 self.files
461 }
462 pub const fn files_free(&self) -> u64 {
464 self.files_free
465 }
466 pub const fn files_available(&self) -> u64 {
468 self.files_available
469 }
470 pub const fn fragment_size(&self) -> u32 {
472 self.fragment_size
473 }
474 pub const fn fsid(&self) -> Option<u64> {
476 self.fsid
477 }
478 pub const fn name_max(&self) -> u32 {
480 self.name_max
481 }
482 pub const fn linux_flags(&self) -> Option<u64> {
484 match self.platform {
485 UnixFsMetadataPlatform::Linux { flags } => Some(flags),
486 _ => None,
487 }
488 }
489 pub const fn freebsd_flags(&self) -> Option<u64> {
491 match self.platform {
492 UnixFsMetadataPlatform::FreeBsd { flags } => Some(flags),
493 _ => None,
494 }
495 }
496 pub const fn macos_flags(&self) -> Option<u64> {
498 match self.platform {
499 UnixFsMetadataPlatform::Macos { flags } => Some(flags),
500 _ => None,
501 }
502 }
503}
504impl WindowsFsMetadata {
505 pub const fn flags(&self) -> u32 {
507 self.flags
508 }
509 pub const fn volume_serial_number(&self) -> u32 {
511 self.volume_serial_number
512 }
513 pub const fn component_length_max(&self) -> u32 {
515 self.component_length_max
516 }
517}
518impl UnixFsMetadataPlatform {
519 pub fn flags(&self) -> u64 {
520 match self {
521 Self::FreeBsd { flags } | Self::Linux { flags } | Self::Macos { flags } => *flags,
522 }
523 }
524}
525
526#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
528#[serde(transparent)]
529pub struct AttrFlags(u64);
530impl AttrFlags {
531 pub const READONLY: Self = Self(1 << 0);
533 pub const HIDDEN: Self = Self(1 << 1);
535 pub const SYSTEM: Self = Self(1 << 2);
537 pub const ARCHIVE: Self = Self(1 << 3);
539 pub const COMPRESSED: Self = Self(1 << 4);
541 pub const TEMPORARY: Self = Self(1 << 5);
543 pub const OFFLINE: Self = Self(1 << 6);
545 pub const NOT_CONTENT_INDEXED: Self = Self(1 << 7);
547 pub const IMMUTABLE: Self = Self(1 << 8);
549 pub const APPEND_ONLY: Self = Self(1 << 9);
551 pub const NO_DUMP: Self = Self(1 << 10);
553 pub const NO_ATIME: Self = Self(1 << 11);
555 pub const NO_COPY_ON_WRITE: Self = Self(1 << 12);
557 pub const DIR_SYNC: Self = Self(1 << 13);
559 pub const CASEFOLD: Self = Self(1 << 14);
561 pub const DATA_JOURNALING: Self = Self(1 << 15);
563 pub const NO_COMPRESS: Self = Self(1 << 16);
565 pub const PROJECT_INHERIT: Self = Self(1 << 17);
567 pub const SECURE_DELETE: Self = Self(1 << 18);
569 pub const SYNC: Self = Self(1 << 19);
571 pub const NO_TAIL_MERGE: Self = Self(1 << 20);
573 pub const TOP_DIR: Self = Self(1 << 21);
575 pub const UNDELETE: Self = Self(1 << 22);
577 pub const DIRECT_ACCESS: Self = Self(1 << 23);
579 pub const EXTENT_FORMAT: Self = Self(1 << 24);
581 pub const OPAQUE: Self = Self(1 << 25);
583 pub const SPARSE: Self = Self(1 << 26);
585 pub const fn empty() -> Self {
587 Self(0)
588 }
589 pub const fn contains(self, flag: Self) -> bool {
591 self.0 & flag.0 != 0
592 }
593 pub const fn intersects(self, other: Self) -> bool {
595 self.0 & other.0 != 0
596 }
597 pub const fn union(self, other: Self) -> Self {
599 Self(self.0 | other.0)
600 }
601 pub const fn difference(self, other: Self) -> Self {
603 Self(self.0 & !other.0)
604 }
605 pub const fn is_empty(self) -> bool {
607 self.0 == 0
608 }
609}
610#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
611pub(crate) struct AttrsPatch {
612 pub(crate) set: AttrFlags,
613 pub(crate) clear: AttrFlags,
614}
615impl AttrsPatch {
616 pub fn update(&mut self, flag: AttrFlags, value: Option<bool>) {
617 match value {
618 Some(true) => {
619 self.set = self.set.union(flag);
620 self.clear = self.clear.difference(flag);
621 }
622 Some(false) => {
623 self.clear = self.clear.union(flag);
624 self.set = self.set.difference(flag);
625 }
626 None => {}
627 }
628 }
629 pub const fn requested(self) -> AttrFlags {
630 self.set.union(self.clear)
631 }
632 pub const fn is_empty(self) -> bool {
633 self.set.is_empty() && self.clear.is_empty()
634 }
635}
636
637#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
639pub struct MetadataPatch {
640 pub(crate) mode: Option<Mode>,
641 pub(crate) user: Option<OwnershipIdentity>,
642 pub(crate) group: Option<OwnershipIdentity>,
643 pub(crate) accessed: Option<i128>,
644 pub(crate) modified: Option<i128>,
645 pub(crate) created: Option<i128>,
646 pub(crate) attrs: AttrsPatch,
647 pub(crate) follow: bool,
648}
649impl Default for MetadataPatch {
650 fn default() -> Self {
651 Self {
652 mode: None,
653 user: None,
654 group: None,
655 accessed: None,
656 modified: None,
657 created: None,
658 attrs: AttrsPatch::default(),
659 follow: true,
660 }
661 }
662}
663impl MetadataPatch {
664 pub fn new() -> Self {
666 Self::default()
667 }
668 pub fn mode(&mut self, mode: Mode) -> &mut Self {
670 self.mode = Some(mode);
671 self
672 }
673 pub fn user(&mut self, user: OwnershipIdentity) -> &mut Self {
675 self.user = Some(user);
676 self
677 }
678 pub fn group(&mut self, group: OwnershipIdentity) -> &mut Self {
680 self.group = Some(group);
681 self
682 }
683 pub fn accessed(&mut self, accessed: i128) -> &mut Self {
685 self.accessed = Some(accessed);
686 self
687 }
688 pub fn modified(&mut self, modified: i128) -> &mut Self {
690 self.modified = Some(modified);
691 self
692 }
693 pub fn created(&mut self, created: i128) -> &mut Self {
695 self.created = Some(created);
696 self
697 }
698 pub fn attribute(&mut self, flag: AttrFlags, value: Option<bool>) -> &mut Self {
700 self.attrs.update(flag, value);
701 self
702 }
703 pub fn follow_links(&mut self, follow: bool) -> &mut Self {
705 self.follow = follow;
706 self
707 }
708 pub fn with_mode(mut self, mode: Mode) -> Self {
710 self.mode(mode);
711 self
712 }
713 pub fn with_user(mut self, user: OwnershipIdentity) -> Self {
715 self.user(user);
716 self
717 }
718 pub fn with_group(mut self, group: OwnershipIdentity) -> Self {
720 self.group(group);
721 self
722 }
723 pub fn with_accessed(mut self, accessed: i128) -> Self {
725 self.accessed(accessed);
726 self
727 }
728 pub fn with_modified(mut self, modified: i128) -> Self {
730 self.modified(modified);
731 self
732 }
733 pub fn with_created(mut self, created: i128) -> Self {
735 self.created(created);
736 self
737 }
738 pub fn with_attribute(mut self, flag: AttrFlags, value: Option<bool>) -> Self {
740 self.attribute(flag, value);
741 self
742 }
743 pub fn with_follow_links(mut self, follow: bool) -> Self {
745 self.follow_links(follow);
746 self
747 }
748 pub fn is_empty(&self) -> bool {
750 self.mode.is_none()
751 && self.user.is_none()
752 && self.group.is_none()
753 && self.accessed.is_none()
754 && self.modified.is_none()
755 && self.created.is_none()
756 && self.attrs.is_empty()
757 }
758}
759
760pub(crate) fn metadata_from_std(metadata: std::fs::Metadata) -> Metadata {
761 #[cfg(unix)]
762 {
763 use nix::sys::stat::{SFlag, mode_t};
764 #[cfg(target_os = "macos")]
765 use std::os::darwin::fs::MetadataExt as DarwinMetadataExt;
766 #[cfg(target_os = "freebsd")]
767 use std::os::freebsd::fs::MetadataExt as FreeBsdMetadataExt;
768 use std::os::unix::fs::MetadataExt;
769 let mode = metadata.mode();
770 let file_type = match SFlag::from_bits_truncate(mode as mode_t) & SFlag::S_IFMT {
771 SFlag::S_IFREG => FileType::File,
772 SFlag::S_IFDIR => FileType::Dir,
773 SFlag::S_IFLNK => FileType::Symlink,
774 SFlag::S_IFIFO => FileType::Fifo,
775 SFlag::S_IFCHR => FileType::CharacterDevice,
776 SFlag::S_IFBLK => FileType::BlockDevice,
777 SFlag::S_IFSOCK => FileType::Socket,
778 _ => FileType::Unknown,
779 };
780 Metadata {
781 len: metadata.len(),
782 file_type,
783 atime: metadata.atime(),
784 atime_nsec: metadata.atime_nsec(),
785 mtime: metadata.mtime(),
786 mtime_nsec: metadata.mtime_nsec(),
787 ctime: metadata.ctime(),
788 ctime_nsec: metadata.ctime_nsec(),
789 family: MetadataFamily::Unix(UnixMetadata {
790 mode: Mode::from_bits_retain(mode),
791 dev: metadata.dev(),
792 ino: metadata.ino(),
793 nlink: metadata.nlink(),
794 uid: metadata.uid(),
795 gid: metadata.gid(),
796 rdev: metadata.rdev(),
797 blksize: metadata.blksize(),
798 blocks: metadata.blocks(),
799 #[cfg(target_os = "linux")]
800 platform: UnixMetadataPlatform::Linux { attrs: None },
801 #[cfg(target_os = "freebsd")]
802 platform: UnixMetadataPlatform::FreeBsd {
803 attrs: metadata.st_flags(),
804 },
805 #[cfg(target_os = "macos")]
806 platform: UnixMetadataPlatform::Macos {
807 attrs: metadata.st_flags(),
808 },
809 }),
810 }
811 }
812 #[cfg(windows)]
813 {
814 use std::os::windows::fs::MetadataExt;
815 let file_type = if metadata.is_file() {
816 FileType::File
817 } else if metadata.is_dir() {
818 FileType::Dir
819 } else if metadata.file_type().is_symlink() {
820 FileType::Symlink
821 } else {
822 FileType::Unknown
823 };
824 Metadata {
825 len: metadata.len(),
826 file_type,
827 atime: system_time_to_parts(metadata.accessed().ok()).0,
828 atime_nsec: i64::from(system_time_to_parts(metadata.accessed().ok()).1),
829 mtime: system_time_to_parts(metadata.modified().ok()).0,
830 mtime_nsec: i64::from(system_time_to_parts(metadata.modified().ok()).1),
831 ctime: system_time_to_parts(metadata.created().ok()).0,
832 ctime_nsec: i64::from(system_time_to_parts(metadata.created().ok()).1),
833 family: MetadataFamily::Windows(WindowsMetadata {
834 attrs: metadata.file_attributes(),
835 user: None,
836 group: None,
837 }),
838 }
839 }
840}
841#[cfg(windows)]
842pub(crate) fn metadata_with_sids(
843 mut metadata: Metadata,
844 user: Option<Sid>,
845 group: Option<Sid>,
846) -> Metadata {
847 let MetadataFamily::Windows(windows) = &mut metadata.family else {
848 unreachable!()
849 };
850 windows.user = user;
851 windows.group = group;
852 metadata
853}
854#[cfg(windows)]
855fn system_time_to_parts(time: Option<std::time::SystemTime>) -> (i64, u32) {
856 use std::time::UNIX_EPOCH;
857 let Some(time) = time else { return (0, 0) };
858 match time.duration_since(UNIX_EPOCH) {
859 Ok(duration) => (
860 i64::try_from(duration.as_secs()).unwrap_or(i64::MAX),
861 duration.subsec_nanos(),
862 ),
863 Err(err) => {
864 let duration = err.duration();
865 let secs = i64::try_from(duration.as_secs()).unwrap_or(i64::MAX);
866 if duration.subsec_nanos() == 0 {
867 (-secs, 0)
868 } else {
869 (-secs - 1, 1_000_000_000 - duration.subsec_nanos())
870 }
871 }
872 }
873}
874
875#[cfg(test)]
876mod tests {
877 use super::{AttrFlags, MetadataPatch, Mode};
878 use crate::security::OwnershipIdentity;
879
880 #[test]
881 fn metadata_patch_builder_tracks_requested_changes() {
882 let patch = MetadataPatch::new()
883 .with_mode(Mode::OWNER_READ)
884 .with_user(OwnershipIdentity::Id(1))
885 .with_group(OwnershipIdentity::Name("staff".to_owned()))
886 .with_accessed(10)
887 .with_modified(20)
888 .with_created(30)
889 .with_attribute(AttrFlags::HIDDEN, Some(true))
890 .with_follow_links(false);
891
892 assert!(!patch.is_empty());
893 assert_eq!(patch.mode, Some(Mode::OWNER_READ));
894 assert!(patch.attrs.set.contains(AttrFlags::HIDDEN));
895 assert!(!patch.follow);
896 assert_eq!(
897 postcard::from_bytes::<MetadataPatch>(&postcard::to_stdvec(&patch).unwrap()).unwrap(),
898 patch
899 );
900 }
901
902 #[test]
903 fn metadata_patch_attribute_updates_are_disjoint() {
904 let mut patch = MetadataPatch::new();
905 patch.attribute(AttrFlags::READONLY, Some(true));
906 patch.attribute(AttrFlags::READONLY, Some(false));
907 assert!(!patch.attrs.set.contains(AttrFlags::READONLY));
908 assert!(patch.attrs.clear.contains(AttrFlags::READONLY));
909 }
910}