|
1 | 1 | //! inotify support for working with inotifies |
2 | 2 |
|
3 | | -pub use crate::backend::fs::inotify::{CreateFlags, WatchFlags}; |
| 3 | +pub use crate::backend::fs::inotify::{CreateFlags, ReadFlags, WatchFlags}; |
4 | 4 | use crate::backend::fs::syscalls; |
5 | 5 | use crate::fd::{AsFd, OwnedFd}; |
| 6 | +use crate::ffi::CStr; |
6 | 7 | use crate::io; |
| 8 | +use crate::io::{read_uninit, Errno}; |
| 9 | +use core::mem::{align_of, size_of, MaybeUninit}; |
| 10 | +use linux_raw_sys::general::inotify_event; |
7 | 11 |
|
8 | 12 | /// `inotify_init1(flags)`—Creates a new inotify object. |
9 | 13 | /// |
@@ -41,3 +45,118 @@ pub fn inotify_add_watch<P: crate::path::Arg>( |
41 | 45 | pub fn inotify_remove_watch(inot: impl AsFd, wd: i32) -> io::Result<()> { |
42 | 46 | syscalls::inotify_rm_watch(inot.as_fd(), wd) |
43 | 47 | } |
| 48 | + |
| 49 | +/// An inotify event iterator implemented with the read syscall. |
| 50 | +/// |
| 51 | +/// See the [`RawDir`] API for more details and usage examples as this API is |
| 52 | +/// based on it. |
| 53 | +/// |
| 54 | +/// [`RawDir`]: crate::fs::raw_dir::RawDir |
| 55 | +pub struct InotifyReader<'buf, Fd: AsFd> { |
| 56 | + fd: Fd, |
| 57 | + buf: &'buf mut [MaybeUninit<u8>], |
| 58 | + initialized: usize, |
| 59 | + offset: usize, |
| 60 | +} |
| 61 | + |
| 62 | +impl<'buf, Fd: AsFd> InotifyReader<'buf, Fd> { |
| 63 | + /// Create a new iterator from the given file descriptor and buffer. |
| 64 | + pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self { |
| 65 | + Self { |
| 66 | + fd, |
| 67 | + buf: { |
| 68 | + let offset = buf.as_ptr().align_offset(align_of::<inotify_event>()); |
| 69 | + if offset < buf.len() { |
| 70 | + &mut buf[offset..] |
| 71 | + } else { |
| 72 | + &mut [] |
| 73 | + } |
| 74 | + }, |
| 75 | + initialized: 0, |
| 76 | + offset: 0, |
| 77 | + } |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +/// An inotify event. |
| 82 | +#[derive(Debug)] |
| 83 | +pub struct InotifyEvent<'a> { |
| 84 | + wd: i32, |
| 85 | + events: ReadFlags, |
| 86 | + cookie: u32, |
| 87 | + file_name: Option<&'a CStr>, |
| 88 | +} |
| 89 | + |
| 90 | +impl<'a> InotifyEvent<'a> { |
| 91 | + /// Returns the watch for which this event occurs. |
| 92 | + #[inline] |
| 93 | + pub fn wd(&self) -> i32 { |
| 94 | + self.wd |
| 95 | + } |
| 96 | + |
| 97 | + /// Returns a description of the events. |
| 98 | + #[inline] |
| 99 | + #[doc(alias = "mask")] |
| 100 | + pub fn events(&self) -> ReadFlags { |
| 101 | + self.events |
| 102 | + } |
| 103 | + |
| 104 | + /// Returns the unique cookie associating related events. |
| 105 | + #[inline] |
| 106 | + pub fn cookie(&self) -> u32 { |
| 107 | + self.cookie |
| 108 | + } |
| 109 | + |
| 110 | + /// Returns the file name of this event, if any. |
| 111 | + #[inline] |
| 112 | + pub fn file_name(&self) -> Option<&CStr> { |
| 113 | + self.file_name |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +impl<'buf, Fd: AsFd> InotifyReader<'buf, Fd> { |
| 118 | + /// Read the next inotify event. |
| 119 | + #[allow(unsafe_code)] |
| 120 | + pub fn next(&mut self) -> io::Result<InotifyEvent> { |
| 121 | + if self.is_buffer_empty() { |
| 122 | + match read_uninit(self.fd.as_fd(), self.buf).map(|(init, _)| init.len()) { |
| 123 | + Ok(0) => return Err(Errno::INVAL), |
| 124 | + Ok(bytes_read) => { |
| 125 | + self.initialized = bytes_read; |
| 126 | + self.offset = 0; |
| 127 | + } |
| 128 | + Err(e) => return Err(e), |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + let ptr = self.buf[self.offset..].as_ptr(); |
| 133 | + // SAFETY: |
| 134 | + // - This data is initialized by the check above. |
| 135 | + // - Assumption: the kernel will not give us partial structs. |
| 136 | + // - Assumption: the kernel uses proper alignment between structs. |
| 137 | + // - The starting pointer is aligned (performed in RawDir::new) |
| 138 | + let event = unsafe { &*ptr.cast::<inotify_event>() }; |
| 139 | + |
| 140 | + self.offset += size_of::<inotify_event>() + usize::try_from(event.len).unwrap(); |
| 141 | + |
| 142 | + Ok(InotifyEvent { |
| 143 | + wd: event.wd, |
| 144 | + events: ReadFlags::from_bits_retain(event.mask), |
| 145 | + cookie: event.cookie, |
| 146 | + file_name: if event.len > 0 { |
| 147 | + // SAFETY: The kernel guarantees a NUL-terminated string. |
| 148 | + Some(unsafe { CStr::from_ptr(event.name.as_ptr().cast()) }) |
| 149 | + } else { |
| 150 | + None |
| 151 | + }, |
| 152 | + }) |
| 153 | + } |
| 154 | + |
| 155 | + /// Returns true if the internal buffer is empty and will be refilled when |
| 156 | + /// calling [`next`]. This is useful to avoid further blocking reads. |
| 157 | + /// |
| 158 | + /// [`next`]: Self::next |
| 159 | + pub fn is_buffer_empty(&self) -> bool { |
| 160 | + self.offset >= self.initialized |
| 161 | + } |
| 162 | +} |
0 commit comments