1#![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
28pub struct INotify {
30 fd: c_int,
31 file: File,
32}
33
34#[derive(Clone, Copy)]
36pub struct Watch {
37 wd: c_int,
38}
39
40#[derive(Debug)]
42pub struct Event {
43 pub watch: Watch,
45
46 pub mask: Mask,
48
49 pub cookie: u32,
51
52 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 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 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 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 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 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}