Skip to content

Commit 0576707

Browse files
authored
Merge pull request #7 from jcaesar/enh
Enums for testing permissions and matching file type
2 parents 8dec8f4 + 3c9f4b8 commit 0576707

File tree

1 file changed

+132
-68
lines changed

1 file changed

+132
-68
lines changed

src/lib.rs

Lines changed: 132 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -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
/// ```
52130
pub 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
/// ```
62140
pub 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
/// ```
72150
pub 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.
77155
pub 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.
82160
pub 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.
87165
pub 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.
92170
pub 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 {
130208
pub 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

Comments
 (0)