• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2017 Richard Palethorpe <rpalethorpe@suse.com>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 /*
18  * Perform a small read on every file in a directory tree.
19  *
20  * Useful for testing file systems like proc, sysfs and debugfs or anything
21  * which exposes a file like API so long as it respects O_NONBLOCK. This test
22  * is not concerned if a particular file in one of these file systems conforms
23  * exactly to its specific documented behavior. Just whether reading from that
24  * file causes a serious error such as a NULL pointer dereference.
25  *
26  * It is not required to run this as root, but test coverage will be much
27  * higher with full privileges.
28  *
29  * The reads are preformed by worker processes which are given file paths by a
30  * single parent process. The parent process recursively scans a given
31  * directory and passes the file paths it finds to the child processes using a
32  * queue structure stored in shared memory.
33  *
34  * This allows the file system and individual files to be accessed in
35  * parallel. Passing the 'reads' parameter (-r) will encourage this. The
36  * number of worker processes is based on the number of available
37  * processors. However this is limited by default to 15 to avoid this becoming
38  * an IPC stress test on systems with large numbers of weak cores. This can be
39  * overridden with the 'w' parameters.
40  */
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <lapi/fnmatch.h>
44 #include <stdlib.h>
45 #include <stdio.h>
46 #include <dirent.h>
47 #include <errno.h>
48 #include <unistd.h>
49 #include <string.h>
50 #include <limits.h>
51 #include <fnmatch.h>
52 #include <semaphore.h>
53 #include <ctype.h>
54 #include <pwd.h>
55 #include <grp.h>
56 
57 #include "tst_test.h"
58 
59 #define QUEUE_SIZE 16384
60 #define BUFFER_SIZE 1024
61 #define MAX_PATH 4096
62 #define MAX_DISPLAY 40
63 
64 struct queue {
65 	sem_t sem;
66 	int front;
67 	int back;
68 	char data[QUEUE_SIZE];
69 };
70 
71 struct worker {
72 	pid_t pid;
73 	struct queue *q;
74 };
75 
76 enum dent_action {
77 	DA_UNKNOWN,
78 	DA_IGNORE,
79 	DA_READ,
80 	DA_VISIT,
81 };
82 
83 static char *verbose;
84 static char *quiet;
85 static char *root_dir;
86 static char *exclude;
87 static char *str_reads;
88 static int reads = 1;
89 static char *str_worker_count;
90 static long worker_count;
91 static char *str_max_workers;
92 static long max_workers = 15;
93 static struct worker *workers;
94 static char *drop_privs;
95 
96 static struct tst_option options[] = {
97 	{"v", &verbose,
98 	 "-v       Print information about successful reads."},
99 	{"q", &quiet,
100 	 "-q       Don't print file read or open errors."},
101 	{"d:", &root_dir,
102 	 "-d path  Path to the directory to read from, defaults to /sys."},
103 	{"e:", &exclude,
104 	 "-e pattern Ignore files which match an 'extended' pattern, see fnmatch(3)."},
105 	{"r:", &str_reads,
106 	 "-r count The number of times to schedule a file for reading."},
107 	{"w:", &str_max_workers,
108 	 "-w count Set the worker count limit, the default is 15."},
109 	{"W:", &str_worker_count,
110 	 "-W count Override the worker count. Ignores (-w) and the processor count."},
111 	{"p", &drop_privs,
112 	 "-p       Drop privileges; switch to the nobody user."},
113 	{NULL, NULL, NULL}
114 };
115 
queue_pop(struct queue * q,char * buf)116 static int queue_pop(struct queue *q, char *buf)
117 {
118 	int i = q->front, j = 0;
119 
120 	sem_wait(&q->sem);
121 
122 	if (!q->data[i])
123 		return 0;
124 
125 	while (q->data[i]) {
126 		buf[j] = q->data[i];
127 
128 		if (++j >= BUFFER_SIZE - 1)
129 			tst_brk(TBROK, "Buffer is too small for path");
130 		if (++i >= QUEUE_SIZE)
131 			i = 0;
132 	}
133 
134 	buf[j] = '\0';
135 	tst_atomic_store(i + 1, &q->front);
136 
137 	return 1;
138 }
139 
queue_push(struct queue * q,const char * buf)140 static int queue_push(struct queue *q, const char *buf)
141 {
142 	int i = q->back, j = 0;
143 	int front = tst_atomic_load(&q->front);
144 
145 	do {
146 		q->data[i] = buf[j];
147 
148 		if (++i >= QUEUE_SIZE)
149 			i = 0;
150 		if (i == front)
151 			return 0;
152 
153 	} while (buf[j++]);
154 
155 	q->back = i;
156 	sem_post(&q->sem);
157 
158 	return 1;
159 }
160 
queue_init(void)161 static struct queue *queue_init(void)
162 {
163 	struct queue *q = SAFE_MMAP(NULL, sizeof(*q),
164 				    PROT_READ | PROT_WRITE,
165 				    MAP_SHARED | MAP_ANONYMOUS,
166 				    0, 0);
167 
168 	sem_init(&q->sem, 1, 0);
169 	q->front = 0;
170 	q->back = 0;
171 
172 	return q;
173 }
174 
queue_destroy(struct queue * q,int is_worker)175 static void queue_destroy(struct queue *q, int is_worker)
176 {
177 	if (is_worker)
178 		sem_destroy(&q->sem);
179 
180 	SAFE_MUNMAP(q, sizeof(*q));
181 }
182 
sanitize_str(char * buf,ssize_t count)183 static void sanitize_str(char *buf, ssize_t count)
184 {
185 	int i;
186 
187 	for (i = 0; i < MIN(count, MAX_DISPLAY); i++)
188 		if (!isprint(buf[i]))
189 			buf[i] = ' ';
190 
191 	if (count <= MAX_DISPLAY)
192 		buf[count] = '\0';
193 	else
194 		strcpy(buf + MAX_DISPLAY, "...");
195 }
196 
read_test(const char * path)197 static void read_test(const char *path)
198 {
199 	char buf[BUFFER_SIZE];
200 	int fd;
201 	ssize_t count;
202 
203 	if (exclude && !fnmatch(exclude, path, FNM_EXTMATCH)) {
204 		if (verbose)
205 			tst_res(TINFO, "Ignoring %s", path);
206 		return;
207 	}
208 
209 	if (verbose)
210 		tst_res(TINFO, "%s(%s)", __func__, path);
211 
212 	fd = open(path, O_RDONLY | O_NONBLOCK);
213 	if (fd < 0) {
214 		if (!quiet)
215 			tst_res(TINFO | TERRNO, "open(%s)", path);
216 		return;
217 	}
218 
219 	count = read(fd, buf, sizeof(buf) - 1);
220 	if (count > 0 && verbose) {
221 		sanitize_str(buf, count);
222 		tst_res(TINFO, "read(%s, buf) = %zi, buf = %s",
223 			path, count, buf);
224 	} else if (!count && verbose) {
225 		tst_res(TINFO, "read(%s) = EOF", path);
226 	} else if (count < 0 && !quiet) {
227 		tst_res(TINFO | TERRNO, "read(%s)", path);
228 	}
229 
230 	SAFE_CLOSE(fd);
231 }
232 
worker_run(struct worker * self)233 static int worker_run(struct worker *self)
234 {
235 	char buf[BUFFER_SIZE];
236 	struct sigaction term_sa = {
237 		.sa_handler = SIG_IGN,
238 		.sa_flags = 0,
239 	};
240 	struct queue *q = self->q;
241 
242 	sigaction(SIGTTIN, &term_sa, NULL);
243 
244 	while (1) {
245 		if (!queue_pop(q, buf))
246 			break;
247 
248 		read_test(buf);
249 	}
250 
251 	queue_destroy(q, 1);
252 	tst_flush();
253 	return 0;
254 }
255 
maybe_drop_privs(void)256 static void maybe_drop_privs(void)
257 {
258 	struct passwd *nobody;
259 
260 	if (!drop_privs)
261 		return;
262 
263 	TEST(setgroups(0, NULL));
264 	if (TST_RET < 0 && TST_ERR != EPERM) {
265 		tst_brk(TBROK | TTERRNO,
266 			"Failed to clear suplementary group set");
267 	}
268 
269 	nobody = SAFE_GETPWNAM("nobody");
270 
271 	TEST(setgid(nobody->pw_gid));
272 	if (TST_RET < 0 && TST_ERR != EPERM)
273 		tst_brk(TBROK | TTERRNO, "Failed to use nobody gid");
274 
275 	TEST(setuid(nobody->pw_uid));
276 	if (TST_RET < 0 && TST_ERR != EPERM)
277 		tst_brk(TBROK | TTERRNO, "Failed to use nobody uid");
278 }
279 
spawn_workers(void)280 static void spawn_workers(void)
281 {
282 	int i;
283 	struct worker *wa = workers;
284 
285 	bzero(workers, worker_count * sizeof(*workers));
286 
287 	for (i = 0; i < worker_count; i++) {
288 		wa[i].q = queue_init();
289 		wa[i].pid = SAFE_FORK();
290 		if (!wa[i].pid) {
291 			maybe_drop_privs();
292 			exit(worker_run(wa + i));
293 		}
294 	}
295 }
296 
stop_workers(void)297 static void stop_workers(void)
298 {
299 	const char stop_code[1] = { '\0' };
300 	int i;
301 
302 	if (!workers)
303 		return;
304 
305 	for (i = 0; i < worker_count; i++) {
306 		if (workers[i].q)
307 			TST_RETRY_FUNC(queue_push(workers[i].q, stop_code), 1);
308 	}
309 
310 	for (i = 0; i < worker_count; i++) {
311 		if (workers[i].q) {
312 			queue_destroy(workers[i].q, 0);
313 			workers[i].q = 0;
314 		}
315 	}
316 }
317 
rep_sched_work(const char * path,int rep)318 static void rep_sched_work(const char *path, int rep)
319 {
320 	int i, j;
321 
322 	for (i = j = 0; i < rep; i++, j++) {
323 		if (j >= worker_count)
324 			j = 0;
325 		TST_RETRY_FUNC(queue_push(workers[j].q, path), 1);
326 	}
327 }
328 
setup(void)329 static void setup(void)
330 {
331 	if (tst_parse_int(str_reads, &reads, 1, INT_MAX))
332 		tst_brk(TBROK,
333 			"Invalid reads (-r) argument: '%s'", str_reads);
334 
335 	if (tst_parse_long(str_max_workers, &max_workers, 1, LONG_MAX)) {
336 		tst_brk(TBROK,
337 			"Invalid max workers (-w) argument: '%s'",
338 			str_max_workers);
339 	}
340 
341 	if (tst_parse_long(str_worker_count, &worker_count, 1, LONG_MAX)) {
342 		tst_brk(TBROK,
343 			"Invalid worker count (-W) argument: '%s'",
344 			str_worker_count);
345 	}
346 
347 	if (!root_dir)
348 		tst_brk(TBROK, "The directory argument (-d) is required");
349 
350 	if (!worker_count)
351 		worker_count = MIN(MAX(tst_ncpus() - 1, 1), max_workers);
352 	workers = SAFE_MALLOC(worker_count * sizeof(*workers));
353 }
354 
cleanup(void)355 static void cleanup(void)
356 {
357 	stop_workers();
358 	free(workers);
359 }
360 
visit_dir(const char * path)361 static void visit_dir(const char *path)
362 {
363 	DIR *dir;
364 	struct dirent *dent;
365 	struct stat dent_st;
366 	char dent_path[MAX_PATH];
367 	enum dent_action act;
368 
369 	dir = opendir(path);
370 	if (!dir) {
371 		tst_res(TINFO | TERRNO, "opendir(%s)", path);
372 		return;
373 	}
374 
375 	while (1) {
376 		errno = 0;
377 		dent = readdir(dir);
378 		if (!dent && errno) {
379 			tst_res(TINFO | TERRNO, "readdir(%s)", path);
380 			break;
381 		} else if (!dent) {
382 			break;
383 		}
384 
385 		if (!strcmp(dent->d_name, ".") ||
386 		    !strcmp(dent->d_name, ".."))
387 			continue;
388 
389 		if (dent->d_type == DT_DIR)
390 			act = DA_VISIT;
391 		else if (dent->d_type == DT_LNK)
392 			act = DA_IGNORE;
393 		else if (dent->d_type == DT_UNKNOWN)
394 			act = DA_UNKNOWN;
395 		else
396 			act = DA_READ;
397 
398 		snprintf(dent_path, MAX_PATH,
399 			 "%s/%s", path, dent->d_name);
400 
401 		if (act == DA_UNKNOWN) {
402 			if (lstat(dent_path, &dent_st))
403 				tst_res(TINFO | TERRNO, "lstat(%s)", path);
404 			else if ((dent_st.st_mode & S_IFMT) == S_IFDIR)
405 				act = DA_VISIT;
406 			else if ((dent_st.st_mode & S_IFMT) == S_IFLNK)
407 				act = DA_IGNORE;
408 			else
409 				act = DA_READ;
410 		}
411 
412 		if (act == DA_VISIT)
413 			visit_dir(dent_path);
414 		else if (act == DA_READ)
415 			rep_sched_work(dent_path, reads);
416 	}
417 
418 	if (closedir(dir))
419 		tst_res(TINFO | TERRNO, "closedir(%s)", path);
420 }
421 
run(void)422 static void run(void)
423 {
424 	spawn_workers();
425 	visit_dir(root_dir);
426 	stop_workers();
427 
428 	tst_reap_children();
429 	tst_res(TPASS, "Finished reading files");
430 }
431 
432 static struct tst_test test = {
433 	.options = options,
434 	.setup = setup,
435 	.cleanup = cleanup,
436 	.test_all = run,
437 	.forks_child = 1,
438 };
439 
440