1 use crate::*;
2 use nix::sys::fanotify::{
3 EventFFlags, Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags,
4 Response,
5 };
6 use std::fs::{read_link, File, OpenOptions};
7 use std::io::ErrorKind;
8 use std::io::{Read, Write};
9 use std::os::fd::AsRawFd;
10 use std::thread;
11
12 #[test]
13 /// Run fanotify tests sequentially to avoid tmp files races
test_fanotify()14 pub fn test_fanotify() {
15 require_capability!("test_fanotify", CAP_SYS_ADMIN);
16
17 test_fanotify_notifications();
18 test_fanotify_responses();
19 }
20
test_fanotify_notifications()21 fn test_fanotify_notifications() {
22 let group =
23 Fanotify::init(InitFlags::FAN_CLASS_NOTIF, EventFFlags::O_RDONLY)
24 .unwrap();
25 let tempdir = tempfile::tempdir().unwrap();
26 let tempfile = tempdir.path().join("test");
27 OpenOptions::new()
28 .write(true)
29 .create_new(true)
30 .open(&tempfile)
31 .unwrap();
32
33 group
34 .mark(
35 MarkFlags::FAN_MARK_ADD,
36 MaskFlags::FAN_OPEN | MaskFlags::FAN_MODIFY | MaskFlags::FAN_CLOSE,
37 None,
38 Some(&tempfile),
39 )
40 .unwrap();
41
42 // modify test file
43 {
44 let mut f = OpenOptions::new().write(true).open(&tempfile).unwrap();
45 f.write_all(b"hello").unwrap();
46 }
47
48 let mut events = group.read_events().unwrap();
49 assert_eq!(events.len(), 1, "should have read exactly one event");
50 let event = events.pop().unwrap();
51 assert!(event.check_version());
52 assert_eq!(
53 event.mask(),
54 MaskFlags::FAN_OPEN
55 | MaskFlags::FAN_MODIFY
56 | MaskFlags::FAN_CLOSE_WRITE
57 );
58 let fd_opt = event.fd();
59 let fd = fd_opt.as_ref().unwrap();
60 let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap();
61 assert_eq!(path, tempfile);
62
63 // read test file
64 {
65 let mut f = File::open(&tempfile).unwrap();
66 let mut s = String::new();
67 f.read_to_string(&mut s).unwrap();
68 }
69
70 let mut events = group.read_events().unwrap();
71 assert_eq!(events.len(), 1, "should have read exactly one event");
72 let event = events.pop().unwrap();
73 assert!(event.check_version());
74 assert_eq!(
75 event.mask(),
76 MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_NOWRITE
77 );
78 let fd_opt = event.fd();
79 let fd = fd_opt.as_ref().unwrap();
80 let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap();
81 assert_eq!(path, tempfile);
82 }
83
test_fanotify_responses()84 fn test_fanotify_responses() {
85 let group =
86 Fanotify::init(InitFlags::FAN_CLASS_CONTENT, EventFFlags::O_RDONLY)
87 .unwrap();
88 let tempdir = tempfile::tempdir().unwrap();
89 let tempfile = tempdir.path().join("test");
90 OpenOptions::new()
91 .write(true)
92 .create_new(true)
93 .open(&tempfile)
94 .unwrap();
95
96 group
97 .mark(
98 MarkFlags::FAN_MARK_ADD,
99 MaskFlags::FAN_OPEN_PERM,
100 None,
101 Some(&tempfile),
102 )
103 .unwrap();
104
105 let file_thread = thread::spawn({
106 let tempfile = tempfile.clone();
107
108 move || {
109 // first open, should fail
110 let Err(e) = File::open(&tempfile) else {
111 panic!("The first open should fail");
112 };
113 assert_eq!(e.kind(), ErrorKind::PermissionDenied);
114
115 // second open, should succeed
116 File::open(&tempfile).unwrap();
117 }
118 });
119
120 // Deny the first open try
121 let mut events = group.read_events().unwrap();
122 assert_eq!(events.len(), 1, "should have read exactly one event");
123 let event = events.pop().unwrap();
124 assert!(event.check_version());
125 assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM);
126 let fd_opt = event.fd();
127 let fd = fd_opt.as_ref().unwrap();
128 let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap();
129 assert_eq!(path, tempfile);
130 group
131 .write_response(FanotifyResponse::new(*fd, Response::FAN_DENY))
132 .unwrap();
133
134 // Allow the second open try
135 let mut events = group.read_events().unwrap();
136 assert_eq!(events.len(), 1, "should have read exactly one event");
137 let event = events.pop().unwrap();
138 assert!(event.check_version());
139 assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM);
140 let fd_opt = event.fd();
141 let fd = fd_opt.as_ref().unwrap();
142 let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap();
143 assert_eq!(path, tempfile);
144 group
145 .write_response(FanotifyResponse::new(*fd, Response::FAN_ALLOW))
146 .unwrap();
147
148 file_thread.join().unwrap();
149 }
150