1pub mod consts;
58pub mod handle;
59pub mod parse;
60pub mod read;
61pub mod types;
62
63use std::borrow::Cow;
64use std::ffi::OsStr;
65use std::fmt;
66use std::io;
67use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
68use std::os::unix::ffi::OsStrExt;
69
70use crate::types::HandleCache;
71
72pub mod prelude {
74 pub use crate::consts::*;
75 pub use crate::handle::{name_to_handle_at, open_by_handle_at, resolve_file_handle};
76 pub use crate::parse::parse_fid_events;
77 pub use crate::read::{read_fid_events, read_legacy, read_legacy_do, write_response, legacy_buffer_events, set_legacy_buffer_events};
78 pub use crate::types::{FidEvent, HandleCache, HandleKey, LegacyEvent, FanotifyResponse};
79 pub use crate::{fanotify_init, fanotify_mark, open_mount, Fanotify, FanotifyBuilder, FanotifyError};
80}
81
82#[derive(Debug)]
95pub enum FanotifyError {
96 Init(i32),
98 Mark(i32),
100 Read(i32),
102 Handle(i32),
105 Io(io::Error),
107}
108
109impl fmt::Display for FanotifyError {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 Self::Init(code) => write!(f, "fanotify_init failed (errno={}): {}", code, errno_desc_init(*code)),
113 Self::Mark(code) => write!(f, "fanotify_mark failed (errno={}): {}", code, errno_desc_mark(*code)),
114 Self::Read(code) => write!(f, "fanotify_read failed (errno={}): {}", code, errno_desc_read(*code)),
115 Self::Handle(code) => write!(f, "file_handle operation failed (errno={}): {}", code, errno_desc_handle(*code)),
116 Self::Io(e) => write!(f, "I/O error: {}", e),
117 }
118 }
119}
120
121impl std::error::Error for FanotifyError {}
122
123impl From<io::Error> for FanotifyError {
124 fn from(e: io::Error) -> Self {
125 Self::Io(e)
126 }
127}
128
129fn errno_desc_init(code: i32) -> Cow<'static, str> {
130 match code {
131 libc::EINVAL => Cow::Borrowed(concat!(
132 "An invalid value was passed in flags or event_f_flags.\n",
133 " Common mistakes:\n",
134 " - Using FAN_REPORT_NAME without FAN_REPORT_DIR_FID\n",
135 " - Combining FAN_REPORT_FID with legacy-only flags\n",
136 " - Setting reserved or unsupported bits in event_f_flags\n",
137 " Check man fanotify_init(2) for all allowable bits."
138 )),
139 libc::EMFILE => Cow::Borrowed(concat!(
140 "Too many fanotify groups for this user.\n",
141 " The per-user limit is 128 groups. Each init() call creates\n",
142 " a new notification group. Check if previous groups are still\n",
143 " open (forgeting to close an OwnedFd can leak groups)."
144 )),
145 libc::ENOMEM => Cow::Borrowed(concat!(
146 "Out of memory.\n",
147 " The kernel could not allocate memory for the notification\n",
148 " group's internal data structures. Try reducing the event\n",
149 " queue size or closing other fanotify groups."
150 )),
151 libc::ENOSYS => Cow::Borrowed(concat!(
152 "This kernel does not support fanotify.\n",
153 " The fanotify API is available only if the kernel was\n",
154 " configured with CONFIG_FANOTIFY. Most distro kernels\n",
155 " include this by default. Custom or container-optimized\n",
156 " kernels may omit it. Check /proc/config.gz or\n",
157 " /boot/config-$(uname -r) for CONFIG_FANOTIFY=y."
158 )),
159 libc::EPERM => Cow::Borrowed(concat!(
160 "Need CAP_SYS_ADMIN capability.\n",
161 " Creating a fanotify group requires elevated privileges.\n",
162 " Run as root, or add the capability via:\n",
163 " sudo setcap cap_sys_admin+ep /path/to/binary\n",
164 " Or run the process under a user namespace with\n",
165 " CAP_SYS_ADMIN mapped."
166 )),
167 _ => Cow::Owned(format!("Unknown error (errno={}). See fanotify_init(2) for details.", code)),
168 }
169}
170
171fn errno_desc_mark(code: i32) -> Cow<'static, str> {
172 match code {
173 libc::EBADF => Cow::Borrowed(concat!(
174 "Invalid file descriptor.\n",
175 " Either the fanotify fd is invalid, or pathname is relative\n",
176 " but dirfd is neither AT_FDCWD nor a valid fd.\n",
177 " Check that fanotify_init succeeded and the fd hasn't been\n",
178 " closed or moved into another process."
179 )),
180 libc::EINVAL => Cow::Borrowed(concat!(
181 "Invalid flags or mask, or wrong notification class.\n",
182 " Common causes:\n",
183 " - The fanotify group was created with FAN_CLASS_NOTIF but\n",
184 " mask contains permission events (FAN_OPEN_PERM or\n",
185 " FAN_ACCESS_PERM). Permission events require\n",
186 " FAN_CLASS_CONTENT or FAN_CLASS_PRE_CONTENT.\n",
187 " - An invalid combination of mark flags was passed.\n",
188 " - In FID mode, some mask flags are incompatible."
189 )),
190 libc::ENODEV => Cow::Borrowed(concat!(
191 "Filesystem does not support fsid.\n",
192 " The filesystem indicated by pathname is not associated with\n",
193 " a filesystem that supports fsid (e.g., tmpfs). This error\n",
194 " can occur only with a fanotify group that identifies objects\n",
195 " by file handles (FID mode)."
196 )),
197 libc::ENOENT => Cow::Borrowed(concat!(
198 "Path does not exist.\n",
199 " The filesystem object indicated by dirfd and pathname does\n",
200 " not exist. This also occurs when trying to remove a mark\n",
201 " from an object which is not marked.\n",
202 " Tip: use FAN_MARK_DONT_FOLLOW if pathname is a dangling\n",
203 " symlink, or check that the path exists before marking."
204 )),
205 libc::ENOMEM => Cow::Borrowed(concat!(
206 "Out of memory.\n",
207 " The kernel could not allocate memory to store the mark.\n",
208 " Try reducing the number of marks or closing other groups."
209 )),
210 libc::ENOSPC => Cow::Borrowed(concat!(
211 "Too many marks (exceeded 8192 limit).\n",
212 " The default mark limit is 8192 per group. Either:\n",
213 " - Use FAN_MARK_FILESYSTEM instead of marking individual\n",
214 " paths to reduce mark count.\n",
215 " - Pass FAN_UNLIMITED_MARKS to init() if you have\n",
216 " CAP_SYS_ADMIN and genuinely need more marks.\n",
217 " - Remove unused marks with FAN_MARK_REMOVE."
218 )),
219 libc::ENOSYS => Cow::Borrowed(concat!(
220 "This kernel does not implement fanotify_mark.\n",
221 " CONFIG_FANOTIFY is likely missing from the kernel config."
222 )),
223 libc::ENOTDIR => Cow::Borrowed(concat!(
224 "FAN_MARK_ONLYDIR specified but path is not a directory.\n",
225 " Remove FAN_MARK_ONLYDIR if you intended to mark a regular\n",
226 " file, or point the path to a directory."
227 )),
228 libc::EOPNOTSUPP => Cow::Borrowed(concat!(
229 "Filesystem does not support file handles.\n",
230 " The object is on a filesystem that does not support the\n",
231 " encoding of file handles (e.g., some FUSE filesystems,\n",
232 " network filesystems without export support). This error\n",
233 " can occur only with a fanotify group in FID mode."
234 )),
235 libc::EXDEV => Cow::Borrowed(concat!(
236 "Filesystem subvolume uses a different fsid.\n",
237 " The object resides within a filesystem subvolume (e.g.,\n",
238 " btrfs subvolume) which uses a different fsid than its root\n",
239 " superblock. Try marking the subvolume's mount point,\n",
240 " or use FAN_MARK_FILESYSTEM on the subvolume directly."
241 )),
242 _ => Cow::Owned(format!("Unknown error (errno={}). See fanotify_mark(2) for details.", code)),
243 }
244}
245
246fn errno_desc_read(code: i32) -> Cow<'static, str> {
247 match code {
248 libc::EAGAIN => Cow::Borrowed(concat!(
249 "No events available (non-blocking fd).\n",
250 " The fanotify fd was created with FAN_NONBLOCK and no events\n",
251 " are currently pending. This is not an error — retry later\n",
252 " using epoll/poll/select to wait for readability, or switch\n",
253 " to blocking mode (remove FAN_NONBLOCK)."
254 )),
255 libc::EBADF => Cow::Borrowed(concat!(
256 "Invalid file descriptor.\n",
257 " The fanotify fd is not a valid open file descriptor or\n",
258 " was not opened for reading. Check that fanotify_init()\n",
259 " succeeded and the fd hasn't been closed."
260 )),
261 libc::EINTR => Cow::Borrowed(concat!(
262 "Interrupted by signal.\n",
263 " The read call was interrupted by a signal before any data\n",
264 " was read. Retry the read (EINTR is transient)."
265 )),
266 libc::ENOMEM => Cow::Borrowed(concat!(
267 "Out of memory.\n",
268 " Cannot allocate memory for the read buffer. Try reducing\n",
269 " the buffer size or closing other memory-intensive\n",
270 " applications."
271 )),
272 _ => Cow::Owned(format!("Unknown error (errno={}). See fanotify_read(2) for details.", code)),
273 }
274}
275
276fn errno_desc_handle(code: i32) -> Cow<'static, str> {
277 match code {
278 libc::EBADF => Cow::Borrowed(concat!(
279 "Invalid mount file descriptor.\n",
280 " The mount_fd passed to open_by_handle_at is not a valid\n",
281 " open file descriptor. Make sure open_mount() succeeded\n",
282 " and the fd hasn't been closed. The mount_fd must belong\n",
283 " to a mount point on the same filesystem as the handle."
284 )),
285 libc::ENOENT => Cow::Borrowed(concat!(
286 "File or directory does not exist.\n",
287 " The file identified by the handle has been deleted. In\n",
288 " fanotify FID mode this is expected when events are\n",
289 " delivered concurrently with deletions. Use a persistent\n",
290 " HandleCache to recover paths in later read cycles.\n",
291 " See parse::resolve_with_cache for details."
292 )),
293 libc::EINVAL => Cow::Borrowed(concat!(
294 "Invalid handle or flags.\n",
295 " The file handle data is malformed or the flags passed to\n",
296 " open_by_handle_at are invalid. This may indicate a kernel\n",
297 " bug or corrupted handle data."
298 )),
299 libc::EOVERFLOW => Cow::Borrowed(concat!(
300 "Handle buffer too small.\n",
301 " The initial buffer passed to name_to_handle_at was too\n",
302 " small. This is handled automatically by retrying with\n",
303 " the correct size, but if you see this error it means the\n",
304 " retry also failed. Try using a larger initial buffer."
305 )),
306 libc::EOPNOTSUPP => Cow::Borrowed(concat!(
307 "Filesystem does not support file handles.\n",
308 " The filesystem does not support name_to_handle_at or\n",
309 " open_by_handle_at. Common examples:\n",
310 " - tmpfs (only supports handles for directories)\n",
311 " - Some FUSE filesystems\n",
312 " - Network filesystems without export support\n",
313 " Try using open_mount() on a different path backed by a\n",
314 " filesystem that supports handles (e.g., ext4, xfs, btrfs)."
315 )),
316 _ => Cow::Owned(format!("Unknown error (errno={}). See name_to_handle_at(2) for details.", code)),
317 }
318}
319
320pub type Result<T> = std::result::Result<T, FanotifyError>;
323
324#[derive(Debug)]
347pub struct Fanotify {
348 fd: OwnedFd,
349}
350
351impl Fanotify {
352 #[allow(clippy::new_ret_no_self)]
357 pub fn new() -> FanotifyBuilder {
358 FanotifyBuilder {
359 flags: consts::FAN_CLASS_NOTIF | consts::FAN_CLOEXEC,
360 event_f_flags: 0,
361 }
362 }
363
364 pub fn mark<P: AsRef<OsStr> + ?Sized>(
376 &self,
377 flags: u32,
378 mask: u64,
379 path: &P,
380 ) -> std::result::Result<(), FanotifyError> {
381 fanotify_mark(&self.fd, flags, mask, consts::AT_FDCWD, path)
382 }
383
384 pub fn read_events(
390 &self,
391 mount_fds: &[OwnedFd],
392 buf: &mut Vec<u8>,
393 cache: Option<&mut HandleCache>,
394 ) -> std::result::Result<Vec<crate::types::FidEvent>, FanotifyError> {
395 crate::read::read_fid_events(&self.fd, mount_fds, buf, cache)
396 }
397
398 pub fn read_legacy(&self) -> Result<Vec<crate::types::LegacyEvent>> {
402 crate::read::read_legacy(&self.fd)
403 }
404
405 pub fn read_legacy_do<F>(&self, callback: F) -> Result<()>
409 where
410 F: FnMut(&crate::types::LegacyEvent),
411 {
412 crate::read::read_legacy_do(&self.fd, callback)
413 }
414
415 pub fn send_response(&self, response: &crate::types::FanotifyResponse) -> Result<()> {
419 crate::read::write_response(&self.fd, response)
420 }
421
422 pub fn legacy_buffer_events() -> usize {
424 crate::read::legacy_buffer_events()
425 }
426
427 pub fn set_legacy_buffer_events(n: usize) {
429 crate::read::set_legacy_buffer_events(n)
430 }
431
432 pub fn mark_mount<P: AsRef<OsStr> + ?Sized>(
434 &self,
435 mask: u64,
436 path: &P,
437 ) -> Result<()> {
438 fanotify_mark(
439 &self.fd,
440 crate::consts::FAN_MARK_ADD | crate::consts::FAN_MARK_MOUNT,
441 mask,
442 crate::consts::AT_FDCWD,
443 path,
444 )
445 }
446
447 pub fn as_fd(&self) -> &OwnedFd {
449 &self.fd
450 }
451
452 pub fn into_inner(self) -> OwnedFd {
456 self.fd
457 }
458}
459
460impl AsFd for Fanotify {
461 fn as_fd(&self) -> BorrowedFd<'_> {
462 self.fd.as_fd()
463 }
464}
465
466#[derive(Debug, Clone)]
472pub struct FanotifyBuilder {
473 flags: u32,
474 event_f_flags: u32,
475}
476
477impl FanotifyBuilder {
478 pub fn cloexec(mut self) -> Self {
480 self.flags |= consts::FAN_CLOEXEC;
481 self
482 }
483
484 pub fn nonblock(mut self) -> Self {
486 self.flags |= consts::FAN_NONBLOCK;
487 self
488 }
489
490 pub fn class_notif(mut self) -> Self {
492 self.flags = (self.flags & !0x0C) | consts::FAN_CLASS_NOTIF;
493 self
494 }
495
496 pub fn class_content(mut self) -> Self {
498 self.flags = (self.flags & !0x0C) | 0x0000_0004;
499 self
500 }
501
502 pub fn class_pre_content(mut self) -> Self {
504 self.flags = (self.flags & !0x0C) | 0x0000_0008;
505 self
506 }
507
508 pub fn report_fid(mut self) -> Self {
510 self.flags |= consts::FAN_REPORT_FID;
511 self
512 }
513
514 pub fn report_dir_fid(mut self) -> Self {
516 self.flags |= consts::FAN_REPORT_DIR_FID;
517 self
518 }
519
520 pub fn report_name(mut self) -> Self {
522 self.flags |= consts::FAN_REPORT_NAME;
523 self
524 }
525
526 pub fn report_tid(mut self) -> Self {
528 self.flags |= 0x0000_0100;
529 self
530 }
531
532 pub fn unlimited_queue(mut self) -> Self {
534 self.flags |= 0x0000_0010;
535 self
536 }
537
538 pub fn unlimited_marks(mut self) -> Self {
540 self.flags |= 0x0000_0020;
541 self
542 }
543
544 pub fn event_flags(mut self, flags: u32) -> Self {
549 self.event_f_flags = flags;
550 self
551 }
552
553 pub fn enable_audit(mut self) -> Self {
555 self.flags |= crate::consts::FAN_ENABLE_AUDIT;
556 self
557 }
558
559 pub fn report_pidfd(mut self) -> Self {
561 self.flags |= crate::consts::FAN_REPORT_PIDFD;
562 self
563 }
564
565 pub fn report_target_fid(mut self) -> Self {
569 self.flags |= crate::consts::FAN_REPORT_TARGET_FID;
570 self
571 }
572
573 pub fn raw_flags(mut self, flags: u32) -> Self {
575 self.flags |= flags;
576 self
577 }
578
579 pub fn init(self) -> std::result::Result<Fanotify, FanotifyError> {
583 let fd = fanotify_init(self.flags, self.event_f_flags)?;
584 Ok(Fanotify { fd })
585 }
586}
587
588impl Default for FanotifyBuilder {
589 fn default() -> Self {
590 FanotifyBuilder {
591 flags: consts::FAN_CLASS_NOTIF | consts::FAN_CLOEXEC,
592 event_f_flags: 0,
593 }
594 }
595}
596
597pub fn fanotify_init(flags: u32, event_f_flags: u32) -> std::result::Result<OwnedFd, FanotifyError> {
610 let fd = unsafe { libc::fanotify_init(flags as libc::c_uint, event_f_flags as libc::c_uint) };
614 if fd < 0 {
615 return Err(FanotifyError::Init(io::Error::last_os_error().raw_os_error().unwrap_or(0)));
616 }
617 Ok(unsafe { <OwnedFd as FromRawFd>::from_raw_fd(fd) })
621}
622
623pub fn fanotify_mark<P: AsRef<OsStr> + ?Sized>(
627 fanotify_fd: &OwnedFd,
628 flags: u32,
629 mask: u64,
630 dirfd: i32,
631 path: &P,
632) -> std::result::Result<(), FanotifyError> {
633 let mut raw = path.as_ref().as_bytes().to_vec();
634 raw.push(0); let ret = unsafe {
640 libc::fanotify_mark(
641 fanotify_fd.as_raw_fd(),
642 flags as libc::c_uint,
643 mask,
644 dirfd,
645 raw.as_ptr() as *const libc::c_char,
646 )
647 };
648 if ret < 0 {
649 return Err(FanotifyError::Mark(io::Error::last_os_error().raw_os_error().unwrap_or(0)));
650 }
651 Ok(())
652}
653
654pub fn open_mount<P: AsRef<OsStr> + ?Sized>(path: &P) -> std::result::Result<OwnedFd, FanotifyError> {
666 use std::os::unix::fs::OpenOptionsExt;
667 let file = std::fs::OpenOptions::new()
668 .custom_flags(libc::O_PATH | libc::O_CLOEXEC)
669 .read(true)
670 .open(path.as_ref())
671 .map_err(FanotifyError::Io)?;
672 let fd = file.into();
673 Ok(fd)
674}
675
676#[cfg(test)]
679mod integration_tests {
680 use super::*;
681 use std::path::PathBuf;
682 use crate::types::{FidEvent, LegacyEvent, FanotifyResponse, HandleCache};
683
684 #[test]
687 fn test_new_event_constants_exist() {
688 let _ = consts::FAN_OPEN_PERM;
690 let _ = consts::FAN_ACCESS_PERM;
691 let _ = consts::FAN_OPEN_EXEC_PERM;
692 let _ = consts::FAN_RENAME;
693 let _ = consts::FAN_FS_ERROR;
694 let _ = consts::FAN_REPORT_TID;
695 let _ = consts::FAN_REPORT_PIDFD;
696 let _ = consts::FAN_REPORT_TARGET_FID;
697 let _ = consts::FAN_UNLIMITED_QUEUE;
698 let _ = consts::FAN_UNLIMITED_MARKS;
699 let _ = consts::FAN_ENABLE_AUDIT;
700 let _ = consts::FAN_CLASS_CONTENT;
701 let _ = consts::FAN_CLASS_PRE_CONTENT;
702 let _ = consts::FAN_REPORT_DFID_NAME;
703 let _ = consts::FAN_REPORT_DFID_NAME_TARGET;
704 let _ = consts::FAN_MARK_DONT_FOLLOW;
705 let _ = consts::FAN_MARK_ONLYDIR;
706 let _ = consts::FAN_MARK_MOUNT;
707 let _ = consts::FAN_MARK_IGNORED_MASK;
708 let _ = consts::FAN_MARK_IGNORED_SURV_MODIFY;
709 let _ = consts::FAN_MARK_EVICTABLE;
710 let _ = consts::FAN_MARK_IGNORE;
711 let _ = consts::FAN_MARK_IGNORE_SURV;
712 let _ = consts::FAN_ALLOW;
713 let _ = consts::FAN_DENY;
714 let _ = consts::FAN_AUDIT;
715 let _ = consts::O_RDONLY;
716 let _ = consts::O_WRONLY;
717 let _ = consts::O_RDWR;
718 let _ = consts::O_APPEND;
719 let _ = consts::O_CLOEXEC;
720 }
721
722 #[test]
723 fn test_deprecated_constants_still_compile() {
724 #[allow(deprecated)]
725 {
726 let _ = consts::FAN_ALL_CLASS_BITS;
727 let _ = consts::FAN_ALL_INIT_FLAGS;
728 let _ = consts::FAN_ALL_MARK_FLAGS;
729 let _ = consts::FAN_ALL_EVENTS;
730 let _ = consts::FAN_ALL_PERM_EVENTS;
731 let _ = consts::FAN_ALL_OUTGOING_EVENTS;
732 }
733 }
734
735 #[test]
736 fn test_mask_to_event_names_includes_new() {
737 let names = consts::mask_to_event_names(
738 consts::FAN_OPEN_PERM | consts::FAN_RENAME | consts::FAN_FS_ERROR
739 );
740 assert!(names.contains(&"OPEN_PERM"));
741 assert!(names.contains(&"RENAME"));
742 assert!(names.contains(&"FS_ERROR"));
743 }
744
745 #[test]
746 fn test_composed_event_masks() {
747 let close = consts::FAN_CLOSE;
748 assert_eq!(close, consts::FAN_CLOSE_WRITE | consts::FAN_CLOSE_NOWRITE);
749
750 let mv = consts::FAN_MOVE;
751 assert_eq!(mv, consts::FAN_MOVED_FROM | consts::FAN_MOVED_TO);
752
753 let dfid_name = consts::FAN_REPORT_DFID_NAME;
754 assert_eq!(dfid_name, consts::FAN_REPORT_DIR_FID | consts::FAN_REPORT_NAME);
755 }
756
757 #[test]
760 fn test_builder_default_flags() {
761 let builder = FanotifyBuilder::default();
762 assert!(builder.flags & 0x0C == 0, "class bits should be NOTIF");
765 assert!(builder.flags & consts::FAN_CLOEXEC != 0, "CLOEXEC should be set by default");
766 assert!(builder.flags & consts::FAN_CLOEXEC != 0);
767 }
768
769 #[test]
770 fn test_builder_chain_all_flags() {
771 let builder = FanotifyBuilder::default()
772 .cloexec()
773 .nonblock()
774 .class_content()
775 .report_fid()
776 .report_dir_fid()
777 .report_name()
778 .report_tid()
779 .report_pidfd()
780 .report_target_fid()
781 .unlimited_queue()
782 .unlimited_marks()
783 .enable_audit()
784 .event_flags(consts::O_CLOEXEC)
785 .raw_flags(0x1000);
786 assert!(builder.flags & consts::FAN_NONBLOCK != 0);
788 assert!(builder.flags & consts::FAN_REPORT_FID != 0);
789 assert!(builder.flags & consts::FAN_REPORT_TID != 0);
790 assert!(builder.flags & consts::FAN_UNLIMITED_QUEUE != 0);
791 assert!(builder.flags & 0x1000 != 0);
792 assert_eq!(builder.event_f_flags, consts::O_CLOEXEC);
793 }
794
795 #[test]
796 fn test_builder_class_modes_are_exclusive() {
797 let b = FanotifyBuilder::default().class_content();
799 assert!(b.flags & 0x0C == consts::FAN_CLASS_CONTENT || (b.flags & 0x0C) == 0x04);
800
801 let b = b.class_pre_content();
802 assert_eq!(b.flags & 0x0C, consts::FAN_CLASS_PRE_CONTENT);
804 }
805
806 #[test]
809 fn test_error_display_init() {
810 let e = FanotifyError::Init(libc::EPERM);
811 let msg = e.to_string();
812 assert!(msg.contains("fanotify_init"));
813 assert!(msg.contains("CAP_SYS_ADMIN"));
814 }
815
816 #[test]
817 fn test_error_display_mark() {
818 let e = FanotifyError::Mark(libc::ENOENT);
819 let msg = e.to_string();
820 assert!(msg.contains("fanotify_mark"));
821 assert!(msg.contains("does not exist"));
822 }
823
824 #[test]
825 fn test_error_display_read() {
826 let e = FanotifyError::Read(libc::EAGAIN);
827 let msg = e.to_string();
828 assert!(msg.contains("fanotify_read"));
829 assert!(msg.contains("non-blocking"));
830 }
831
832 #[test]
833 fn test_error_display_handle() {
834 let e = FanotifyError::Handle(libc::EOPNOTSUPP);
835 let msg = e.to_string();
836 assert!(msg.contains("file_handle"));
837 assert!(msg.contains("does not support file handles"));
838 }
839
840 #[test]
841 fn test_error_into_from_io() {
842 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
843 let e: FanotifyError = io_err.into();
844 match e {
845 FanotifyError::Io(_) => {}
846 _ => panic!("expected Io variant"),
847 }
848 }
849
850 #[test]
851 fn test_error_impl_error_trait() {
852 fn check_error(_: &dyn std::error::Error) {}
853 let e = FanotifyError::Init(libc::EINVAL);
854 check_error(&e); }
856
857 #[test]
860 fn test_fid_event_methods() {
861 let ev = FidEvent {
862 mask: consts::FAN_CREATE | consts::FAN_MODIFY,
863 pid: 42,
864 path: PathBuf::from("/tmp/foo"),
865 dfid_name_handle: None,
866 dfid_name_filename: None,
867 self_handle: None,
868 };
869 assert!(!ev.is_overflow());
870 let names = ev.event_names();
871 assert_eq!(names, vec!["MODIFY", "CREATE"]);
872 }
873
874 #[test]
875 fn test_fid_event_overflow() {
876 let ev = FidEvent {
877 mask: consts::FAN_Q_OVERFLOW,
878 pid: 0,
879 path: PathBuf::new(),
880 dfid_name_handle: None,
881 dfid_name_filename: None,
882 self_handle: None,
883 };
884 assert!(ev.is_overflow());
885 }
886
887 #[test]
888 fn test_legacy_event_auto_close_fd() {
889 let ev = LegacyEvent {
891 mask: 0,
892 fd: -1,
893 pid: 0,
894 path: PathBuf::new(),
895 };
896 drop(ev);
897 }
898
899 #[test]
900 fn test_fanotify_response_struct() {
901 let resp = FanotifyResponse {
902 fd: 5,
903 response: consts::FAN_ALLOW,
904 };
905 assert_eq!(resp.fd, 5);
906 assert_eq!(resp.response, 0x01);
907 }
908
909 #[test]
910 fn test_handle_cache_type() {
911 use std::collections::HashMap;
912 let _cache: HandleCache = HashMap::new();
913 }
914
915 #[test]
918 fn test_open_mount_fails_on_nonexistent() {
919 let result = open_mount("/nonexistent_path_12345");
920 assert!(result.is_err());
921 }
922
923 #[test]
924 fn test_open_mount_succeeds_on_dev() {
925 let result = open_mount("/dev");
927 assert!(result.is_ok());
928 }
929
930 #[test]
933 fn test_fanotify_impl_as_fd() {
934 use std::os::fd::AsFd;
935 fn _takes_as_fd(_: &impl AsFd) {}
938 }
940
941 #[test]
946 fn test_public_api_function_signatures() {
947 fn _check_free_fns() {
949 let _ = fanotify_init(0, 0);
950 let _ = open_mount("/");
951 let _ = handle::name_to_handle_at(std::path::Path::new("/"));
952 }
953
954 fn _check_prelude() {
956 let _ = crate::prelude::Fanotify::new();
957 let _ = crate::prelude::FanotifyBuilder::default();
958 let _ = crate::prelude::FidEvent {
959 mask: 0, pid: 0, path: PathBuf::new(),
960 dfid_name_handle: None, dfid_name_filename: None, self_handle: None,
961 };
962 let _ = crate::prelude::LegacyEvent {
963 mask: 0, fd: -1, pid: 0, path: PathBuf::new(),
964 };
965 let _ = crate::prelude::FanotifyResponse { fd: -1, response: 0 };
966 }
967
968 _check_free_fns();
969 _check_prelude();
970 }
971}