Skip to content

Commit e53d863

Browse files
committed
Add is_allowed for testing access
1 parent bd3baa1 commit e53d863

File tree

1 file changed

+70
-51
lines changed

1 file changed

+70
-51
lines changed

src/lib.rs

Lines changed: 70 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)