1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2013 SUSE. All Rights Reserved.
4 *
5 * Started by Jan Kara <jack@suse.cz>
6 */
7
8 /*\
9 * [Description]
10 * Check various fanotify special flags.
11 */
12
13 #define _GNU_SOURCE
14 #include "config.h"
15
16 #include <stdio.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19 #include <errno.h>
20 #include <string.h>
21 #include <sys/syscall.h>
22 #include "tst_test.h"
23
24 #ifdef HAVE_SYS_FANOTIFY_H
25 #include "fanotify.h"
26
27 #define EVENT_MAX 1024
28 /* size of the event structure, not counting name */
29 #define EVENT_SIZE (sizeof (struct fanotify_event_metadata))
30 /* reasonable guess as to size of 1024 events */
31 #define EVENT_BUF_LEN (EVENT_MAX * EVENT_SIZE)
32
33 #define BUF_SIZE 256
34 #define TST_TOTAL 9
35
36 static char fname[BUF_SIZE];
37 static char sname[BUF_SIZE];
38 static char dir[BUF_SIZE];
39 static int fd_notify;
40
41 static int len;
42 static char event_buf[EVENT_BUF_LEN];
43
expect_str_fail(int expect)44 static char *expect_str_fail(int expect)
45 {
46 if (expect == 0)
47 return "failed";
48 return "unexpectedly succeeded";
49 }
50
expect_str_pass(int expect)51 static char *expect_str_pass(int expect)
52 {
53 if (expect == 0)
54 return "succeeded";
55 return "failed";
56 }
57
check_mark(char * file,unsigned long long flag,char * flagstr,int expect,void (* test_event)(char *))58 static void check_mark(char *file, unsigned long long flag, char *flagstr,
59 int expect, void (*test_event)(char *))
60 {
61 if (fanotify_mark(fd_notify, FAN_MARK_ADD | flag, FAN_OPEN, AT_FDCWD,
62 file) != expect) {
63 tst_res(TFAIL,
64 "fanotify_mark (%d, FAN_MARK_ADD | %s, FAN_OPEN, "
65 "AT_FDCWD, '%s') %s", fd_notify, flagstr, file,
66 expect_str_fail(expect));
67 } else {
68 tst_res(TPASS,
69 "fanotify_mark (%d, FAN_MARK_ADD | %s, FAN_OPEN, "
70 "AT_FDCWD, '%s') %s", fd_notify, flagstr, file,
71 expect_str_pass(expect));
72
73 /* If we expected failure there's nothing to clean up */
74 if (expect == -1)
75 return;
76
77 if (test_event)
78 test_event(file);
79
80 SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_REMOVE | flag,
81 FAN_OPEN, AT_FDCWD, file);
82 }
83 }
84
85 #define CHECK_MARK(file, flag, expect, func) check_mark(file, flag, #flag, expect, func)
86
do_open(char * file,int flag)87 static void do_open(char *file, int flag)
88 {
89 int fd;
90
91 fd = SAFE_OPEN(file, O_RDONLY | flag);
92 SAFE_CLOSE(fd);
93 }
94
open_file(char * file)95 static void open_file(char *file)
96 {
97 do_open(file, 0);
98 }
99
open_dir(char * file)100 static void open_dir(char *file)
101 {
102 do_open(file, O_DIRECTORY);
103 }
104
verify_event(int mask)105 static void verify_event(int mask)
106 {
107 int ret;
108 struct fanotify_event_metadata *event;
109 struct stat st;
110
111 /* Read the event */
112 ret = SAFE_READ(0, fd_notify, event_buf + len,
113 EVENT_BUF_LEN - len);
114 event = (struct fanotify_event_metadata *)&event_buf[len];
115 len += ret;
116
117 if (event->mask != FAN_OPEN) {
118 tst_res(TFAIL, "got unexpected event %llx",
119 (unsigned long long)event->mask);
120 } else if (fstat(event->fd, &st) < 0) {
121 tst_res(TFAIL, "failed to stat event->fd (%s)",
122 strerror(errno));
123 } else if ((int)(st.st_mode & S_IFMT) != mask) {
124 tst_res(TFAIL, "event->fd points to object of different type "
125 "(%o != %o)", st.st_mode & S_IFMT, mask);
126 } else {
127 tst_res(TPASS, "event generated properly for type %o", mask);
128 }
129 if (event->fd != FAN_NOFD)
130 SAFE_CLOSE(event->fd);
131 }
132
do_open_test(char * file,int flag,int mask)133 static void do_open_test(char *file, int flag, int mask)
134 {
135 do_open(file, flag);
136
137 verify_event(mask);
138 }
139
test_open_file(char * file)140 static void test_open_file(char *file)
141 {
142 do_open_test(file, 0, S_IFREG);
143 }
144
verify_no_event(void)145 static void verify_no_event(void)
146 {
147 int ret;
148
149 ret = read(fd_notify, event_buf + len, EVENT_BUF_LEN - len);
150 if (ret != -1) {
151 struct fanotify_event_metadata *event;
152
153 event = (struct fanotify_event_metadata *)&event_buf[len];
154 tst_res(TFAIL, "seen unexpected event (mask %llx)",
155 (unsigned long long)event->mask);
156 /* Cleanup fd from the event */
157 if (event->fd != FAN_NOFD)
158 SAFE_CLOSE(event->fd);
159 } else if (errno != EAGAIN) {
160 tst_res(TFAIL | TERRNO, "read(%d, buf, %zu) failed", fd_notify,
161 EVENT_BUF_LEN);
162 } else {
163 tst_res(TPASS, "No event as expected");
164 }
165 }
166
test_open_symlink(char * file)167 static void test_open_symlink(char *file)
168 {
169 /* Since mark is on a symlink, no event should be generated by opening a file */
170 do_open(file, 0);
171 verify_no_event();
172 }
173
test01(void)174 void test01(void)
175 {
176 /* Check ONLYDIR on a directory */
177 CHECK_MARK(".", FAN_MARK_ONLYDIR, 0, NULL);
178
179 /* Check ONLYDIR without a directory */
180 CHECK_MARK(fname, FAN_MARK_ONLYDIR, -1, NULL);
181
182 /* Check DONT_FOLLOW for a symlink */
183 CHECK_MARK(sname, FAN_MARK_DONT_FOLLOW, 0, test_open_symlink);
184
185 /* Check without DONT_FOLLOW for a symlink */
186 CHECK_MARK(sname, 0, 0, test_open_file);
187
188 /* Verify FAN_MARK_FLUSH destroys all inode marks */
189 SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD, FAN_OPEN, AT_FDCWD, fname);
190 SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD, FAN_OPEN | FAN_ONDIR,
191 AT_FDCWD, dir);
192 open_file(fname);
193 verify_event(S_IFREG);
194 open_dir(dir);
195 verify_event(S_IFDIR);
196 SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_FLUSH, 0, AT_FDCWD, ".");
197
198 open_dir(dir);
199 verify_no_event();
200 }
201
setup(void)202 static void setup(void)
203 {
204 int fd;
205
206 sprintf(fname, "fname_%d", getpid());
207 fd = SAFE_OPEN(fname, O_RDWR | O_CREAT, 0644);
208 SAFE_CLOSE(fd);
209
210 sprintf(sname, "symlink_%d", getpid());
211 SAFE_SYMLINK(fname, sname);
212
213 sprintf(dir, "dir_%d", getpid());
214 SAFE_MKDIR(dir, 0755);
215
216 fd_notify = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | FAN_NONBLOCK,
217 O_RDONLY);
218 }
219
cleanup(void)220 static void cleanup(void)
221 {
222 if (fd_notify > 0)
223 SAFE_CLOSE(fd_notify);
224 }
225
226 static struct tst_test test = {
227 .test_all = test01,
228 .setup = setup,
229 .cleanup = cleanup,
230 .needs_tmpdir = 1,
231 .needs_root = 1
232 };
233
234 #else
235 TST_TEST_TCONF("system doesn't have required fanotify support");
236 #endif
237