yz_posix_mode/
lib.rs

1#![no_std]
2#![forbid(unsafe_code)]
3
4extern crate core;
5
6use bitflags::bitflags;
7use core::fmt::{self, Write};
8
9#[cfg(feature = "num")]
10use num_derive as nd;
11
12#[repr(u16)]
13#[rustfmt::skip]
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[cfg_attr(feature = "num", derive(nd::FromPrimitive, nd::ToPrimitive))]
16#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
17pub enum FileType {
18    /// directory
19    IFDIR  = 0o040000,
20    /// character device
21    IFCHR  = 0o020000,
22    /// block device
23    IFBLK  = 0o060000,
24    /// regular file
25    IFREG  = 0o100000,
26    // FIFO
27    IFIFO  = 0o010000,
28    // symbolic link
29    IFLNK  = 0o120000,
30    // socket
31    IFSOCK = 0o140000,
32}
33
34impl FileType {
35    /// file type bitmask
36    pub const IFMT: u16 = 0o170000;
37
38    /// Helper function for compatibility with bitflags structs
39    #[inline(always)]
40    #[cfg(feature = "num")]
41    pub fn from_bits(x: u16) -> Option<Self> {
42        <Self as num_traits::FromPrimitive>::from_u16(x)
43    }
44
45    /// Helper function for compatibility with bitflags structs
46    #[inline(always)]
47    pub fn bits(&self) -> u16 {
48        *self as _
49    }
50}
51
52impl fmt::Display for FileType {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        f.write_char(match self {
55            Self::IFSOCK => 's',
56            Self::IFLNK => 'l',
57            Self::IFREG => '-',
58            Self::IFBLK => 'b',
59            Self::IFDIR => 'd',
60            Self::IFCHR => 'c',
61            Self::IFIFO => 'p',
62        })
63    }
64}
65
66bitflags! {
67    #[derive(Default)]
68    #[repr(transparent)]
69    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
70    #[cfg_attr(feature = "serde", serde(transparent))]
71    pub struct Mode: u16 {
72        // POSIX protection bits
73        /// set user ID on exec
74        const ISUID  = 0o004000;
75        /// set group ID on exec
76        const ISGID  = 0o002000;
77        /// sticky
78        const ISVTX  = 0o001000;
79
80        // portable owner protection bits (both windows and unix)
81        /// owner: read
82        const IREAD  = 0o000400;
83        /// owner: write
84        const IWRITE = 0o000200;
85        /// owner: exec
86        const IEXEC  = 0o000100;
87
88        // POSIX owner protection bits
89        /// owner: read
90        const IRUSR  = Self::IREAD.bits;
91        /// owner: write
92        const IWUSR  = Self::IWRITE.bits;
93        /// owner: exec
94        const IXUSR  = Self::IEXEC.bits;
95        const IRWXU  = Self::IRUSR.bits | Self::IWUSR.bits | Self::IXUSR.bits;
96
97        // POSIX group protection bits
98        /// group: read
99        const IRGRP  = Self::IRUSR.bits >> 3;
100        /// group: write
101        const IWGRP  = Self::IWUSR.bits >> 3;
102        /// group: exec
103        const IXGRP  = Self::IXUSR.bits >> 3;
104        const IRWXG  = Self::IRGRP.bits | Self::IWGRP.bits | Self::IXGRP.bits;
105
106        // POSIX other protection bits
107        /// other: read
108        const IROTH  = Self::IRGRP.bits >> 3;
109        /// other: write
110        const IWOTH  = Self::IWGRP.bits >> 3;
111        /// other: exec
112        const IXOTH  = Self::IXGRP.bits >> 3;
113        const IRWXO  = Self::IROTH.bits | Self::IWOTH.bits | Self::IXOTH.bits;
114    }
115}
116
117impl Mode {
118    fn fmt_rwx_bits(&self, shift: u8, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        let sxbit = match shift {
120            2 /* owner */ => self.contains(Self::ISUID),
121            1 /* group */ => self.contains(Self::ISGID),
122            0 /* other */ => self.contains(Self::ISVTX),
123            _ => panic!("fmt_rwx_bits: illegal shift value"),
124        };
125        let protbits = (self.bits >> (3 * shift)) & 0o7;
126        f.write_char(if (protbits & 0o4) > 0 { 'r' } else { '-' })?;
127        f.write_char(if (protbits & 0o2) > 0 { 'w' } else { '-' })?;
128        f.write_char(match (shift, (protbits & 0o1) > 0, sxbit) {
129            (0, true, true) => 't',
130            (0, false, true) => 'T',
131            (_, true, true) => 's',
132            (_, false, true) => 'S',
133            (_, true, false) => 'x',
134            (_, false, false) => '-',
135        })?;
136        Ok(())
137    }
138}
139
140impl fmt::Display for Mode {
141    #[inline]
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        self.fmt_rwx_bits(2, f)?;
144        self.fmt_rwx_bits(1, f)?;
145        self.fmt_rwx_bits(0, f)?;
146        Ok(())
147    }
148}
149
150#[cfg(feature = "num-traits")]
151impl num_traits::FromPrimitive for Mode {
152    #[inline]
153    fn from_i64(n: i64) -> Option<Self> {
154        Self::from_u16(u16::from_i64(n)?)
155    }
156
157    #[inline]
158    fn from_u64(n: u64) -> Option<Self> {
159        Self::from_u16(u16::from_u64(n)?)
160    }
161
162    #[inline(always)]
163    fn from_u16(n: u16) -> Option<Self> {
164        Self::from_bits(n)
165    }
166}
167
168#[cfg(feature = "num-traits")]
169impl num_traits::ToPrimitive for Mode {
170    #[inline(always)]
171    fn to_u64(&self) -> Option<u64> {
172        Some(self.bits.into())
173    }
174    #[inline(always)]
175    fn to_u16(&self) -> Option<u16> {
176        Some(self.bits)
177    }
178    #[inline(always)]
179    fn to_i64(&self) -> Option<i64> {
180        Some(self.bits.into())
181    }
182}
183
184/// Split a file mode into file type and protection bits
185#[cfg(feature = "num")]
186pub fn split(fmode: u16) -> Option<(FileType, Mode)> {
187    let ft = FileType::from_bits(fmode & FileType::IFMT)?;
188    let md = Mode::from_bits(fmode & !FileType::IFMT)?;
189    Some((ft, md))
190}
191
192#[cfg(all(unix, any(test, feature = "nix")))]
193mod nix_;
194
195// we can't provide a TryFrom<umask::Mode> because the version of umask with
196// an Into<u32> impl is currently not published on crates.io
197
198/*
199pub struct InvalidBits<T>(pub T);
200
201#[cfg(feature = "umask")]
202impl core::convert::TryFrom<umask::Mode> for Mode {
203    type Error = InvalidBits<u32>;
204
205    #[inline]
206    fn try_from(x: umask::Mode) -> Result<Mode, InvalidBits<u32>> {
207        let x: u32 = x.into();
208        let db: u32 = Mode::all().bits.into();
209        let ib = x & !db;
210        if ib > 0 {
211            Err(InvalidBits(ib))
212        } else {
213            Ok(Mode::from_bits(x as u16).unwrap())
214        }
215    }
216}
217*/
218
219#[cfg(feature = "umask")]
220impl From<Mode> for umask::Mode {
221    #[inline]
222    fn from(x: Mode) -> umask::Mode {
223        umask::Mode::from(u32::from(x.bits))
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    #[test]
230    fn display() {
231        macro_rules! bytes_and_str {
232            ($($a:expr => $b:expr),+ $(,)?) => {
233                $( assert_eq!(std::format!("{}", crate::Mode::from_bits($a).expect("unknown bitmask")), $b); )+
234            }
235        };
236        extern crate std;
237        bytes_and_str! {
238            0o0200 => "-w-------",
239            0o0706 => "rwx---rw-",
240            0o0074 => "---rwxr--",
241            0o0777 => "rwxrwxrwx",
242            0o1777 => "rwxrwxrwt",
243            0o2777 => "rwxrwsrwx",
244            0o4777 => "rwsrwxrwx",
245            0o7777 => "rwsrwsrwt",
246            0o7747 => "rwsr-Srwt",
247            0o7000 => "--S--S--T",
248        }
249    }
250}