• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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