win_durable_ioring/windows_backend/file_attributes.rs
1// Copyright (c) 2026 Mike Grier
2//! [`FileAttributes`]: the attributes a file is created with, as a typed set (DI-14.3, DI-D-64).
3//!
4//! `CreateFileW` takes the attributes of a file it creates in `dwFlagsAndAttributes`. The set here is
5//! the six the table keeps: `ARCHIVE`, `HIDDEN`, `OFFLINE`, `READONLY`, `SYSTEM` and `TEMPORARY`.
6//! `FILE_ATTRIBUTE_NORMAL` is not a member, since the page says it is valid only alone and that any
7//! other attribute overrides it, so it is the absence of attributes and the empty set stands for it.
8//! `FILE_ATTRIBUTE_ENCRYPTED` is excluded (DI-D-64). There is no constructor from a raw number, so the
9//! only masks that exist are subsets of the six.
10
11use std::fmt;
12use std::ops::{BitOr, BitOrAssign};
13
14use windows_sys::Win32::Storage::FileSystem::{
15 FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_READONLY,
16 FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_TEMPORARY,
17};
18
19#[cfg(test)]
20mod tests;
21
22/// The attributes a file is created with: a set of `ARCHIVE`, `HIDDEN`, `OFFLINE`, `READONLY`, `SYSTEM`
23/// and `TEMPORARY`, named as `CreateFileW` names `FILE_ATTRIBUTE_*` without their prefix.
24///
25/// ```
26/// use win_durable_ioring::windows_backend::FileAttributes;
27///
28/// let attributes = FileAttributes::HIDDEN | FileAttributes::SYSTEM;
29///
30/// assert!(attributes.contains(FileAttributes::HIDDEN));
31/// assert!(!attributes.contains(FileAttributes::READONLY));
32/// assert!(FileAttributes::NONE.is_empty());
33/// ```
34///
35/// They apply to a file the open creates. The `CreateFileW` page says that for a file that exists the
36/// supplied attributes are generally combined with it or ignored, which is Win32's and is not
37/// refined here; [`FileOptions::attributes`](super::FileOptions::attributes) says what was measured.
38/// The page also says applications should not change `OFFLINE` arbitrarily.
39///
40/// The empty set asks for no attribute and is what an open passes when none is given; it is not
41/// `FILE_ATTRIBUTE_NORMAL`, which the page defines as valid only alone.
42#[derive(Clone, Copy, PartialEq, Eq, Hash)]
43pub struct FileAttributes(u32);
44
45/// Names the members once, so the constants, the names `Debug` prints and the tests that walk every
46/// subset are one list and not three that can disagree.
47macro_rules! attributes {
48 ( $( $(#[$doc:meta])* $name:ident = $value:expr; )* ) => {
49 impl FileAttributes {
50 $( $(#[$doc])* pub const $name: Self = Self($value); )*
51 }
52
53 /// Every named member, in the order they are declared.
54 const NAMED: &[(&str, FileAttributes)] = &[ $( (stringify!($name), FileAttributes::$name), )* ];
55 };
56}
57
58attributes! {
59 /// `FILE_ATTRIBUTE_ARCHIVE`: the file is marked for backup or removal.
60 ARCHIVE = FILE_ATTRIBUTE_ARCHIVE;
61 /// `FILE_ATTRIBUTE_HIDDEN`: the file is hidden, and not included in an ordinary directory listing.
62 HIDDEN = FILE_ATTRIBUTE_HIDDEN;
63 /// `FILE_ATTRIBUTE_OFFLINE`: the data is not immediately available, because it has been moved to
64 /// offline storage. The `CreateFileW` page says an application should not change it arbitrarily.
65 OFFLINE = FILE_ATTRIBUTE_OFFLINE;
66 /// `FILE_ATTRIBUTE_READONLY`: the file can be read and not written or deleted.
67 READONLY = FILE_ATTRIBUTE_READONLY;
68 /// `FILE_ATTRIBUTE_SYSTEM`: the file is part of, or used exclusively by, the operating system.
69 SYSTEM = FILE_ATTRIBUTE_SYSTEM;
70 /// `FILE_ATTRIBUTE_TEMPORARY`: the file is for temporary storage. The page says it lets a file
71 /// system keep the data in the cache and avoid writing it back while memory allows.
72 TEMPORARY = FILE_ATTRIBUTE_TEMPORARY;
73}
74
75impl FileAttributes {
76 /// No attribute: what an open asks for unless it is told otherwise.
77 pub const NONE: Self = Self(0);
78
79 /// The union of two sets.
80 #[must_use]
81 pub const fn union(self, other: Self) -> Self {
82 Self(self.0 | other.0)
83 }
84
85 /// Whether every member of `other` is in this set.
86 #[must_use]
87 pub const fn contains(self, other: Self) -> bool {
88 self.0 & other.0 == other.0
89 }
90
91 /// Whether the set has no member.
92 #[must_use]
93 pub const fn is_empty(self) -> bool {
94 self.0 == 0
95 }
96
97 /// The mask this set is in `dwFlagsAndAttributes`.
98 pub(super) const fn bits(self) -> u32 {
99 self.0
100 }
101}
102
103impl BitOr for FileAttributes {
104 type Output = Self;
105
106 fn bitor(self, other: Self) -> Self {
107 self.union(other)
108 }
109}
110
111impl BitOrAssign for FileAttributes {
112 fn bitor_assign(&mut self, other: Self) {
113 *self = self.union(other);
114 }
115}
116
117impl fmt::Debug for FileAttributes {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 f.write_str("FileAttributes(")?;
120 if self.is_empty() {
121 f.write_str("none")?;
122 } else {
123 let mut first = true;
124 for (name, _) in NAMED.iter().filter(|(_, member)| self.contains(*member)) {
125 if !first {
126 f.write_str(" | ")?;
127 }
128 first = false;
129 f.write_str(name)?;
130 }
131 }
132 f.write_str(")")
133 }
134}