chj_rustbin/io/
unix_fs.rs

1//! Unix FS easy functions.
2
3//! Why not use std ones? Because those expect Path, and CString is not representable as Path.
4
5use enumn::N;
6use nix::sys::stat::FileStat;
7use std::ffi::CStr;
8
9#[derive(N, Eq, PartialEq, Debug)]
10#[repr(u8)]
11pub enum FileType {
12    // XX are these Linux-specific, use C constants?
13    Pipe = 1,
14    CharDevice = 2,
15    Dir = 4,
16    BlockDevice = 6,
17    File = 8,
18    Link = 10,
19    Socket = 12,
20}
21
22pub trait EasyFileStat {
23    fn filetype(&self) -> FileType;
24}
25
26impl EasyFileStat for FileStat {
27    fn filetype(&self) -> FileType {
28        FileType::n(stat_filetype(self))
29            .expect("OS using one of the known constants")
30    }
31}
32
33fn stat_filetype(st: &FileStat) -> u8 {
34    ((st.st_mode & 0o0170000) >> 12) as u8
35}
36
37/// Test whether stat on `path` succeeds and yields the given file
38/// type. If permissions deny access or there are disk errors, the
39/// result is simply `false`.
40pub fn path_is_type(
41    path: &CStr,
42    ftypes: &[FileType],
43    follow_links: bool,
44) -> bool {
45    let statfunction: fn(&CStr) -> _ = if follow_links {
46        nix::sys::stat::stat
47    } else {
48        nix::sys::stat::lstat
49    };
50    match statfunction(path) {
51        Ok(m) => ftypes.iter().any(|ftype| m.filetype() == *ftype),
52        Err(_) => false,
53    }
54}
55
56pub fn path_is_file(path: &CStr) -> bool {
57    path_is_type(path, &[FileType::File], false)
58}
59pub fn path_is_dir(path: &CStr) -> bool {
60    path_is_type(path, &[FileType::Dir], false)
61}
62pub fn path_is_link(path: &CStr) -> bool {
63    path_is_type(path, &[FileType::Link], false)
64}
65pub fn path_is_blockdevice(path: &CStr) -> bool {
66    path_is_type(path, &[FileType::BlockDevice], false)
67}
68pub fn path_is_pipe(path: &CStr) -> bool {
69    path_is_type(path, &[FileType::Pipe], false)
70}
71pub fn path_is_socket(path: &CStr) -> bool {
72    path_is_type(path, &[FileType::Socket], false)
73}
74pub fn path_is_chardevice(path: &CStr) -> bool {
75    path_is_type(path, &[FileType::CharDevice], false)
76}
77
78pub fn path_is_normal_file(path: &CStr) -> bool {
79    path_is_type(path, &[FileType::File, FileType::Dir], true)
80}
81
82#[cfg(test)]
83mod tests {
84    use std::ffi::CString;
85
86    use super::*;
87
88    #[test]
89    fn t_filetype() {
90        fn t(f: fn(&CStr) -> bool, s: &str, expected: bool) {
91            assert_eq!(f(&CString::new(s).unwrap()), expected);
92        }
93        t(path_is_dir, ".", true);
94        t(path_is_file, ".", false);
95        // Non-existing:
96        t(path_is_dir, "8hbrr2kz8kmztb4dqh4", false);
97        t(path_is_file, "8hbrr2kz8kmztb4dqh4", false);
98        // Looking at hopefully existing paths (XX evil):
99        t(path_is_file, "/etc/fstab", true);
100        t(path_is_chardevice, "/dev/null", true);
101        t(path_is_chardevice, "/dev/loop0", false);
102        t(path_is_blockdevice, "/dev/sda", true);
103        t(path_is_normal_file, "/dev/sda", false);
104        t(path_is_normal_file, "8hbrr2kz8kmztb4dqh4", false);
105        t(path_is_normal_file, "/etc/fstab", true);
106        t(path_is_link, "/etc/localtime", true);
107        t(path_is_normal_file, "/etc/localtime", true);
108    }
109}