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