@@ -43,14 +43,92 @@ fn type_bits(mode: u32) -> u32 {
4343 ( mode >> 12 ) & 0o17
4444}
4545
46+ /// The different types of files known to this library
47+ ///
48+ /// Can be constructed `From<u32>`.
49+ /// ```
50+ /// assert_eq!(unix_mode::Type::from(0o0100640), unix_mode::Type::File);
51+ /// ```
52+ #[ derive( Debug , PartialEq , Eq , Clone , Copy ) ]
53+ #[ non_exhaustive]
54+ pub enum Type {
55+ File ,
56+ Dir ,
57+ Symlink ,
58+ Socket ,
59+ Fifo ,
60+ BlockDevice ,
61+ CharDevice ,
62+ /// Removed file in union filesystems
63+ Whiteout ,
64+ /// File type not recognized by this version of this library
65+ ///
66+ /// More types might be added in the future, so the semantics of this variant may change.
67+ Unknown ,
68+ }
69+
70+ impl From < u32 > for Type {
71+ /// Parse type from mode
72+ ///
73+ fn from ( mode : u32 ) -> Type {
74+ use Type :: * ;
75+ match type_bits ( mode) {
76+ 0o001 => Fifo ,
77+ 0o002 => CharDevice ,
78+ 0o004 => Dir ,
79+ 0o006 => BlockDevice ,
80+ 0o010 => File ,
81+ 0o012 => Symlink ,
82+ 0o014 => Socket ,
83+ 0o016 => Whiteout ,
84+ _ => Unknown ,
85+ }
86+ }
87+ }
88+
89+ /// Enum for specifying the context / "who" accesses in [is_allowed]
90+ #[ derive( Debug , PartialEq , Eq , Clone , Copy ) ]
91+ pub enum Accessor {
92+ Other ,
93+ Group ,
94+ User ,
95+ }
96+
97+ /// Enum for specifying the type of access in [is_allowed]
98+ #[ derive( Debug , PartialEq , Eq , Clone , Copy ) ]
99+ pub enum Access {
100+ /// (Beware: execute has various meanings depending on the type of file)
101+ Execute ,
102+ Write ,
103+ Read ,
104+ }
105+
106+ /// Check whether `mode` represents an allowed (`true`) or denied (`false`) access
107+ pub fn is_allowed ( by : Accessor , ty : Access , mode : u32 ) -> bool {
108+ use Access :: * ;
109+ use Accessor :: * ;
110+ let by = match by {
111+ User => 2 ,
112+ Group => 1 ,
113+ Other => 0 ,
114+ } ;
115+ let bits = ( mode >> ( 3 * by) ) & 0o7 ;
116+ let ty = match ty {
117+ Read => 2 ,
118+ Write => 1 ,
119+ Execute => 0 ,
120+ } ;
121+ bits & ( 1 << ty) != 0
122+ }
123+
46124/// Returns true if this mode represents a regular file.
47125///
48126/// ```
49127/// assert_eq!(unix_mode::is_file(0o0041777), false);
50128/// assert_eq!(unix_mode::is_file(0o0100640), true);
51129/// ```
52130pub fn is_file ( mode : u32 ) -> bool {
53- type_bits ( mode) == 0o010
131+ Type :: from ( mode) == Type :: File
54132}
55133
56134/// Returns true if this mode represents a directory.
@@ -60,7 +138,7 @@ pub fn is_file(mode: u32) -> bool {
60138/// assert_eq!(unix_mode::is_dir(0o0100640), false);
61139/// ```
62140pub fn is_dir ( mode : u32 ) -> bool {
63- type_bits ( mode) == 0o004
141+ Type :: from ( mode) == Type :: Dir
64142}
65143
66144/// Returns true if this mode represents a symlink.
@@ -70,27 +148,27 @@ pub fn is_dir(mode: u32) -> bool {
70148/// assert_eq!(unix_mode::is_symlink(0o0120755), true);
71149/// ```
72150pub fn is_symlink ( mode : u32 ) -> bool {
73- type_bits ( mode) == 0o012
151+ Type :: from ( mode) == Type :: Symlink
74152}
75153
76154/// Returns true if this mode represents a fifo, also known as a named pipe.
77155pub fn is_fifo ( mode : u32 ) -> bool {
78- type_bits ( mode) == 0o001
156+ Type :: from ( mode) == Type :: Fifo
79157}
80158
81159/// Returns true if this mode represents a character device.
82160pub fn is_char_device ( mode : u32 ) -> bool {
83- type_bits ( mode) == 0o002
161+ Type :: from ( mode) == Type :: CharDevice
84162}
85163
86164/// Returns true if this mode represents a block device.
87165pub fn is_block_device ( mode : u32 ) -> bool {
88- type_bits ( mode) == 0o006
166+ Type :: from ( mode) == Type :: BlockDevice
89167}
90168
91169/// Returns true if this mode represents a Unix-domain socket.
92170pub fn is_socket ( mode : u32 ) -> bool {
93- type_bits ( mode) == 0o014
171+ Type :: from ( mode) == Type :: Socket
94172}
95173
96174/// Returns true if the set-user-ID bit is set
@@ -130,71 +208,43 @@ pub fn is_sticky(mode: u32) -> bool {
130208pub fn to_string ( mode : u32 ) -> String {
131209 // This is decoded "by hand" here so that it'll work
132210 // on non-Unix platforms.
211+ use Access :: * ;
212+ use Accessor :: * ;
213+ use Type :: * ;
133214
134- fn bitset ( a : u32 , b : u32 ) -> bool {
135- a & b != 0
136- }
137-
138- fn permch ( mode : u32 , b : u32 , ch : char ) -> char {
139- if bitset ( mode, b) {
140- ch
141- } else {
142- '-'
143- }
144- }
145-
146- let mut s = String :: with_capacity ( 10 ) ;
147- s. push ( match ( mode >> 12 ) & 0o17 {
148- 0o001 => 'p' , // pipe/fifo
149- 0o002 => 'c' , // character dev
150- 0o004 => 'd' , // directory
151- 0o006 => 'b' , // block dev
152- 0o010 => '-' , // regular file
153- 0o012 => 'l' , // link
154- 0o014 => 's' , // socket
155- 0o016 => 'w' , // whiteout
156- _ => '?' , // unknown
157- } ) ;
158215 let setuid = is_setuid ( mode) ;
159216 let setgid = is_setgid ( mode) ;
160217 let sticky = is_sticky ( mode) ;
161- s. push ( permch ( mode, 0o400 , 'r' ) ) ;
162- s. push ( permch ( mode, 0o200 , 'w' ) ) ;
163- let usrx = bitset ( mode, 0o100 ) ;
164- if setuid && usrx {
165- s. push ( 's' )
166- } else if setuid && !usrx {
167- s. push ( 'S' )
168- } else if usrx {
169- s. push ( 'x' )
170- } else {
171- s. push ( '-' )
172- }
173- // group
174- s. push ( permch ( mode, 0o40 , 'r' ) ) ;
175- s. push ( permch ( mode, 0o20 , 'w' ) ) ;
176- let grpx = bitset ( mode, 0o10 ) ;
177- if setgid && grpx {
178- s. push ( 's' )
179- } else if setgid && !grpx {
180- s. push ( 'S' )
181- } else if grpx {
182- s. push ( 'x' )
183- } else {
184- s. push ( '-' )
185- }
186- // other
187- s. push ( permch ( mode, 0o4 , 'r' ) ) ;
188- s. push ( permch ( mode, 0o2 , 'w' ) ) ;
189- let otherx = bitset ( mode, 0o1 ) ;
190- if sticky && otherx {
191- s. push ( 't' )
192- } else if sticky && !otherx {
193- s. push ( 'T' )
194- } else if otherx {
195- s. push ( 'x' )
196- } else {
197- s. push ( '-' )
218+
219+ let mut s = String :: with_capacity ( 10 ) ;
220+ s. push ( match Type :: from ( mode) {
221+ Fifo => 'p' ,
222+ CharDevice => 'c' ,
223+ Dir => 'd' ,
224+ BlockDevice => 'b' ,
225+ File => '-' ,
226+ Symlink => 'l' ,
227+ Socket => 's' ,
228+ Whiteout => 'w' ,
229+ Unknown => '?' ,
230+ } ) ;
231+ for accessor in [ User , Group , Other ] {
232+ for access in [ Read , Write , Execute ] {
233+ s. push (
234+ match ( access, accessor, is_allowed ( accessor, access, mode) ) {
235+ ( Execute , User , true ) if setuid => 's' ,
236+ ( Execute , User , false ) if setuid => 'S' ,
237+ ( Execute , Group , true ) if setgid => 's' ,
238+ ( Execute , Group , false ) if setgid => 'S' ,
239+ ( Execute , Other , true ) if sticky => 't' ,
240+ ( Execute , Other , false ) if sticky => 'T' ,
241+ ( Execute , _, true ) => 'x' ,
242+ ( Write , _, true ) => 'w' ,
243+ ( Read , _, true ) => 'r' ,
244+ ( _, _, false ) => '-' ,
245+ } ,
246+ ) ;
247+ }
198248 }
199249 s
200250}
@@ -229,6 +279,20 @@ mod unix_tests {
229279 // we can't make one (without root.)
230280 }
231281
282+ /// Test [is_allowed] against files likely to already exist on a Unix system.
283+ #[ test]
284+ fn existing_file_perms ( ) {
285+ use Access :: * ;
286+ use Accessor :: * ;
287+ for by in [ User , Group , Other ] {
288+ assert ! ( is_allowed( by, Read , file_mode( "/" ) ) ) ;
289+ assert ! ( is_allowed( by, Execute , file_mode( "/" ) ) ) ;
290+ assert ! ( is_allowed( by, Write , file_mode( "/dev/null" ) ) ) ;
291+ }
292+ assert ! ( !is_allowed( Other , Write , file_mode( "/dev/" ) ) ) ;
293+ assert ! ( !is_allowed( Other , Execute , file_mode( "/dev/null" ) ) ) ;
294+ }
295+
232296 #[ test]
233297 fn stat_created_symlink ( ) {
234298 let tmp_dir = tempdir ( ) . unwrap ( ) ;
0 commit comments