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