Skip to main content

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}