tokinotify/
lib.rs

1//!
2//! Using the inotify syscalls with tokio support
3//!
4
5#![warn(missing_docs)]
6
7use std::{
8    ffi::{c_int, OsStr},
9    io,
10    mem::size_of,
11    os::{fd::FromRawFd, unix::ffi::OsStrExt},
12    path::{Path, PathBuf},
13};
14
15use tokio::{fs::File, io::AsyncReadExt};
16
17mod mask;
18
19pub use mask::Mask;
20
21extern "C" {
22    fn inotify_init1(flag: c_int) -> c_int;
23    fn inotify_add_watch(fd: c_int, buf: *const u8, mask: u32) -> c_int;
24    fn inotify_rm_watch(fd: c_int, wd: c_int) -> c_int;
25    fn close(fd: c_int) -> c_int;
26}
27
28/// Watch filesytem changes on linux
29pub struct INotify {
30    fd: c_int,
31    file: File,
32}
33
34/// A WatchDescriptor
35#[derive(Clone, Copy)]
36pub struct Watch {
37    wd: c_int,
38}
39
40/// An event returned by the kernel
41#[derive(Debug)]
42pub struct Event {
43    /// The Watch associated with this event
44    pub watch: Watch,
45
46    /// The mask associated with this event
47    pub mask: Mask,
48
49    /// A cookie associated with the event
50    pub cookie: u32,
51
52    /// A path associated with this event (empty unless disambigous to the kernel)
53    pub path: PathBuf,
54}
55
56#[repr(C)]
57struct EventHeader {
58    wd: c_int,
59    mask: u32,
60    cookie: u32,
61    len: u32,
62}
63
64impl INotify {
65    /// Build a new INotify
66    pub fn new() -> io::Result<Self> {
67        let fd = unsafe { inotify_init1(0) };
68
69        if fd == -1 {
70            return Err(io::Error::from_raw_os_error(fd));
71        }
72
73        let file = unsafe { File::from_raw_fd(fd) };
74
75        Ok(Self { fd, file })
76    }
77
78    /// Add a file (, or directory) to be watched
79    pub fn add(&mut self, path: &Path, mask: Mask) -> io::Result<Watch> {
80        let path: &OsStr = path.as_ref();
81        let res = unsafe { inotify_add_watch(self.fd, path.as_bytes().as_ptr(), mask.0) };
82        if res == -1 {
83            return Err(io::Error::from_raw_os_error(res));
84        }
85
86        Ok(Watch { wd: res })
87    }
88
89    /// remove a watch from this INotify
90    pub fn rm(&mut self, watch: Watch) -> io::Result<()> {
91        let res = unsafe { inotify_rm_watch(self.fd, watch.wd) };
92        if res == -1 {
93            return Err(io::Error::from_raw_os_error(res));
94        }
95
96        Ok(())
97    }
98
99    /// start watching for events
100    pub async fn watch(&mut self) -> io::Result<Event> {
101        const SIZE: usize = size_of::<EventHeader>();
102        let mut buffer = [0u8; SIZE];
103
104        let mut amt = 0;
105        while amt < SIZE {
106            amt += self.file.read(&mut buffer[amt..SIZE]).await?;
107        }
108
109        let header: EventHeader = unsafe { std::mem::transmute(buffer) };
110        let total = header.len as usize;
111        let mut buffer = [0u8; 0x1000];
112
113        let mut amt: usize = 0;
114        while amt < total {
115            amt += self.file.read(&mut buffer[amt..total]).await?;
116        }
117
118        let os = OsStr::from_bytes(&buffer[0..total]);
119        let path = PathBuf::from(os);
120
121        Ok(Event {
122            watch: Watch { wd: header.wd },
123            mask: Mask(header.mask),
124            cookie: header.cookie,
125            path,
126        })
127    }
128
129    /// intentionally close the inotify instance
130    pub async fn close(self) -> io::Result<()> {
131        std::mem::forget(self.file);
132        let res = unsafe { close(self.fd) };
133
134        if res == -1 {
135            return Err(io::Error::from_raw_os_error(res));
136        }
137
138        Ok(())
139    }
140}
141
142impl std::fmt::Debug for Watch {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        f.debug_tuple("Watch").field(&self.wd).finish()?;
145        Ok(())
146    }
147}