starnix_uapi/
file_mode.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::errors::{Errno, errno, error};
6use crate::open_flags::OpenFlags;
7use crate::uapi;
8use bstr::BStr;
9use std::ops;
10
11#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
12pub struct FileMode(u32);
13
14impl FileMode {
15    pub const IFLNK: FileMode = FileMode(uapi::S_IFLNK);
16    pub const IFREG: FileMode = FileMode(uapi::S_IFREG);
17    pub const IFDIR: FileMode = FileMode(uapi::S_IFDIR);
18    pub const IFCHR: FileMode = FileMode(uapi::S_IFCHR);
19    pub const IFBLK: FileMode = FileMode(uapi::S_IFBLK);
20    pub const IFIFO: FileMode = FileMode(uapi::S_IFIFO);
21    pub const IFSOCK: FileMode = FileMode(uapi::S_IFSOCK);
22
23    pub const ISUID: FileMode = FileMode(uapi::S_ISUID);
24    pub const ISGID: FileMode = FileMode(uapi::S_ISGID);
25    pub const ISVTX: FileMode = FileMode(uapi::S_ISVTX);
26    pub const IRWXU: FileMode = FileMode(uapi::S_IRWXU);
27    pub const IRUSR: FileMode = FileMode(uapi::S_IRUSR);
28    pub const IWUSR: FileMode = FileMode(uapi::S_IWUSR);
29    pub const IXUSR: FileMode = FileMode(uapi::S_IXUSR);
30    pub const IRWXG: FileMode = FileMode(uapi::S_IRWXG);
31    pub const IRGRP: FileMode = FileMode(uapi::S_IRGRP);
32    pub const IWGRP: FileMode = FileMode(uapi::S_IWGRP);
33    pub const IXGRP: FileMode = FileMode(uapi::S_IXGRP);
34    pub const IRWXO: FileMode = FileMode(uapi::S_IRWXO);
35    pub const IROTH: FileMode = FileMode(uapi::S_IROTH);
36    pub const IWOTH: FileMode = FileMode(uapi::S_IWOTH);
37    pub const IXOTH: FileMode = FileMode(uapi::S_IXOTH);
38
39    pub const IFMT: FileMode = FileMode(uapi::S_IFMT);
40
41    pub const DEFAULT_UMASK: FileMode = FileMode(0o022);
42    pub const ALLOW_ALL: FileMode = FileMode(0o777);
43    pub const PERMISSIONS: FileMode = FileMode(0o7777);
44    pub const EMPTY: FileMode = FileMode(0);
45
46    pub const fn from_bits(mask: u32) -> FileMode {
47        FileMode(mask)
48    }
49
50    pub fn from_string(mask: &BStr) -> Result<FileMode, Errno> {
51        if !mask.starts_with(b"0") {
52            return error!(EINVAL);
53        }
54        let mask = std::str::from_utf8(mask).map_err(|_| errno!(EINVAL))?;
55        let mask = u32::from_str_radix(mask, 8).map_err(|_| errno!(EINVAL))?;
56        Ok(Self::from_bits(mask))
57    }
58
59    pub const fn bits(&self) -> u32 {
60        self.0
61    }
62
63    pub const fn contains(&self, other: FileMode) -> bool {
64        self.0 & other.0 == other.bits()
65    }
66
67    pub const fn intersects(&self, other: FileMode) -> bool {
68        self.0 & other.0 != 0
69    }
70
71    pub fn fmt(&self) -> FileMode {
72        FileMode(self.bits() & uapi::S_IFMT)
73    }
74
75    pub const fn with_type(&self, file_type: FileMode) -> FileMode {
76        FileMode((self.bits() & Self::PERMISSIONS.bits()) | (file_type.bits() & uapi::S_IFMT))
77    }
78
79    pub const fn is_lnk(&self) -> bool {
80        (self.bits() & uapi::S_IFMT) == uapi::S_IFLNK
81    }
82
83    pub const fn is_reg(&self) -> bool {
84        (self.bits() & uapi::S_IFMT) == uapi::S_IFREG
85    }
86
87    pub const fn is_dir(&self) -> bool {
88        (self.bits() & uapi::S_IFMT) == uapi::S_IFDIR
89    }
90
91    pub const fn is_chr(&self) -> bool {
92        (self.bits() & uapi::S_IFMT) == uapi::S_IFCHR
93    }
94
95    pub const fn is_blk(&self) -> bool {
96        (self.bits() & uapi::S_IFMT) == uapi::S_IFBLK
97    }
98
99    pub const fn is_fifo(&self) -> bool {
100        (self.bits() & uapi::S_IFMT) == uapi::S_IFIFO
101    }
102
103    pub const fn is_sock(&self) -> bool {
104        (self.bits() & uapi::S_IFMT) == uapi::S_IFSOCK
105    }
106
107    pub fn user_access(&self) -> inner_access::Access {
108        inner_access::Access::try_from((self.bits() & 0o700) >> 6).unwrap()
109    }
110
111    pub fn group_access(&self) -> inner_access::Access {
112        inner_access::Access::try_from((self.bits() & 0o070) >> 3).unwrap()
113    }
114
115    pub fn other_access(&self) -> inner_access::Access {
116        inner_access::Access::try_from(self.bits() & 0o007).unwrap()
117    }
118}
119
120impl ops::BitOr for FileMode {
121    type Output = Self;
122
123    fn bitor(self, rhs: Self) -> Self::Output {
124        Self(self.0 | rhs.0)
125    }
126}
127
128impl ops::BitAnd for FileMode {
129    type Output = Self;
130
131    fn bitand(self, rhs: Self) -> Self::Output {
132        Self(self.0 & rhs.0)
133    }
134}
135
136impl ops::BitOrAssign for FileMode {
137    fn bitor_assign(&mut self, rhs: Self) {
138        self.0 |= rhs.0;
139    }
140}
141
142impl ops::BitAndAssign for FileMode {
143    fn bitand_assign(&mut self, rhs: Self) {
144        self.0 &= rhs.0;
145    }
146}
147
148impl ops::Not for FileMode {
149    type Output = Self;
150
151    fn not(self) -> Self::Output {
152        Self(!self.0)
153    }
154}
155
156#[macro_export]
157macro_rules! mode {
158    ($type:ident, $mode:expr) => {
159        $crate::file_mode::FileMode::from_bits($mode | $crate::file_mode::FileMode::$type.bits())
160    };
161}
162
163// The inner mod is required because bitflags cannot pass the attribute through to the single
164// variant, and attributes cannot be applied to macro invocations.
165mod inner_access {
166    // Part of the code for the NONE cases that are produced by the macro triggers the lint, but as
167    // a whole, the produced code is still correct.
168    #![allow(clippy::bad_bit_mask)] // TODO(b/303500202) Remove once addressed in bitflags.
169    use super::OpenFlags;
170    use crate::errors::{Errno, errno};
171
172    bitflags::bitflags! {
173        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
174        pub struct Access: u8 {
175            const EXIST = 0;
176            const EXEC = 1;
177            const WRITE = 2;
178            const READ = 4;
179
180            // Access mask is the part of access related to the file access mode. It is
181            // exec/write/read.
182            const ACCESS_MASK = 1 | 2 | 4;
183        }
184    }
185
186    impl Access {
187        pub fn from_open_flags(flags: OpenFlags) -> Self {
188            match flags & OpenFlags::ACCESS_MASK {
189                OpenFlags::RDONLY => Self::READ,
190                OpenFlags::WRONLY => Self::WRITE,
191                OpenFlags::RDWR => Self::READ | Self::WRITE,
192                _ => Self::EXIST, // Nonstandard access modes can be opened but will fail to read or write.
193            }
194        }
195
196        pub fn rwx() -> Self {
197            Access::EXEC | Access::WRITE | Access::READ
198        }
199
200        pub fn is_nontrivial(&self) -> bool {
201            *self != Self::EXIST
202        }
203
204        pub fn rwx_bits(&self) -> u8 {
205            self.bits() & Self::ACCESS_MASK.bits()
206        }
207    }
208
209    impl std::convert::TryFrom<u32> for Access {
210        type Error = Errno;
211
212        fn try_from(value: u32) -> Result<Self, Self::Error> {
213            Self::from_bits(value.try_into().map_err(|_| errno!(EINVAL))?)
214                .ok_or_else(|| errno!(EINVAL))
215        }
216    }
217}
218
219pub use inner_access::Access;
220
221pub struct AccessCheck(Option<Access>);
222
223impl Default for AccessCheck {
224    /// Perform the default access checks.
225    fn default() -> Self {
226        Self(None)
227    }
228}
229
230impl AccessCheck {
231    /// Skip access checks.
232    pub fn skip() -> Self {
233        Self(Some(Access::EXIST))
234    }
235
236    /// Check for the given access values.
237    pub fn check_for(access: Access) -> Self {
238        Self(Some(access))
239    }
240
241    /// The actual access bits to check for given the open flags.
242    ///
243    /// If the access check is the default, then this function will return
244    /// the access bits needed to open the file with the rights specified in
245    /// open flags.
246    pub fn resolve(&self, flags: OpenFlags) -> Access {
247        self.0.unwrap_or_else(|| Access::from_open_flags(flags))
248    }
249}
250
251// Public re-export of macros allows them to be used like regular rust items.
252pub use mode;
253
254#[cfg(test)]
255mod test {
256    use super::*;
257
258    #[::fuchsia::test]
259    fn test_file_mode_from_string() {
260        assert_eq!(FileMode::from_string(b"0123".into()), Ok(FileMode(0o123)));
261        assert!(FileMode::from_string(b"123".into()).is_err());
262        assert!(FileMode::from_string(b"\x80".into()).is_err());
263        assert!(FileMode::from_string(b"0999".into()).is_err());
264    }
265}