use crate::*; use nix::sys::fanotify::{ EventFFlags, Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags, Response, }; use std::fs::{read_link, File, OpenOptions}; use std::io::ErrorKind; use std::io::{Read, Write}; use std::os::fd::AsRawFd; use std::thread; #[test] /// Run fanotify tests sequentially to avoid tmp files races pub fn test_fanotify() { require_capability!("test_fanotify", CAP_SYS_ADMIN); test_fanotify_notifications(); test_fanotify_responses(); } fn test_fanotify_notifications() { let group = Fanotify::init(InitFlags::FAN_CLASS_NOTIF, EventFFlags::O_RDONLY) .unwrap(); let tempdir = tempfile::tempdir().unwrap(); let tempfile = tempdir.path().join("test"); OpenOptions::new() .write(true) .create_new(true) .open(&tempfile) .unwrap(); group .mark( MarkFlags::FAN_MARK_ADD, MaskFlags::FAN_OPEN | MaskFlags::FAN_MODIFY | MaskFlags::FAN_CLOSE, None, Some(&tempfile), ) .unwrap(); // modify test file { let mut f = OpenOptions::new().write(true).open(&tempfile).unwrap(); f.write_all(b"hello").unwrap(); } let mut events = group.read_events().unwrap(); assert_eq!(events.len(), 1, "should have read exactly one event"); let event = events.pop().unwrap(); assert!(event.check_version()); assert_eq!( event.mask(), MaskFlags::FAN_OPEN | MaskFlags::FAN_MODIFY | MaskFlags::FAN_CLOSE_WRITE ); let fd_opt = event.fd(); let fd = fd_opt.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfile); // read test file { let mut f = File::open(&tempfile).unwrap(); let mut s = String::new(); f.read_to_string(&mut s).unwrap(); } let mut events = group.read_events().unwrap(); assert_eq!(events.len(), 1, "should have read exactly one event"); let event = events.pop().unwrap(); assert!(event.check_version()); assert_eq!( event.mask(), MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_NOWRITE ); let fd_opt = event.fd(); let fd = fd_opt.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfile); } fn test_fanotify_responses() { let group = Fanotify::init(InitFlags::FAN_CLASS_CONTENT, EventFFlags::O_RDONLY) .unwrap(); let tempdir = tempfile::tempdir().unwrap(); let tempfile = tempdir.path().join("test"); OpenOptions::new() .write(true) .create_new(true) .open(&tempfile) .unwrap(); group .mark( MarkFlags::FAN_MARK_ADD, MaskFlags::FAN_OPEN_PERM, None, Some(&tempfile), ) .unwrap(); let file_thread = thread::spawn({ let tempfile = tempfile.clone(); move || { // first open, should fail let Err(e) = File::open(&tempfile) else { panic!("The first open should fail"); }; assert_eq!(e.kind(), ErrorKind::PermissionDenied); // second open, should succeed File::open(&tempfile).unwrap(); } }); // Deny the first open try let mut events = group.read_events().unwrap(); assert_eq!(events.len(), 1, "should have read exactly one event"); let event = events.pop().unwrap(); assert!(event.check_version()); assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM); let fd_opt = event.fd(); let fd = fd_opt.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfile); group .write_response(FanotifyResponse::new(*fd, Response::FAN_DENY)) .unwrap(); // Allow the second open try let mut events = group.read_events().unwrap(); assert_eq!(events.len(), 1, "should have read exactly one event"); let event = events.pop().unwrap(); assert!(event.check_version()); assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM); let fd_opt = event.fd(); let fd = fd_opt.as_ref().unwrap(); let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); assert_eq!(path, tempfile); group .write_response(FanotifyResponse::new(*fd, Response::FAN_ALLOW)) .unwrap(); file_thread.join().unwrap(); }