1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2017-2022 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
9 * anything which exposes a file like API. This test is not concerned
10 * if a particular file in one of these file systems conforms exactly
11 * 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 <signal.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/wait.h>
33 #include <fnmatch.h>
34 #include <lapi/fnmatch.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <dirent.h>
39 #include <errno.h>
40 #include <unistd.h>
41 #include <string.h>
42 #include <limits.h>
43 #include <semaphore.h>
44 #include <ctype.h>
45 #include <pwd.h>
46 #include <grp.h>
47
48 #include "tst_atomic.h"
49 #include "tst_safe_clocks.h"
50 #include "tst_test.h"
51 #include "tst_timer.h"
52
53 #define QUEUE_SIZE 16384
54 #define BUFFER_SIZE 1024
55 #define MAX_PATH 4096
56 #define MAX_DISPLAY 40
57
58 struct queue {
59 sem_t sem;
60 int front;
61 int back;
62 char data[QUEUE_SIZE];
63 char popped[BUFFER_SIZE];
64 };
65
66 struct worker {
67 int i;
68 pid_t pid;
69 struct queue *q;
70 int last_seen;
71 unsigned int kill_sent:1;
72 };
73
74 enum dent_action {
75 DA_UNKNOWN,
76 DA_IGNORE,
77 DA_READ,
78 DA_VISIT,
79 };
80
81 static char *verbose;
82 static char *quiet;
83 static char *root_dir;
84 static char *str_reads;
85 static int reads = 1;
86 static char *str_worker_count;
87 static long worker_count;
88 static char *str_max_workers;
89 static long max_workers = 15;
90 static struct worker *workers;
91 static char *drop_privs;
92 static char *str_worker_timeout;
93 static int worker_timeout;
94 static int timeout_warnings_left = 15;
95
96 static char *blacklist[] = {
97 "/reserved/", /* reserved for -e parameter */
98 "/sys/kernel/debug/*",
99 "/sys/devices/platform/*/eeprom",
100 "/sys/devices/platform/*/nvmem",
101 "/sys/*/cpu??*(?)/*", /* cpu* entries with 2 or more digits */
102 NULL
103 };
104
105 static char *ratelimit_list[] = {
106 "/sys/devices/*/tpm*",
107 NULL,
108 };
109
110 static long long epoch;
111
atomic_timestamp(void)112 static int atomic_timestamp(void)
113 {
114 struct timespec now;
115
116 SAFE_CLOCK_GETTIME(CLOCK_MONOTONIC_RAW, &now);
117
118 return tst_timespec_to_us(now) - epoch;
119 }
120
queue_pop(struct queue * q)121 static int queue_pop(struct queue *q)
122 {
123 int i = q->front, j = 0;
124
125 sem_wait(&q->sem);
126
127 if (!q->data[i])
128 return 0;
129
130 while (q->data[i]) {
131 q->popped[j] = q->data[i];
132
133 if (++j >= BUFFER_SIZE - 1)
134 tst_brk(TBROK, "Buffer is too small for path");
135
136 i = (i + 1) % QUEUE_SIZE;
137 }
138
139 q->popped[j] = '\0';
140 tst_atomic_store((i + 1) % QUEUE_SIZE, &q->front);
141
142 return 1;
143 }
144
queue_push(struct queue * q,const char * buf)145 static int queue_push(struct queue *q, const char *buf)
146 {
147 int i = q->back, j = 0;
148 int front = tst_atomic_load(&q->front);
149
150 do {
151 q->data[i] = buf[j];
152
153 i = (i + 1) % QUEUE_SIZE;
154
155 if (i == front)
156 return 0;
157
158 } while (buf[j++]);
159
160 q->back = i;
161 sem_post(&q->sem);
162
163 return 1;
164 }
165
queue_init(void)166 static struct queue *queue_init(void)
167 {
168 struct queue *q = SAFE_MMAP(NULL, sizeof(*q),
169 PROT_READ | PROT_WRITE,
170 MAP_SHARED | MAP_ANONYMOUS,
171 0, 0);
172
173 sem_init(&q->sem, 1, 0);
174 q->front = 0;
175 q->back = 0;
176
177 return q;
178 }
179
queue_destroy(struct queue * q,int is_worker)180 static void queue_destroy(struct queue *q, int is_worker)
181 {
182 if (is_worker)
183 sem_destroy(&q->sem);
184
185 SAFE_MUNMAP(q, sizeof(*q));
186 }
187
sanitize_str(char * buf,ssize_t count)188 static void sanitize_str(char *buf, ssize_t count)
189 {
190 int i;
191
192 for (i = 0; i < MIN(count, (ssize_t)MAX_DISPLAY); i++)
193 if (!isprint(buf[i]))
194 buf[i] = ' ';
195
196 if (count <= MAX_DISPLAY)
197 buf[count] = '\0';
198 else
199 strcpy(buf + MAX_DISPLAY, "...");
200 }
201
is_onlist(const char * path,char * list[])202 static int is_onlist(const char *path, char *list[])
203 {
204 unsigned int i = 0;
205
206 while (1) {
207 const char *pattern = list[i++];
208
209 if (!pattern)
210 break;
211 if (!fnmatch(pattern, path, FNM_EXTMATCH))
212 return 1;
213 }
214
215 return 0;
216
217 }
218
is_blacklisted(const char * path)219 static int is_blacklisted(const char *path)
220 {
221 int ret;
222
223 ret = is_onlist(path, blacklist);
224 if (ret && verbose)
225 tst_res(TINFO, "Ignoring %s", path);
226
227 return ret;
228 }
229
is_ratelimitted(const char * path)230 static int is_ratelimitted(const char *path)
231 {
232 int ret;
233
234 ret = is_onlist(path, ratelimit_list);
235 if (ret && verbose)
236 tst_res(TINFO, "Limiting to single worker %s", path);
237
238 return ret;
239 }
240
worker_heartbeat(const int worker)241 static void worker_heartbeat(const int worker)
242 {
243 tst_atomic_store(atomic_timestamp(), &workers[worker].last_seen);
244 }
245
worker_elapsed(const int worker)246 static int worker_elapsed(const int worker)
247 {
248 struct worker *const w = workers + worker;
249
250 return atomic_timestamp() - tst_atomic_load(&w->last_seen);
251 }
252
worker_ttl(const int worker)253 static int worker_ttl(const int worker)
254 {
255 return MAX(0, worker_timeout - worker_elapsed(worker));
256 }
257
read_test(const int worker,const char * const path)258 static void read_test(const int worker, const char *const path)
259 {
260 char buf[BUFFER_SIZE];
261 int fd;
262 ssize_t count;
263 const pid_t pid = workers[worker].pid;
264 int elapsed;
265
266 if (is_blacklisted(path))
267 return;
268
269 if (verbose)
270 tst_res(TINFO, "Worker %d: %s(%s)", pid, __func__, path);
271
272 fd = open(path, O_RDONLY | O_NONBLOCK);
273 if (fd < 0) {
274 if (!quiet) {
275 tst_res(TINFO | TERRNO, "Worker %d (%d): open(%s)",
276 pid, worker, path);
277 }
278 return;
279 }
280
281 worker_heartbeat(worker);
282 count = read(fd, buf, sizeof(buf) - 1);
283 elapsed = worker_elapsed(worker);
284
285 if (count > 0 && verbose) {
286 sanitize_str(buf, count);
287 tst_res(TINFO,
288 "Worker %d (%d): read(%s, buf) = %zi, buf = %s, elapsed = %dus",
289 pid, worker, path, count, buf, elapsed);
290 } else if (!count && verbose) {
291 tst_res(TINFO,
292 "Worker %d (%d): read(%s) = EOF, elapsed = %dus",
293 pid, worker, path, elapsed);
294 } else if (count < 0 && !quiet) {
295 tst_res(TINFO | TERRNO,
296 "Worker %d (%d): read(%s), elapsed = %dus",
297 pid, worker, path, elapsed);
298 }
299
300 SAFE_CLOSE(fd);
301 }
302
maybe_drop_privs(void)303 static void maybe_drop_privs(void)
304 {
305 struct passwd *nobody;
306
307 if (!drop_privs)
308 return;
309
310 TEST(setgroups(0, NULL));
311 if (TST_RET < 0 && TST_ERR != EPERM) {
312 tst_brk(TBROK | TTERRNO,
313 "Failed to clear suplementary group set");
314 }
315
316 nobody = SAFE_GETPWNAM("nobody");
317
318 TEST(setgid(nobody->pw_gid));
319 if (TST_RET < 0 && TST_ERR != EPERM)
320 tst_brk(TBROK | TTERRNO, "Failed to use nobody gid");
321
322 TEST(setuid(nobody->pw_uid));
323 if (TST_RET < 0 && TST_ERR != EPERM)
324 tst_brk(TBROK | TTERRNO, "Failed to use nobody uid");
325 }
326
worker_run(int worker)327 static int worker_run(int worker)
328 {
329 struct sigaction term_sa = {
330 .sa_handler = SIG_IGN,
331 .sa_flags = 0,
332 };
333 struct worker *const self = workers + worker;
334 struct queue *q = self->q;
335
336 sigaction(SIGTTIN, &term_sa, NULL);
337 maybe_drop_privs();
338 self->pid = getpid();
339
340 if (!worker_ttl(self->i)) {
341 tst_brk(TBROK,
342 "Worker timeout is too short; restarts take >%dus",
343 worker_elapsed(self->i));
344 }
345
346 while (1) {
347 worker_heartbeat(worker);
348
349 if (!queue_pop(q))
350 break;
351
352 read_test(worker, q->popped);
353 }
354
355 queue_destroy(q, 1);
356 tst_flush();
357 return 0;
358 }
359
spawn_workers(void)360 static void spawn_workers(void)
361 {
362 int i;
363 struct worker *wa = workers;
364
365 memset(workers, 0, worker_count * sizeof(*workers));
366
367 for (i = 0; i < worker_count; i++) {
368 wa[i].i = i;
369 wa[i].q = queue_init();
370 wa[i].last_seen = atomic_timestamp();
371 wa[i].pid = SAFE_FORK();
372 if (!wa[i].pid)
373 exit(worker_run(i));
374 }
375 }
376
restart_worker(const int worker)377 static void restart_worker(const int worker)
378 {
379 struct worker *const w = workers + worker;
380 int wstatus, ret, i, q_len;
381
382 if (!w->kill_sent) {
383 SAFE_KILL(w->pid, SIGKILL);
384 w->kill_sent = 1;
385 worker_heartbeat(worker);
386 }
387
388 ret = waitpid(w->pid, &wstatus, WNOHANG);
389
390 if (!ret) {
391 if (worker_ttl(worker) > 0)
392 return;
393
394 if (!quiet || timeout_warnings_left) {
395 tst_res(TINFO,
396 "Worker %d (%d): Timeout waiting after kill",
397 w->pid, worker);
398 }
399 } else if (ret != w->pid) {
400 tst_brk(TBROK | TERRNO, "Worker %d (%d): waitpid = %d",
401 w->pid, worker, ret);
402 }
403
404 w->kill_sent = 0;
405
406 if (!w->q->popped[0]) {
407 tst_brk(TBROK,
408 "Worker %d (%d): Timed out, but doesn't appear to be reading anything",
409 w->pid, worker);
410 }
411
412 if (!quiet || timeout_warnings_left) {
413 tst_res(TINFO, "Worker %d (%d): Last popped '%s'",
414 w->pid, worker, w->q->popped);
415 }
416
417 /* Make sure the queue length and semaphore match. Threre is a
418 * race in qeue_pop where the semaphore can be decremented
419 * then the worker killed before updating q->front
420 */
421 q_len = 0;
422 i = w->q->front;
423 while (i != w->q->back) {
424 if (!w->q->data[i])
425 q_len++;
426
427 i = (i + 1) % QUEUE_SIZE;
428 }
429
430 ret = sem_destroy(&w->q->sem);
431 if (ret == -1)
432 tst_brk(TBROK | TERRNO, "sem_destroy");
433 ret = sem_init(&w->q->sem, 1, q_len);
434 if (ret == -1)
435 tst_brk(TBROK | TERRNO, "sem_init");
436
437 worker_heartbeat(worker);
438 w->pid = SAFE_FORK();
439
440 if (!w->pid)
441 exit(worker_run(worker));
442 }
443
check_timeout_warnings_limit(void)444 static void check_timeout_warnings_limit(void)
445 {
446 if (!quiet)
447 return;
448
449 timeout_warnings_left--;
450
451 if (timeout_warnings_left)
452 return;
453
454 tst_res(TINFO,
455 "Silencing timeout warnings; consider increasing LTP_RUNTIME_MUL or removing -q");
456 }
457
try_push_work(const int worker,const char * buf)458 static int try_push_work(const int worker, const char *buf)
459 {
460 int ret = 0;
461 int elapsed;
462 struct worker *const w = workers + worker;
463
464 if (w->kill_sent) {
465 restart_worker(worker);
466 return 0;
467 }
468
469 ret = queue_push(w->q, buf);
470 if (ret)
471 return 1;
472
473 elapsed = worker_elapsed(worker);
474
475 if (elapsed > worker_timeout) {
476 if (!quiet || timeout_warnings_left) {
477 tst_res(TINFO,
478 "Worker %d (%d): Stuck for %dus, restarting it",
479 w->pid, worker, elapsed);
480 check_timeout_warnings_limit();
481 }
482 restart_worker(worker);
483 }
484
485 return 0;
486 }
487
push_work(const int worker,const char * buf)488 static void push_work(const int worker, const char *buf)
489 {
490 int sleep_time = 1;
491
492 while (!try_push_work(worker, buf)) {
493 const int ttl = worker_ttl(worker);
494
495 sleep_time = MIN(2 * sleep_time, ttl);
496 usleep(sleep_time);
497 }
498 }
499
stop_workers(void)500 static void stop_workers(void)
501 {
502 const char stop_code[1] = { '\0' };
503 int i;
504
505 if (!workers)
506 return;
507
508 for (i = 0; i < worker_count; i++) {
509 if (workers[i].q)
510 push_work(i, stop_code);
511 }
512 }
513
destroy_workers(void)514 static void destroy_workers(void)
515 {
516 int i;
517
518 if (!workers)
519 return;
520
521 for (i = 0; i < worker_count; i++) {
522 if (workers[i].q) {
523 queue_destroy(workers[i].q, 0);
524 workers[i].q = 0;
525 }
526 }
527 }
528
sched_work(const int first_worker,const char * path,int repetitions)529 static int sched_work(const int first_worker,
530 const char *path, int repetitions)
531 {
532 int i, j;
533 int min_ttl = worker_timeout, sleep_time = 1;
534 int pushed, workers_pushed = 0;
535
536 if (is_ratelimitted(path))
537 repetitions = 1;
538
539 for (i = 0, j = first_worker; i < repetitions; j++) {
540 if (j >= worker_count)
541 j = 0;
542
543 if (j == first_worker && !workers_pushed) {
544 sleep_time = MIN(2 * sleep_time, min_ttl);
545 usleep(sleep_time);
546 min_ttl = worker_timeout;
547 }
548
549 if (j == first_worker)
550 workers_pushed = 0;
551
552 pushed = try_push_work(j, path);
553 i += pushed;
554 workers_pushed += pushed;
555
556 if (!pushed)
557 min_ttl = MIN(min_ttl, worker_ttl(j));
558 }
559
560 return j;
561 }
562
setup(void)563 static void setup(void)
564 {
565 struct timespec now;
566
567 if (tst_parse_int(str_reads, &reads, 1, INT_MAX))
568 tst_brk(TBROK,
569 "Invalid reads (-r) argument: '%s'", str_reads);
570
571 if (tst_parse_long(str_max_workers, &max_workers, 1, LONG_MAX)) {
572 tst_brk(TBROK,
573 "Invalid max workers (-w) argument: '%s'",
574 str_max_workers);
575 }
576
577 if (tst_parse_long(str_worker_count, &worker_count, 1, LONG_MAX)) {
578 tst_brk(TBROK,
579 "Invalid worker count (-W) argument: '%s'",
580 str_worker_count);
581 }
582
583 if (!root_dir)
584 tst_brk(TBROK, "The directory argument (-d) is required");
585
586 if (!worker_count)
587 worker_count = MIN(MAX(tst_ncpus() - 1, 1L), max_workers);
588 workers = SAFE_MALLOC(worker_count * sizeof(*workers));
589
590 if (tst_parse_int(str_worker_timeout, &worker_timeout, 1, INT_MAX)) {
591 tst_brk(TBROK,
592 "Invalid worker timeout (-t) argument: '%s'",
593 str_worker_count);
594 }
595
596 if (worker_timeout) {
597 tst_res(TINFO, "Worker timeout forcibly set to %dms",
598 worker_timeout);
599 } else {
600 worker_timeout = 10 * tst_remaining_runtime();
601 tst_res(TINFO, "Worker timeout set to 10%% of runtime: %dms",
602 worker_timeout);
603 }
604 worker_timeout *= 1000;
605
606 SAFE_CLOCK_GETTIME(CLOCK_MONOTONIC_RAW, &now);
607 epoch = tst_timespec_to_us(now);
608 }
609
reap_children(void)610 static void reap_children(void)
611 {
612 int status, bad_exit = 0;
613 pid_t pid;
614
615 for (;;) {
616 pid = wait(&status);
617
618 if (pid > 0) {
619 if (!WIFEXITED(status))
620 bad_exit = 1;
621
622 continue;
623 }
624
625 if (errno == ECHILD)
626 break;
627
628 if (errno == EINTR)
629 continue;
630
631 tst_brk(TBROK | TERRNO, "wait() failed");
632 }
633
634 if (!bad_exit)
635 return;
636
637 tst_res(TINFO,
638 "Zombie workers detected; consider increasing LTP_RUNTIME_MUL");
639 }
640
cleanup(void)641 static void cleanup(void)
642 {
643 stop_workers();
644 reap_children();
645 destroy_workers();
646 free(workers);
647 }
648
visit_dir(const char * path)649 static void visit_dir(const char *path)
650 {
651 DIR *dir;
652 struct dirent *dent;
653 struct stat dent_st;
654 char dent_path[MAX_PATH];
655 enum dent_action act;
656 int last_sched = 0;
657
658 dir = opendir(path);
659 if (!dir) {
660 tst_res(TINFO | TERRNO, "opendir(%s)", path);
661 return;
662 }
663
664 while (1) {
665 errno = 0;
666 dent = readdir(dir);
667 if (!dent && errno) {
668 tst_res(TINFO | TERRNO, "readdir(%s)", path);
669 break;
670 } else if (!dent) {
671 break;
672 }
673
674 if (!strcmp(dent->d_name, ".") ||
675 !strcmp(dent->d_name, ".."))
676 continue;
677
678 if (dent->d_type == DT_DIR)
679 act = DA_VISIT;
680 else if (dent->d_type == DT_LNK)
681 act = DA_IGNORE;
682 else if (dent->d_type == DT_UNKNOWN)
683 act = DA_UNKNOWN;
684 else
685 act = DA_READ;
686
687 snprintf(dent_path, MAX_PATH,
688 "%s/%s", path, dent->d_name);
689
690 if (act == DA_UNKNOWN) {
691 if (lstat(dent_path, &dent_st))
692 tst_res(TINFO | TERRNO, "lstat(%s)", path);
693 else if ((dent_st.st_mode & S_IFMT) == S_IFDIR)
694 act = DA_VISIT;
695 else if ((dent_st.st_mode & S_IFMT) == S_IFLNK)
696 act = DA_IGNORE;
697 else
698 act = DA_READ;
699 }
700
701 if (act == DA_VISIT)
702 visit_dir(dent_path);
703 else if (act == DA_READ)
704 last_sched = sched_work(last_sched, dent_path, reads);
705 }
706
707 if (closedir(dir))
708 tst_res(TINFO | TERRNO, "closedir(%s)", path);
709 }
710
run(void)711 static void run(void)
712 {
713 spawn_workers();
714 visit_dir(root_dir);
715
716 stop_workers();
717 reap_children();
718 destroy_workers();
719
720 tst_res(TPASS, "Finished reading files");
721 }
722
723 static struct tst_test test = {
724 .options = (struct tst_option[]) {
725 {"v", &verbose,
726 "Print information about successful reads."},
727 {"q", &quiet,
728 "Don't print file read or open errors."},
729 {"d:", &root_dir,
730 "Path to the directory to read from, defaults to /sys."},
731 {"e:", &blacklist[0],
732 "Pattern Ignore files which match an 'extended' pattern, see fnmatch(3)."},
733 {"r:", &str_reads,
734 "Count The number of times to schedule a file for reading."},
735 {"w:", &str_max_workers,
736 "Count Set the worker count limit, the default is 15."},
737 {"W:", &str_worker_count,
738 "Count Override the worker count. Ignores (-w) and the processor count."},
739 {"p", &drop_privs,
740 "Drop privileges; switch to the nobody user."},
741 {"t:", &str_worker_timeout,
742 "Milliseconds a worker has to read a file before it is restarted"},
743 {}
744 },
745 .setup = setup,
746 .cleanup = cleanup,
747 .test_all = run,
748 .forks_child = 1,
749 .runtime = 100,
750 };
751