• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 	"/sys/devices/platform/*/eeprom",
89 	"/sys/devices/platform/*/nvmem",
90 	"/sys/*/cpu??*(?)/*",	/* cpu* entries with 2 or more digits */
91 };
92 
queue_pop(struct queue * q,char * buf)93 static int queue_pop(struct queue *q, char *buf)
94 {
95 	int i = q->front, j = 0;
96 
97 	sem_wait(&q->sem);
98 
99 	if (!q->data[i])
100 		return 0;
101 
102 	while (q->data[i]) {
103 		buf[j] = q->data[i];
104 
105 		if (++j >= BUFFER_SIZE - 1)
106 			tst_brk(TBROK, "Buffer is too small for path");
107 
108 		 i = (i + 1) % QUEUE_SIZE;
109 	}
110 
111 	buf[j] = '\0';
112 	tst_atomic_store((i + 1) % QUEUE_SIZE, &q->front);
113 
114 	return 1;
115 }
116 
queue_push(struct queue * q,const char * buf)117 static int queue_push(struct queue *q, const char *buf)
118 {
119 	int i = q->back, j = 0;
120 	int front = tst_atomic_load(&q->front);
121 
122 	do {
123 		q->data[i] = buf[j];
124 
125 		i = (i + 1) % QUEUE_SIZE;
126 
127 		if (i == front)
128 			return 0;
129 
130 	} while (buf[j++]);
131 
132 	q->back = i;
133 	sem_post(&q->sem);
134 
135 	return 1;
136 }
137 
queue_init(void)138 static struct queue *queue_init(void)
139 {
140 	struct queue *q = SAFE_MMAP(NULL, sizeof(*q),
141 				    PROT_READ | PROT_WRITE,
142 				    MAP_SHARED | MAP_ANONYMOUS,
143 				    0, 0);
144 
145 	sem_init(&q->sem, 1, 0);
146 	q->front = 0;
147 	q->back = 0;
148 
149 	return q;
150 }
151 
queue_destroy(struct queue * q,int is_worker)152 static void queue_destroy(struct queue *q, int is_worker)
153 {
154 	if (is_worker)
155 		sem_destroy(&q->sem);
156 
157 	SAFE_MUNMAP(q, sizeof(*q));
158 }
159 
sanitize_str(char * buf,ssize_t count)160 static void sanitize_str(char *buf, ssize_t count)
161 {
162 	int i;
163 
164 	for (i = 0; i < MIN(count, MAX_DISPLAY); i++)
165 		if (!isprint(buf[i]))
166 			buf[i] = ' ';
167 
168 	if (count <= MAX_DISPLAY)
169 		buf[count] = '\0';
170 	else
171 		strcpy(buf + MAX_DISPLAY, "...");
172 }
173 
is_blacklisted(const char * path)174 static int is_blacklisted(const char *path)
175 {
176 	unsigned int i;
177 
178 	for (i = 0; i < ARRAY_SIZE(blacklist); i++) {
179 		if (blacklist[i] && !fnmatch(blacklist[i], path, FNM_EXTMATCH)) {
180 			if (verbose)
181 				tst_res(TINFO, "Ignoring %s", path);
182 			return 1;
183 		}
184 	}
185 
186 	return 0;
187 }
188 
read_test(const char * path)189 static void read_test(const char *path)
190 {
191 	char buf[BUFFER_SIZE];
192 	int fd;
193 	ssize_t count;
194 
195 	if (is_blacklisted(path))
196 		return;
197 
198 	if (verbose)
199 		tst_res(TINFO, "%s(%s)", __func__, path);
200 
201 	fd = open(path, O_RDONLY | O_NONBLOCK);
202 	if (fd < 0) {
203 		if (!quiet)
204 			tst_res(TINFO | TERRNO, "open(%s)", path);
205 		return;
206 	}
207 
208 	count = read(fd, buf, sizeof(buf) - 1);
209 	if (count > 0 && verbose) {
210 		sanitize_str(buf, count);
211 		tst_res(TINFO, "read(%s, buf) = %zi, buf = %s",
212 			path, count, buf);
213 	} else if (!count && verbose) {
214 		tst_res(TINFO, "read(%s) = EOF", path);
215 	} else if (count < 0 && !quiet) {
216 		tst_res(TINFO | TERRNO, "read(%s)", path);
217 	}
218 
219 	SAFE_CLOSE(fd);
220 }
221 
worker_run(struct worker * self)222 static int worker_run(struct worker *self)
223 {
224 	char buf[BUFFER_SIZE];
225 	struct sigaction term_sa = {
226 		.sa_handler = SIG_IGN,
227 		.sa_flags = 0,
228 	};
229 	struct queue *q = self->q;
230 
231 	sigaction(SIGTTIN, &term_sa, NULL);
232 
233 	while (1) {
234 		if (!queue_pop(q, buf))
235 			break;
236 
237 		read_test(buf);
238 	}
239 
240 	queue_destroy(q, 1);
241 	tst_flush();
242 	return 0;
243 }
244 
maybe_drop_privs(void)245 static void maybe_drop_privs(void)
246 {
247 	struct passwd *nobody;
248 
249 	if (!drop_privs)
250 		return;
251 
252 	TEST(setgroups(0, NULL));
253 	if (TST_RET < 0 && TST_ERR != EPERM) {
254 		tst_brk(TBROK | TTERRNO,
255 			"Failed to clear suplementary group set");
256 	}
257 
258 	nobody = SAFE_GETPWNAM("nobody");
259 
260 	TEST(setgid(nobody->pw_gid));
261 	if (TST_RET < 0 && TST_ERR != EPERM)
262 		tst_brk(TBROK | TTERRNO, "Failed to use nobody gid");
263 
264 	TEST(setuid(nobody->pw_uid));
265 	if (TST_RET < 0 && TST_ERR != EPERM)
266 		tst_brk(TBROK | TTERRNO, "Failed to use nobody uid");
267 }
268 
spawn_workers(void)269 static void spawn_workers(void)
270 {
271 	int i;
272 	struct worker *wa = workers;
273 
274 	memset(workers, 0, worker_count * sizeof(*workers));
275 
276 	for (i = 0; i < worker_count; i++) {
277 		wa[i].q = queue_init();
278 		wa[i].pid = SAFE_FORK();
279 		if (!wa[i].pid) {
280 			maybe_drop_privs();
281 			exit(worker_run(wa + i));
282 		}
283 	}
284 }
285 
work_push_retry(int worker,const char * buf)286 static void work_push_retry(int worker, const char *buf)
287 {
288 	int i, ret, worker_min, worker_max, usleep_time = 100;
289 
290 	if (worker < 0) {
291 		/* pick any, try -worker first */
292 		worker_min = worker * (-1);
293 		worker_max = worker_count;
294 	} else {
295 		/* keep trying worker */
296 		worker_min = worker;
297 		worker_max = worker + 1;
298 	}
299 	i = worker_min;
300 
301 	for (;;) {
302 		ret = queue_push(workers[i].q, buf);
303 		if (ret == 1)
304 			break;
305 
306 		if (++i >= worker_max) {
307 			i = worker_min;
308 			if (usleep_time < 100000)
309 				usleep_time *= 2;
310 			usleep(usleep_time);
311 		}
312 	}
313 }
314 
stop_workers(void)315 static void stop_workers(void)
316 {
317 	const char stop_code[1] = { '\0' };
318 	int i;
319 
320 	if (!workers)
321 		return;
322 
323 	for (i = 0; i < worker_count; i++) {
324 		if (workers[i].q)
325 			work_push_retry(i, stop_code);
326 	}
327 
328 	for (i = 0; i < worker_count; i++) {
329 		if (workers[i].q) {
330 			queue_destroy(workers[i].q, 0);
331 			workers[i].q = 0;
332 		}
333 	}
334 }
335 
rep_sched_work(const char * path,int rep)336 static void rep_sched_work(const char *path, int rep)
337 {
338 	int i, j;
339 
340 	for (i = j = 0; i < rep; i++, j++) {
341 		if (j >= worker_count)
342 			j = 0;
343 		work_push_retry(-j, path);
344 	}
345 }
346 
setup(void)347 static void setup(void)
348 {
349 	if (tst_parse_int(str_reads, &reads, 1, INT_MAX))
350 		tst_brk(TBROK,
351 			"Invalid reads (-r) argument: '%s'", str_reads);
352 
353 	if (tst_parse_long(str_max_workers, &max_workers, 1, LONG_MAX)) {
354 		tst_brk(TBROK,
355 			"Invalid max workers (-w) argument: '%s'",
356 			str_max_workers);
357 	}
358 
359 	if (tst_parse_long(str_worker_count, &worker_count, 1, LONG_MAX)) {
360 		tst_brk(TBROK,
361 			"Invalid worker count (-W) argument: '%s'",
362 			str_worker_count);
363 	}
364 
365 	if (!root_dir)
366 		tst_brk(TBROK, "The directory argument (-d) is required");
367 
368 	if (!worker_count)
369 		worker_count = MIN(MAX(tst_ncpus() - 1, 1), max_workers);
370 	workers = SAFE_MALLOC(worker_count * sizeof(*workers));
371 }
372 
cleanup(void)373 static void cleanup(void)
374 {
375 	stop_workers();
376 	free(workers);
377 }
378 
visit_dir(const char * path)379 static void visit_dir(const char *path)
380 {
381 	DIR *dir;
382 	struct dirent *dent;
383 	struct stat dent_st;
384 	char dent_path[MAX_PATH];
385 	enum dent_action act;
386 
387 	dir = opendir(path);
388 	if (!dir) {
389 		tst_res(TINFO | TERRNO, "opendir(%s)", path);
390 		return;
391 	}
392 
393 	while (1) {
394 		errno = 0;
395 		dent = readdir(dir);
396 		if (!dent && errno) {
397 			tst_res(TINFO | TERRNO, "readdir(%s)", path);
398 			break;
399 		} else if (!dent) {
400 			break;
401 		}
402 
403 		if (!strcmp(dent->d_name, ".") ||
404 		    !strcmp(dent->d_name, ".."))
405 			continue;
406 
407 		if (dent->d_type == DT_DIR)
408 			act = DA_VISIT;
409 		else if (dent->d_type == DT_LNK)
410 			act = DA_IGNORE;
411 		else if (dent->d_type == DT_UNKNOWN)
412 			act = DA_UNKNOWN;
413 		else
414 			act = DA_READ;
415 
416 		snprintf(dent_path, MAX_PATH,
417 			 "%s/%s", path, dent->d_name);
418 
419 		if (act == DA_UNKNOWN) {
420 			if (lstat(dent_path, &dent_st))
421 				tst_res(TINFO | TERRNO, "lstat(%s)", path);
422 			else if ((dent_st.st_mode & S_IFMT) == S_IFDIR)
423 				act = DA_VISIT;
424 			else if ((dent_st.st_mode & S_IFMT) == S_IFLNK)
425 				act = DA_IGNORE;
426 			else
427 				act = DA_READ;
428 		}
429 
430 		if (act == DA_VISIT)
431 			visit_dir(dent_path);
432 		else if (act == DA_READ)
433 			rep_sched_work(dent_path, reads);
434 	}
435 
436 	if (closedir(dir))
437 		tst_res(TINFO | TERRNO, "closedir(%s)", path);
438 }
439 
run(void)440 static void run(void)
441 {
442 	spawn_workers();
443 	visit_dir(root_dir);
444 	stop_workers();
445 
446 	tst_reap_children();
447 	tst_res(TPASS, "Finished reading files");
448 }
449 
450 static struct tst_test test = {
451 	.options = (struct tst_option[]) {
452 		{"v", &verbose,
453 		 "Print information about successful reads."},
454 		{"q", &quiet,
455 		 "Don't print file read or open errors."},
456 		{"d:", &root_dir,
457 		 "Path to the directory to read from, defaults to /sys."},
458 		{"e:", &blacklist[0],
459 		 "Pattern Ignore files which match an 'extended' pattern, see fnmatch(3)."},
460 		{"r:", &str_reads,
461 		 "Count The number of times to schedule a file for reading."},
462 		{"w:", &str_max_workers,
463 		 "Count Set the worker count limit, the default is 15."},
464 		{"W:", &str_worker_count,
465 		 "Count Override the worker count. Ignores (-w) and the processor count."},
466 		{"p", &drop_privs,
467 		 "Drop privileges; switch to the nobody user."},
468 		{}
469 	},
470 	.setup = setup,
471 	.cleanup = cleanup,
472 	.test_all = run,
473 	.forks_child = 1,
474 };
475 
476