win_durable_ioring/windows_backend/file_rights.rs
1// Copyright (c) 2026 Mike Grier
2//! [`FileRights`]: the rights an open asks for beyond reading and writing the data, as a typed set
3//! (DI-13.3, DI-13.8, DI-D-64, DI-D-65).
4//!
5//! `CreateFileW` takes a `dwDesiredAccess` mask. The two rights that decide what this crate's own API
6//! may do with a handle, `FILE_READ_DATA` and `FILE_WRITE_DATA`, are the data access type
7//! ([`DataAccess`](super::DataAccess)), and this set holds the rest: the attribute, extended
8//! attribute, execute, delete and security rights, which gate only calls a consumer makes on the
9//! handle it is lent. Each member is a named constant, and a set is built by naming them and joining
10//! them. There is no constructor from a raw number, so the only masks that exist are ones this
11//! module's table lists, which is the whole of how an option that DI-D-64 excludes stays excluded:
12//! there is no member for `FILE_APPEND_DATA`, and no generic right, whose expansion the kernel makes,
13//! so a handle that appends and cannot write at an offset cannot be asked for.
14
15use std::fmt;
16use std::ops::{BitOr, BitOrAssign};
17
18use windows_sys::Win32::Storage::FileSystem::{
19 DELETE, FILE_EXECUTE, FILE_READ_ATTRIBUTES, FILE_READ_EA, FILE_WRITE_ATTRIBUTES, FILE_WRITE_EA,
20 READ_CONTROL, SYNCHRONIZE, WRITE_DAC, WRITE_OWNER,
21};
22use windows_sys::Win32::System::SystemServices::ACCESS_SYSTEM_SECURITY;
23
24#[cfg(test)]
25mod tests;
26
27/// A set of file access rights other than the data rights, which are the
28/// [`DataAccess`](super::DataAccess) of the handle.
29///
30/// Each member is one right the documentation lists for a file, named as `CreateFileW` names it
31/// without its prefix: [`WRITE_ATTRIBUTES`](Self::WRITE_ATTRIBUTES) is `FILE_WRITE_ATTRIBUTES`, and
32/// the standard rights keep their names. A set is made by joining them with `|`:
33///
34/// ```
35/// use win_durable_ioring::windows_backend::FileRights;
36///
37/// let rights = FileRights::READ_ATTRIBUTES | FileRights::WRITE_ATTRIBUTES;
38///
39/// assert!(rights.contains(FileRights::WRITE_ATTRIBUTES));
40/// assert!(!rights.contains(FileRights::DELETE));
41/// assert!(FileRights::NONE.is_empty());
42/// ```
43///
44/// **There is no generic right and no `FILE_ALL_ACCESS`.** A generic right mixes the data rights, which
45/// are a type, with others, and the kernel expands it when it opens, so a set could not say what it
46/// holds. Ask for the data access with [`FileOptions::read`](super::FileOptions::read) and
47/// [`write`](super::FileOptions::write), and for the rest with the members here.
48///
49/// **Append-only access is not offered**, by construction: there is no member for `FILE_APPEND_DATA`,
50/// and no data access asks for it. A write through a handle opened for appending only lands at the end
51/// of the file whatever offset it names, and the core addresses every write by offset.
52///
53/// The set has no constructor from a raw mask. A right Win32 defines and this table does not list
54/// cannot be expressed until the table gains it.
55#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
56pub struct FileRights(u32);
57
58/// Names the rights once, so the constants, the names `Debug` prints and the tests that walk every
59/// right are one list and not three that can disagree.
60macro_rules! rights {
61 ( $( $(#[$doc:meta])* $name:ident = $value:expr; )* ) => {
62 impl FileRights {
63 $( $(#[$doc])* pub const $name: Self = Self($value); )*
64 }
65
66 /// Every named right, in the order they are declared.
67 const NAMED: &[(&str, FileRights)] = &[ $( (stringify!($name), FileRights::$name), )* ];
68 };
69}
70
71rights! {
72 /// `FILE_READ_ATTRIBUTES`: read the file's attributes.
73 READ_ATTRIBUTES = FILE_READ_ATTRIBUTES;
74 /// `FILE_WRITE_ATTRIBUTES`: write the file's attributes, among them its times.
75 WRITE_ATTRIBUTES = FILE_WRITE_ATTRIBUTES;
76 /// `FILE_READ_EA`: read the file's extended attributes.
77 READ_EA = FILE_READ_EA;
78 /// `FILE_WRITE_EA`: write the file's extended attributes.
79 WRITE_EA = FILE_WRITE_EA;
80 /// `FILE_EXECUTE`: execute the file, for a native code file.
81 EXECUTE = FILE_EXECUTE;
82 /// `DELETE`: delete or rename the file.
83 DELETE = DELETE;
84 /// `READ_CONTROL`: read the file's security descriptor, without its system ACL.
85 READ_CONTROL = READ_CONTROL;
86 /// `WRITE_DAC`: change the file's discretionary ACL.
87 WRITE_DAC = WRITE_DAC;
88 /// `WRITE_OWNER`: change the file's owner.
89 WRITE_OWNER = WRITE_OWNER;
90 /// `SYNCHRONIZE`: wait on the handle.
91 SYNCHRONIZE = SYNCHRONIZE;
92 /// `ACCESS_SYSTEM_SECURITY`: read and change the file's system ACL. A caller the kernel does
93 /// not allow it gets the kernel's error from the open.
94 ACCESS_SYSTEM_SECURITY = ACCESS_SYSTEM_SECURITY;
95}
96
97impl FileRights {
98 /// No rights beyond the data access.
99 pub const NONE: Self = Self(0);
100
101 /// The union of two sets.
102 #[must_use]
103 pub const fn union(self, other: Self) -> Self {
104 Self(self.0 | other.0)
105 }
106
107 /// Whether every right of `other` is in this set.
108 #[must_use]
109 pub const fn contains(self, other: Self) -> bool {
110 self.0 & other.0 == other.0
111 }
112
113 /// Whether the set holds no right.
114 #[must_use]
115 pub const fn is_empty(self) -> bool {
116 self.0 == 0
117 }
118
119 /// The mask this set adds to the data access's, in `dwDesiredAccess`.
120 pub(super) const fn bits(self) -> u32 {
121 self.0
122 }
123}
124
125impl BitOr for FileRights {
126 type Output = Self;
127
128 fn bitor(self, other: Self) -> Self {
129 self.union(other)
130 }
131}
132
133impl BitOrAssign for FileRights {
134 fn bitor_assign(&mut self, other: Self) {
135 *self = self.union(other);
136 }
137}
138
139impl fmt::Debug for FileRights {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 f.write_str("FileRights(")?;
142 if self.is_empty() {
143 f.write_str("none")?;
144 } else {
145 let mut first = true;
146 for (name, _) in NAMED.iter().filter(|(_, right)| self.contains(*right)) {
147 if !first {
148 f.write_str(" | ")?;
149 }
150 first = false;
151 f.write_str(name)?;
152 }
153 }
154 f.write_str(")")
155 }
156}