1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2017 Richard Palethorpe <rpalethorpe@suse.com>
4 */
5 /*
6 * Perform a small read on every file in a directory tree.
7 *
8 * Useful for testing file systems like proc, sysfs and debugfs or anything
9 * which exposes a file like API so long as it respects O_NONBLOCK. This test
10 * is not concerned if a particular file in one of these file systems conforms
11 * exactly to its specific documented behavior. Just whether reading from that
12 * file causes a serious error such as a NULL pointer dereference.
13 *
14 * It is not required to run this as root, but test coverage will be much
15 * higher with full privileges.
16 *
17 * The reads are preformed by worker processes which are given file paths by a
18 * single parent process. The parent process recursively scans a given
19 * directory and passes the file paths it finds to the child processes using a
20 * queue structure stored in shared memory.
21 *
22 * This allows the file system and individual files to be accessed in
23 * parallel. Passing the 'reads' parameter (-r) will encourage this. The
24 * number of worker processes is based on the number of available
25 * processors. However this is limited by default to 15 to avoid this becoming
26 * an IPC stress test on systems with large numbers of weak cores. This can be
27 * overridden with the 'w' parameters.
28 */
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <lapi/fnmatch.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <dirent.h>
36 #include <errno.h>
37 #include <unistd.h>
38 #include <string.h>
39 #include <limits.h>
40 #include <fnmatch.h>
41 #include <semaphore.h>
42 #include <ctype.h>
43 #include <pwd.h>
44 #include <grp.h>
45
46 #include "tst_test.h"
47
48 #define QUEUE_SIZE 16384
49 #define BUFFER_SIZE 1024
50 #define MAX_PATH 4096
51 #define MAX_DISPLAY 40
52
53 struct queue {
54 sem_t sem;
55 int front;
56 int back;
57 char data[QUEUE_SIZE];
58 };
59
60 struct worker {
61 pid_t pid;
62 struct queue *q;
63 };
64
65 enum dent_action {
66 DA_UNKNOWN,
67 DA_IGNORE,
68 DA_READ,
69 DA_VISIT,
70 };
71
72 static char *verbose;
73 static char *quiet;
74 static char *root_dir;
75 static char *str_reads;
76 static int reads = 1;
77 static char *str_worker_count;
78 static long worker_count;
79 static char *str_max_workers;
80 static long max_workers = 15;
81 static struct worker *workers;
82 static char *drop_privs;
83
84 static char *blacklist[] = {
85 NULL, /* reserved for -e parameter */
86 "/sys/power/wakeup_count",
87 "/sys/kernel/debug/*",
88 };
89
90 static struct tst_option options[] = {
91 {"v", &verbose,
92 "-v Print information about successful reads."},
93 {"q", &quiet,
94 "-q Don't print file read or open errors."},
95 {"d:", &root_dir,
96 "-d path Path to the directory to read from, defaults to /sys."},
97 {"e:", &blacklist[0],
98 "-e pattern Ignore files which match an 'extended' pattern, see fnmatch(3)."},
99 {"r:", &str_reads,
100 "-r count The number of times to schedule a file for reading."},
101 {"w:", &str_max_workers,
102 "-w count Set the worker count limit, the default is 15."},
103 {"W:", &str_worker_count,
104 "-W count Override the worker count. Ignores (-w) and the processor count."},
105 {"p", &drop_privs,
106 "-p Drop privileges; switch to the nobody user."},
107 {NULL, NULL, NULL}
108 };
109
queue_pop(struct queue * q,char * buf)110 static int queue_pop(struct queue *q, char *buf)
111 {
112 int i = q->front, j = 0;
113
114 sem_wait(&q->sem);
115
116 if (!q->data[i])
117 return 0;
118
119 while (q->data[i]) {
120 buf[j] = q->data[i];
121
122 if (++j >= BUFFER_SIZE - 1)
123 tst_brk(TBROK, "Buffer is too small for path");
124
125 i = (i + 1) % QUEUE_SIZE;
126 }
127
128 buf[j] = '\0';
129 tst_atomic_store((i + 1) % QUEUE_SIZE, &q->front);
130
131 return 1;
132 }
133
queue_push(struct queue * q,const char * buf)134 static int queue_push(struct queue *q, const char *buf)
135 {
136 int i = q->back, j = 0;
137 int front = tst_atomic_load(&q->front);
138
139 do {
140 q->data[i] = buf[j];
141
142 i = (i + 1) % QUEUE_SIZE;
143
144 if (i == front)
145 return 0;
146
147 } while (buf[j++]);
148
149 q->back = i;
150 sem_post(&q->sem);
151
152 return 1;
153 }
154
queue_init(void)155 static struct queue *queue_init(void)
156 {
157 struct queue *q = SAFE_MMAP(NULL, sizeof(*q),
158 PROT_READ | PROT_WRITE,
159 MAP_SHARED | MAP_ANONYMOUS,
160 0, 0);
161
162 sem_init(&q->sem, 1, 0);
163 q->front = 0;
164 q->back = 0;
165
166 return q;
167 }
168
queue_destroy(struct queue * q,int is_worker)169 static void queue_destroy(struct queue *q, int is_worker)
170 {
171 if (is_worker)
172 sem_destroy(&q->sem);
173
174 SAFE_MUNMAP(q, sizeof(*q));
175 }
176
sanitize_str(char * buf,ssize_t count)177 static void sanitize_str(char *buf, ssize_t count)
178 {
179 int i;
180
181 for (i = 0; i < MIN(count, MAX_DISPLAY); i++)
182 if (!isprint(buf[i]))
183 buf[i] = ' ';
184
185 if (count <= MAX_DISPLAY)
186 buf[count] = '\0';
187 else
188 strcpy(buf + MAX_DISPLAY, "...");
189 }
190
is_blacklisted(const char * path)191 static int is_blacklisted(const char *path)
192 {
193 unsigned int i;
194
195 for (i = 0; i < ARRAY_SIZE(blacklist); i++) {
196 if (blacklist[i] && !fnmatch(blacklist[i], path, FNM_EXTMATCH)) {
197 if (verbose)
198 tst_res(TINFO, "Ignoring %s", path);
199 return 1;
200 }
201 }
202
203 return 0;
204 }
205
read_test(const char * path)206 static void read_test(const char *path)
207 {
208 char buf[BUFFER_SIZE];
209 int fd;
210 ssize_t count;
211
212 if (is_blacklisted(path))
213 return;
214
215 if (verbose)
216 tst_res(TINFO, "%s(%s)", __func__, path);
217
218 fd = open(path, O_RDONLY | O_NONBLOCK);
219 if (fd < 0) {
220 if (!quiet)
221 tst_res(TINFO | TERRNO, "open(%s)", path);
222 return;
223 }
224
225 count = read(fd, buf, sizeof(buf) - 1);
226 if (count > 0 && verbose) {
227 sanitize_str(buf, count);
228 tst_res(TINFO, "read(%s, buf) = %zi, buf = %s",
229 path, count, buf);
230 } else if (!count && verbose) {
231 tst_res(TINFO, "read(%s) = EOF", path);
232 } else if (count < 0 && !quiet) {
233 tst_res(TINFO | TERRNO, "read(%s)", path);
234 }
235
236 SAFE_CLOSE(fd);
237 }
238
worker_run(struct worker * self)239 static int worker_run(struct worker *self)
240 {
241 char buf[BUFFER_SIZE];
242 struct sigaction term_sa = {
243 .sa_handler = SIG_IGN,
244 .sa_flags = 0,
245 };
246 struct queue *q = self->q;
247
248 sigaction(SIGTTIN, &term_sa, NULL);
249
250 while (1) {
251 if (!queue_pop(q, buf))
252 break;
253
254 read_test(buf);
255 }
256
257 queue_destroy(q, 1);
258 tst_flush();
259 return 0;
260 }
261
maybe_drop_privs(void)262 static void maybe_drop_privs(void)
263 {
264 struct passwd *nobody;
265
266 if (!drop_privs)
267 return;
268
269 TEST(setgroups(0, NULL));
270 if (TST_RET < 0 && TST_ERR != EPERM) {
271 tst_brk(TBROK | TTERRNO,
272 "Failed to clear suplementary group set");
273 }
274
275 nobody = SAFE_GETPWNAM("nobody");
276
277 TEST(setgid(nobody->pw_gid));
278 if (TST_RET < 0 && TST_ERR != EPERM)
279 tst_brk(TBROK | TTERRNO, "Failed to use nobody gid");
280
281 TEST(setuid(nobody->pw_uid));
282 if (TST_RET < 0 && TST_ERR != EPERM)
283 tst_brk(TBROK | TTERRNO, "Failed to use nobody uid");
284 }
285
spawn_workers(void)286 static void spawn_workers(void)
287 {
288 int i;
289 struct worker *wa = workers;
290
291 memset(workers, 0, worker_count * sizeof(*workers));
292
293 for (i = 0; i < worker_count; i++) {
294 wa[i].q = queue_init();
295 wa[i].pid = SAFE_FORK();
296 if (!wa[i].pid) {
297 maybe_drop_privs();
298 exit(worker_run(wa + i));
299 }
300 }
301 }
302
work_push_retry(int worker,const char * buf)303 static void work_push_retry(int worker, const char *buf)
304 {
305 int i, ret, worker_min, worker_max, usleep_time = 100;
306
307 if (worker < 0) {
308 /* pick any, try -worker first */
309 worker_min = worker * (-1);
310 worker_max = worker_count;
311 } else {
312 /* keep trying worker */
313 worker_min = worker;
314 worker_max = worker + 1;
315 }
316 i = worker_min;
317
318 for (;;) {
319 ret = queue_push(workers[i].q, buf);
320 if (ret == 1)
321 break;
322
323 if (++i >= worker_max) {
324 i = worker_min;
325 if (usleep_time < 100000)
326 usleep_time *= 2;
327 usleep(usleep_time);
328 }
329 }
330 }
331
stop_workers(void)332 static void stop_workers(void)
333 {
334 const char stop_code[1] = { '\0' };
335 int i;
336
337 if (!workers)
338 return;
339
340 for (i = 0; i < worker_count; i++) {
341 if (workers[i].q)
342 work_push_retry(i, stop_code);
343 }
344
345 for (i = 0; i < worker_count; i++) {
346 if (workers[i].q) {
347 queue_destroy(workers[i].q, 0);
348 workers[i].q = 0;
349 }
350 }
351 }
352
rep_sched_work(const char * path,int rep)353 static void rep_sched_work(const char *path, int rep)
354 {
355 int i, j;
356
357 for (i = j = 0; i < rep; i++, j++) {
358 if (j >= worker_count)
359 j = 0;
360 work_push_retry(-j, path);
361 }
362 }
363
setup(void)364 static void setup(void)
365 {
366 if (tst_parse_int(str_reads, &reads, 1, INT_MAX))
367 tst_brk(TBROK,
368 "Invalid reads (-r) argument: '%s'", str_reads);
369
370 if (tst_parse_long(str_max_workers, &max_workers, 1, LONG_MAX)) {
371 tst_brk(TBROK,
372 "Invalid max workers (-w) argument: '%s'",
373 str_max_workers);
374 }
375
376 if (tst_parse_long(str_worker_count, &worker_count, 1, LONG_MAX)) {
377 tst_brk(TBROK,
378 "Invalid worker count (-W) argument: '%s'",
379 str_worker_count);
380 }
381
382 if (!root_dir)
383 tst_brk(TBROK, "The directory argument (-d) is required");
384
385 if (!worker_count)
386 worker_count = MIN(MAX(tst_ncpus() - 1, 1), max_workers);
387 workers = SAFE_MALLOC(worker_count * sizeof(*workers));
388 }
389
cleanup(void)390 static void cleanup(void)
391 {
392 stop_workers();
393 free(workers);
394 }
395
visit_dir(const char * path)396 static void visit_dir(const char *path)
397 {
398 DIR *dir;
399 struct dirent *dent;
400 struct stat dent_st;
401 char dent_path[MAX_PATH];
402 enum dent_action act;
403
404 dir = opendir(path);
405 if (!dir) {
406 tst_res(TINFO | TERRNO, "opendir(%s)", path);
407 return;
408 }
409
410 while (1) {
411 errno = 0;
412 dent = readdir(dir);
413 if (!dent && errno) {
414 tst_res(TINFO | TERRNO, "readdir(%s)", path);
415 break;
416 } else if (!dent) {
417 break;
418 }
419
420 if (!strcmp(dent->d_name, ".") ||
421 !strcmp(dent->d_name, ".."))
422 continue;
423
424 if (dent->d_type == DT_DIR)
425 act = DA_VISIT;
426 else if (dent->d_type == DT_LNK)
427 act = DA_IGNORE;
428 else if (dent->d_type == DT_UNKNOWN)
429 act = DA_UNKNOWN;
430 else
431 act = DA_READ;
432
433 snprintf(dent_path, MAX_PATH,
434 "%s/%s", path, dent->d_name);
435
436 if (act == DA_UNKNOWN) {
437 if (lstat(dent_path, &dent_st))
438 tst_res(TINFO | TERRNO, "lstat(%s)", path);
439 else if ((dent_st.st_mode & S_IFMT) == S_IFDIR)
440 act = DA_VISIT;
441 else if ((dent_st.st_mode & S_IFMT) == S_IFLNK)
442 act = DA_IGNORE;
443 else
444 act = DA_READ;
445 }
446
447 if (act == DA_VISIT)
448 visit_dir(dent_path);
449 else if (act == DA_READ)
450 rep_sched_work(dent_path, reads);
451 }
452
453 if (closedir(dir))
454 tst_res(TINFO | TERRNO, "closedir(%s)", path);
455 }
456
run(void)457 static void run(void)
458 {
459 spawn_workers();
460 visit_dir(root_dir);
461 stop_workers();
462
463 tst_reap_children();
464 tst_res(TPASS, "Finished reading files");
465 }
466
467 static struct tst_test test = {
468 .options = options,
469 .setup = setup,
470 .cleanup = cleanup,
471 .test_all = run,
472 .forks_child = 1,
473 };
474
475