@@ -95,6 +95,41 @@ impl Type {
9595 }
9696}
9797
98+ /// Enum for specifying the context / "who" accesses in [is_allowed]
99+ #[ derive( Debug , PartialEq , Eq , Clone , Copy ) ]
100+ pub enum Accessor {
101+ Other ,
102+ Group ,
103+ User ,
104+ }
105+
106+ /// Enum for specifying the type of access in [is_allowed]
107+ #[ derive( Debug , PartialEq , Eq , Clone , Copy ) ]
108+ pub enum Access {
109+ /// (Beware: execute has various meanings depending on the type of file)
110+ Execute ,
111+ Write ,
112+ Read ,
113+ }
114+
115+ /// Check whether `mode` represents an allowed (`true`) or denied (`false`) access
116+ pub fn is_allowed ( by : Accessor , ty : Access , mode : u32 ) -> bool {
117+ use Access :: * ;
118+ use Accessor :: * ;
119+ let by = match by {
120+ User => 2 ,
121+ Group => 1 ,
122+ Other => 0 ,
123+ } ;
124+ let bits = ( mode >> ( 3 * by) ) & 0o7 ;
125+ let ty = match ty {
126+ Read => 2 ,
127+ Write => 1 ,
128+ Execute => 0 ,
129+ } ;
130+ bits & ( 1 << ty) != 0
131+ }
132+
98133/// Returns true if this mode represents a regular file.
99134///
100135/// ```
@@ -183,60 +218,31 @@ pub fn to_string(mode: u32) -> String {
183218 // This is decoded "by hand" here so that it'll work
184219 // on non-Unix platforms.
185220
186- fn bitset ( a : u32 , b : u32 ) -> bool {
187- a & b != 0
188- }
189-
190- fn permch ( mode : u32 , b : u32 , ch : char ) -> char {
191- if bitset ( mode, b) {
192- ch
193- } else {
194- '-'
195- }
196- }
197-
198- let mut s = String :: with_capacity ( 10 ) ;
199- s. push ( Type :: from ( mode) . short ( ) ) ;
200221 let setuid = is_setuid ( mode) ;
201222 let setgid = is_setgid ( mode) ;
202223 let sticky = is_sticky ( mode) ;
203- s. push ( permch ( mode, 0o400 , 'r' ) ) ;
204- s. push ( permch ( mode, 0o200 , 'w' ) ) ;
205- let usrx = bitset ( mode, 0o100 ) ;
206- if setuid && usrx {
207- s. push ( 's' )
208- } else if setuid && !usrx {
209- s. push ( 'S' )
210- } else if usrx {
211- s. push ( 'x' )
212- } else {
213- s. push ( '-' )
214- }
215- // group
216- s. push ( permch ( mode, 0o40 , 'r' ) ) ;
217- s. push ( permch ( mode, 0o20 , 'w' ) ) ;
218- let grpx = bitset ( mode, 0o10 ) ;
219- if setgid && grpx {
220- s. push ( 's' )
221- } else if setgid && !grpx {
222- s. push ( 'S' )
223- } else if grpx {
224- s. push ( 'x' )
225- } else {
226- s. push ( '-' )
227- }
228- // other
229- s. push ( permch ( mode, 0o4 , 'r' ) ) ;
230- s. push ( permch ( mode, 0o2 , 'w' ) ) ;
231- let otherx = bitset ( mode, 0o1 ) ;
232- if sticky && otherx {
233- s. push ( 't' )
234- } else if sticky && !otherx {
235- s. push ( 'T' )
236- } else if otherx {
237- s. push ( 'x' )
238- } else {
239- s. push ( '-' )
224+
225+ let mut s = String :: with_capacity ( 10 ) ;
226+ s. push ( Type :: from ( mode) . short ( ) ) ;
227+ use Access :: * ;
228+ use Accessor :: * ;
229+ for accessor in [ User , Group , Other ] {
230+ for access in [ Read , Write , Execute ] {
231+ s. push (
232+ match ( access, accessor, is_allowed ( accessor, access, mode) ) {
233+ ( Execute , User , true ) if setuid => 's' ,
234+ ( Execute , User , false ) if setuid => 'S' ,
235+ ( Execute , Group , true ) if setgid => 's' ,
236+ ( Execute , Group , false ) if setgid => 'S' ,
237+ ( Execute , Other , true ) if sticky => 't' ,
238+ ( Execute , Other , false ) if sticky => 'T' ,
239+ ( Execute , _, true ) => 'x' ,
240+ ( Write , _, true ) => 'w' ,
241+ ( Read , _, true ) => 'r' ,
242+ ( _, _, false ) => '-' ,
243+ } ,
244+ ) ;
245+ }
240246 }
241247 s
242248}
@@ -270,6 +276,19 @@ mod unix_tests {
270276 // I don't know how to reliably find a block device across OSes, and
271277 // we can't make one (without root.)
272278 }
279+ /// Test predicates against files likely to already exist on a Unix system.
280+ #[ test]
281+ fn existing_file_perms ( ) {
282+ use Access :: * ;
283+ use Accessor :: * ;
284+ for by in [ User , Group , Other ] {
285+ assert ! ( is_allowed( by, Read , file_mode( "/" ) ) ) ;
286+ assert ! ( is_allowed( by, Execute , file_mode( "/" ) ) ) ;
287+ assert ! ( is_allowed( by, Write , file_mode( "/dev/null" ) ) ) ;
288+ }
289+ assert ! ( !is_allowed( Other , Write , file_mode( "/dev/" ) ) ) ;
290+ assert ! ( !is_allowed( Other , Execute , file_mode( "/dev/null" ) ) ) ;
291+ }
273292
274293 #[ test]
275294 fn stat_created_symlink ( ) {
0 commit comments