xref: /aosp_15_r20/external/ltp/testcases/kernel/fs/read_all/read_all.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
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