1 use crate::*;
2 use nix::errno::Errno;
3 use nix::sys::fanotify::{
4 EventFFlags, Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags,
5 Response,
6 };
7 use std::fs::{read_link, read_to_string, File, OpenOptions};
8 use std::io::ErrorKind;
9 use std::io::{Read, Write};
10 use std::os::fd::AsRawFd;
11 use std::thread;
12
13 #[test]
14 /// Run fanotify tests sequentially to avoid tmp files races
test_fanotify()15 pub fn test_fanotify() {
16 require_capability!("test_fanotify", CAP_SYS_ADMIN);
17
18 test_fanotify_notifications();
19 test_fanotify_responses();
20 test_fanotify_overflow();
21 }
22
test_fanotify_notifications()23 fn test_fanotify_notifications() {
24 let group =
25 Fanotify::init(InitFlags::FAN_CLASS_NOTIF, EventFFlags::O_RDONLY)
26 .unwrap();
27 let tempdir = tempfile::tempdir().unwrap();
28 let tempfile = tempdir.path().join("test");
29 OpenOptions::new()
30 .write(true)
31 .create_new(true)
32 .open(&tempfile)
33 .unwrap();
34
35 group
36 .mark(
37 MarkFlags::FAN_MARK_ADD,
38 MaskFlags::FAN_OPEN | MaskFlags::FAN_MODIFY | MaskFlags::FAN_CLOSE,
39 None,
40 Some(&tempfile),
41 )
42 .unwrap();
43
44 // modify test file
45 {
46 let mut f = OpenOptions::new().write(true).open(&tempfile).unwrap();
47 f.write_all(b"hello").unwrap();
48 }
49
50 let mut events = group.read_events().unwrap();
51 assert_eq!(events.len(), 1, "should have read exactly one event");
52 let event = events.pop().unwrap();
53 assert!(event.check_version());
54 assert_eq!(
55 event.mask(),
56 MaskFlags::FAN_OPEN
57 | MaskFlags::FAN_MODIFY
58 | MaskFlags::FAN_CLOSE_WRITE
59 );
60 let fd_opt = event.fd();
61 let fd = fd_opt.as_ref().unwrap();
62 let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap();
63 assert_eq!(path, tempfile);
64
65 // read test file
66 {
67 let mut f = File::open(&tempfile).unwrap();
68 let mut s = String::new();
69 f.read_to_string(&mut s).unwrap();
70 }
71
72 let mut events = group.read_events().unwrap();
73 assert_eq!(events.len(), 1, "should have read exactly one event");
74 let event = events.pop().unwrap();
75 assert!(event.check_version());
76 assert_eq!(
77 event.mask(),
78 MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_NOWRITE
79 );
80 let fd_opt = event.fd();
81 let fd = fd_opt.as_ref().unwrap();
82 let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap();
83 assert_eq!(path, tempfile);
84 }
85
test_fanotify_responses()86 fn test_fanotify_responses() {
87 let group =
88 Fanotify::init(InitFlags::FAN_CLASS_CONTENT, EventFFlags::O_RDONLY)
89 .unwrap();
90 let tempdir = tempfile::tempdir().unwrap();
91 let tempfile = tempdir.path().join("test");
92 OpenOptions::new()
93 .write(true)
94 .create_new(true)
95 .open(&tempfile)
96 .unwrap();
97
98 group
99 .mark(
100 MarkFlags::FAN_MARK_ADD,
101 MaskFlags::FAN_OPEN_PERM,
102 None,
103 Some(&tempfile),
104 )
105 .unwrap();
106
107 let file_thread = thread::spawn({
108 let tempfile = tempfile.clone();
109
110 move || {
111 // first open, should fail
112 let Err(e) = File::open(&tempfile) else {
113 panic!("The first open should fail");
114 };
115 assert_eq!(e.kind(), ErrorKind::PermissionDenied);
116
117 // second open, should succeed
118 File::open(&tempfile).unwrap();
119 }
120 });
121
122 // Deny the first open try
123 let mut events = group.read_events().unwrap();
124 assert_eq!(events.len(), 1, "should have read exactly one event");
125 let event = events.pop().unwrap();
126 assert!(event.check_version());
127 assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM);
128 let fd_opt = event.fd();
129 let fd = fd_opt.as_ref().unwrap();
130 let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap();
131 assert_eq!(path, tempfile);
132 group
133 .write_response(FanotifyResponse::new(*fd, Response::FAN_DENY))
134 .unwrap();
135
136 // Allow the second open try
137 let mut events = group.read_events().unwrap();
138 assert_eq!(events.len(), 1, "should have read exactly one event");
139 let event = events.pop().unwrap();
140 assert!(event.check_version());
141 assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM);
142 let fd_opt = event.fd();
143 let fd = fd_opt.as_ref().unwrap();
144 let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap();
145 assert_eq!(path, tempfile);
146 group
147 .write_response(FanotifyResponse::new(*fd, Response::FAN_ALLOW))
148 .unwrap();
149
150 file_thread.join().unwrap();
151 }
152
test_fanotify_overflow()153 fn test_fanotify_overflow() {
154 let max_events: usize =
155 read_to_string("/proc/sys/fs/fanotify/max_queued_events")
156 .unwrap()
157 .trim()
158 .parse()
159 .unwrap();
160
161 // make sure the kernel is configured with the default value,
162 // just so this test doesn't run forever
163 assert_eq!(max_events, 16384);
164
165 let group = Fanotify::init(
166 InitFlags::FAN_CLASS_NOTIF
167 | InitFlags::FAN_REPORT_TID
168 | InitFlags::FAN_NONBLOCK,
169 EventFFlags::O_RDONLY,
170 )
171 .unwrap();
172 let tempdir = tempfile::tempdir().unwrap();
173 let tempfile = tempdir.path().join("test");
174
175 OpenOptions::new()
176 .write(true)
177 .create_new(true)
178 .open(&tempfile)
179 .unwrap();
180
181 group
182 .mark(
183 MarkFlags::FAN_MARK_ADD,
184 MaskFlags::FAN_OPEN,
185 None,
186 Some(&tempfile),
187 )
188 .unwrap();
189
190 thread::scope(|s| {
191 // perform 10 more events to demonstrate some will be dropped
192 for _ in 0..(max_events + 10) {
193 s.spawn(|| {
194 File::open(&tempfile).unwrap();
195 });
196 }
197 });
198
199 // flush the queue until it's empty
200 let mut n = 0;
201 let mut last_event = None;
202 loop {
203 match group.read_events() {
204 Ok(events) => {
205 n += events.len();
206 if let Some(event) = events.last() {
207 last_event = Some(event.mask());
208 }
209 }
210 Err(e) if e == Errno::EWOULDBLOCK => break,
211 Err(e) => panic!("{e:?}"),
212 }
213 }
214
215 // make sure we read all we expected.
216 // the +1 is for the overflow event.
217 assert_eq!(n, max_events + 1);
218 assert_eq!(last_event, Some(MaskFlags::FAN_Q_OVERFLOW));
219 }
220