xref: /freebsd/sys/contrib/openzfs/tests/zfs-tests/cmd/xattrtest.c (revision 8ddb146abcdf061be9f2c0db7e391697dafad85c)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or https://opensource.org/licenses/CDDL-1.0.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2016 Lawrence Livermore National Security, LLC.
24  */
25 
26 /*
27  * An extended attribute (xattr) correctness test.  This program creates
28  * N files and sets M attrs on them of size S.  Optionally is will verify
29  * a pattern stored in the xattr.
30  */
31 #include <stdlib.h>
32 #include <stddef.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <getopt.h>
37 #include <fcntl.h>
38 #include <time.h>
39 #include <unistd.h>
40 #include <sys/xattr.h>
41 #include <sys/types.h>
42 #include <sys/wait.h>
43 #include <sys/stat.h>
44 #include <sys/time.h>
45 #include <linux/limits.h>
46 
47 #define	ERROR(fmt, ...)                                                 \
48 	fprintf(stderr, "xattrtest: %s:%d: %s: " fmt "\n",              \
49 		__FILE__, __LINE__,      				\
50 		__func__, ## __VA_ARGS__);
51 
52 static const char shortopts[] = "hvycdn:f:x:s:p:t:e:rRko:";
53 static const struct option longopts[] = {
54 	{ "help",		no_argument,		0,	'h' },
55 	{ "verbose",		no_argument,		0,	'v' },
56 	{ "verify",		no_argument,		0,	'y' },
57 	{ "nth",		required_argument,	0,	'n' },
58 	{ "files",		required_argument,	0,	'f' },
59 	{ "xattrs",		required_argument,	0,	'x' },
60 	{ "size",		required_argument,	0,	's' },
61 	{ "path",		required_argument,	0,	'p' },
62 	{ "synccaches", 	no_argument,		0,	'c' },
63 	{ "dropcaches",		no_argument,		0,	'd' },
64 	{ "script",		required_argument,	0,	't' },
65 	{ "seed",		required_argument,	0,	'e' },
66 	{ "random",		no_argument,		0,	'r' },
67 	{ "randomvalue",	no_argument,		0,	'R' },
68 	{ "keep",		no_argument,		0,	'k' },
69 	{ "only",		required_argument,	0,	'o' },
70 	{ 0,			0,			0,	0   }
71 };
72 
73 enum phases {
74 	PHASE_ALL = 0,
75 	PHASE_CREATE,
76 	PHASE_SETXATTR,
77 	PHASE_GETXATTR,
78 	PHASE_UNLINK,
79 	PHASE_INVAL
80 };
81 
82 static int verbose = 0;
83 static int verify = 0;
84 static int synccaches = 0;
85 static int dropcaches = 0;
86 static int nth = 0;
87 static int files = 1000;
88 static int xattrs = 1;
89 static int size = 6;
90 static int size_is_random = 0;
91 static int value_is_random = 0;
92 static int keep_files = 0;
93 static int phase = PHASE_ALL;
94 static const char *path = "/tmp/xattrtest";
95 static const char *script = "/bin/true";
96 static char xattrbytes[XATTR_SIZE_MAX];
97 
98 static int
99 usage(char *argv0)
100 {
101 	fprintf(stderr,
102 	    "usage: %s [-hvycdrRk] [-n <nth>] [-f <files>] [-x <xattrs>]\n"
103 	    "       [-s <bytes>] [-p <path>] [-t <script> ] [-o <phase>]\n",
104 	    argv0);
105 
106 	fprintf(stderr,
107 	    "  --help        -h           This help\n"
108 	    "  --verbose     -v           Increase verbosity\n"
109 	    "  --verify      -y           Verify xattr contents\n"
110 	    "  --nth         -n <nth>     Print every nth file\n"
111 	    "  --files       -f <files>   Set xattrs on N files\n"
112 	    "  --xattrs      -x <xattrs>  Set N xattrs on each file\n"
113 	    "  --size        -s <bytes>   Set N bytes per xattr\n"
114 	    "  --path        -p <path>    Path to files\n"
115 	    "  --synccaches  -c           Sync caches between phases\n"
116 	    "  --dropcaches  -d           Drop caches between phases\n"
117 	    "  --script      -t <script>  Exec script between phases\n"
118 	    "  --seed        -e <seed>    Random seed value\n"
119 	    "  --random      -r           Randomly sized xattrs [16-size]\n"
120 	    "  --randomvalue -R           Random xattr values\n"
121 	    "  --keep        -k           Don't unlink files\n"
122 	    "  --only        -o <num>     Only run phase N\n"
123 	    "                             0=all, 1=create, 2=setxattr,\n"
124 	    "                             3=getxattr, 4=unlink\n\n");
125 
126 	return (1);
127 }
128 
129 static int
130 parse_args(int argc, char **argv)
131 {
132 	long seed = time(NULL);
133 	int c;
134 	int rc = 0;
135 
136 	while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
137 		switch (c) {
138 		case 'h':
139 			return (usage(argv[0]));
140 		case 'v':
141 			verbose++;
142 			break;
143 		case 'y':
144 			verify = 1;
145 			break;
146 		case 'n':
147 			nth = strtol(optarg, NULL, 0);
148 			break;
149 		case 'f':
150 			files = strtol(optarg, NULL, 0);
151 			break;
152 		case 'x':
153 			xattrs = strtol(optarg, NULL, 0);
154 			break;
155 		case 's':
156 			size = strtol(optarg, NULL, 0);
157 			if (size > XATTR_SIZE_MAX) {
158 				fprintf(stderr, "Error: the -s value may not "
159 				    "be greater than %d\n", XATTR_SIZE_MAX);
160 				rc = 1;
161 			}
162 			break;
163 		case 'p':
164 			path = optarg;
165 			break;
166 		case 'c':
167 			synccaches = 1;
168 			break;
169 		case 'd':
170 			dropcaches = 1;
171 			break;
172 		case 't':
173 			script = optarg;
174 			break;
175 		case 'e':
176 			seed = strtol(optarg, NULL, 0);
177 			break;
178 		case 'r':
179 			size_is_random = 1;
180 			break;
181 		case 'R':
182 			value_is_random = 1;
183 			break;
184 		case 'k':
185 			keep_files = 1;
186 			break;
187 		case 'o':
188 			phase = strtol(optarg, NULL, 0);
189 			if (phase <= PHASE_ALL || phase >= PHASE_INVAL) {
190 				fprintf(stderr, "Error: the -o value must be "
191 				    "greater than %d and less than %d\n",
192 				    PHASE_ALL, PHASE_INVAL);
193 				rc = 1;
194 			}
195 			break;
196 		default:
197 			rc = 1;
198 			break;
199 		}
200 	}
201 
202 	if (rc != 0)
203 		return (rc);
204 
205 	srandom(seed);
206 
207 	if (verbose) {
208 		fprintf(stdout, "verbose:          %d\n", verbose);
209 		fprintf(stdout, "verify:           %d\n", verify);
210 		fprintf(stdout, "nth:              %d\n", nth);
211 		fprintf(stdout, "files:            %d\n", files);
212 		fprintf(stdout, "xattrs:           %d\n", xattrs);
213 		fprintf(stdout, "size:             %d\n", size);
214 		fprintf(stdout, "path:             %s\n", path);
215 		fprintf(stdout, "synccaches:       %d\n", synccaches);
216 		fprintf(stdout, "dropcaches:       %d\n", dropcaches);
217 		fprintf(stdout, "script:           %s\n", script);
218 		fprintf(stdout, "seed:             %ld\n", seed);
219 		fprintf(stdout, "random size:      %d\n", size_is_random);
220 		fprintf(stdout, "random value:     %d\n", value_is_random);
221 		fprintf(stdout, "keep:             %d\n", keep_files);
222 		fprintf(stdout, "only:             %d\n", phase);
223 		fprintf(stdout, "%s", "\n");
224 	}
225 
226 	return (rc);
227 }
228 
229 static int
230 drop_caches(void)
231 {
232 	char file[] = "/proc/sys/vm/drop_caches";
233 	int fd, rc;
234 
235 	fd = open(file, O_WRONLY);
236 	if (fd == -1) {
237 		ERROR("Error %d: open(\"%s\", O_WRONLY)\n", errno, file);
238 		return (errno);
239 	}
240 
241 	rc = write(fd, "3", 1);
242 	if ((rc == -1) || (rc != 1)) {
243 		ERROR("Error %d: write(%d, \"3\", 1)\n", errno, fd);
244 		(void) close(fd);
245 		return (errno);
246 	}
247 
248 	rc = close(fd);
249 	if (rc == -1) {
250 		ERROR("Error %d: close(%d)\n", errno, fd);
251 		return (errno);
252 	}
253 
254 	return (0);
255 }
256 
257 static int
258 run_process(const char *path, char *argv[])
259 {
260 	pid_t pid;
261 	int rc, devnull_fd;
262 
263 	pid = fork();
264 	if (pid == 0) {
265 		devnull_fd = open("/dev/null", O_WRONLY);
266 
267 		if (devnull_fd < 0)
268 			_exit(-1);
269 
270 		(void) dup2(devnull_fd, STDOUT_FILENO);
271 		(void) dup2(devnull_fd, STDERR_FILENO);
272 		close(devnull_fd);
273 
274 		(void) execvp(path, argv);
275 		_exit(-1);
276 	} else if (pid > 0) {
277 		int status;
278 
279 		while ((rc = waitpid(pid, &status, 0)) == -1 &&
280 		    errno == EINTR) { }
281 
282 		if (rc < 0 || !WIFEXITED(status))
283 			return (-1);
284 
285 		return (WEXITSTATUS(status));
286 	}
287 
288 	return (-1);
289 }
290 
291 static int
292 post_hook(const char *phase)
293 {
294 	char *argv[3] = { (char *)script, (char *)phase, NULL };
295 	int rc;
296 
297 	if (synccaches)
298 		sync();
299 
300 	if (dropcaches) {
301 		rc = drop_caches();
302 		if (rc)
303 			return (rc);
304 	}
305 
306 	rc = run_process(script, argv);
307 	if (rc)
308 		return (rc);
309 
310 	return (0);
311 }
312 
313 #define	USEC_PER_SEC	1000000
314 
315 static void
316 timeval_normalize(struct timeval *tv, time_t sec, suseconds_t usec)
317 {
318 	while (usec >= USEC_PER_SEC) {
319 		usec -= USEC_PER_SEC;
320 		sec++;
321 	}
322 
323 	while (usec < 0) {
324 		usec += USEC_PER_SEC;
325 		sec--;
326 	}
327 
328 	tv->tv_sec = sec;
329 	tv->tv_usec = usec;
330 }
331 
332 static void
333 timeval_sub(struct timeval *delta, struct timeval *tv1, struct timeval *tv2)
334 {
335 	timeval_normalize(delta,
336 	    tv1->tv_sec - tv2->tv_sec,
337 	    tv1->tv_usec - tv2->tv_usec);
338 }
339 
340 static double
341 timeval_sub_seconds(struct timeval *tv1, struct timeval *tv2)
342 {
343 	struct timeval delta;
344 
345 	timeval_sub(&delta, tv1, tv2);
346 	return ((double)delta.tv_usec / USEC_PER_SEC + delta.tv_sec);
347 }
348 
349 static int
350 create_files(void)
351 {
352 	int i, rc;
353 	char *file = NULL;
354 	struct timeval start, stop;
355 	double seconds;
356 	size_t fsize;
357 
358 	fsize = PATH_MAX;
359 	file = malloc(fsize);
360 	if (file == NULL) {
361 		rc = ENOMEM;
362 		ERROR("Error %d: malloc(%d) bytes for file name\n", rc,
363 		    PATH_MAX);
364 		goto out;
365 	}
366 
367 	(void) gettimeofday(&start, NULL);
368 
369 	for (i = 1; i <= files; i++) {
370 		if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
371 			rc = EINVAL;
372 			ERROR("Error %d: path too long\n", rc);
373 			goto out;
374 		}
375 
376 		if (nth && ((i % nth) == 0))
377 			fprintf(stdout, "create: %s\n", file);
378 
379 		rc = unlink(file);
380 		if ((rc == -1) && (errno != ENOENT)) {
381 			ERROR("Error %d: unlink(%s)\n", errno, file);
382 			rc = errno;
383 			goto out;
384 		}
385 
386 		rc = open(file, O_CREAT, 0644);
387 		if (rc == -1) {
388 			ERROR("Error %d: open(%s, O_CREATE, 0644)\n",
389 			    errno, file);
390 			rc = errno;
391 			goto out;
392 		}
393 
394 		rc = close(rc);
395 		if (rc == -1) {
396 			ERROR("Error %d: close(%d)\n", errno, rc);
397 			rc = errno;
398 			goto out;
399 		}
400 	}
401 
402 	(void) gettimeofday(&stop, NULL);
403 	seconds = timeval_sub_seconds(&stop, &start);
404 	fprintf(stdout, "create:   %f seconds %f creates/second\n",
405 	    seconds, files / seconds);
406 
407 	rc = post_hook("post");
408 out:
409 	if (file)
410 		free(file);
411 
412 	return (rc);
413 }
414 
415 static int
416 get_random_bytes(char *buf, size_t bytes)
417 {
418 	int rand;
419 	ssize_t bytes_read = 0;
420 
421 	rand = open("/dev/urandom", O_RDONLY);
422 
423 	if (rand < 0)
424 		return (rand);
425 
426 	while (bytes_read < bytes) {
427 		ssize_t rc = read(rand, buf + bytes_read, bytes - bytes_read);
428 		if (rc < 0)
429 			break;
430 		bytes_read += rc;
431 	}
432 
433 	(void) close(rand);
434 
435 	return (bytes_read);
436 }
437 
438 static int
439 setxattrs(void)
440 {
441 	int i, j, rnd_size = size, shift, rc = 0;
442 	char name[XATTR_NAME_MAX];
443 	char *value = NULL;
444 	char *file = NULL;
445 	struct timeval start, stop;
446 	double seconds;
447 	size_t fsize;
448 
449 	value = malloc(XATTR_SIZE_MAX);
450 	if (value == NULL) {
451 		rc = ENOMEM;
452 		ERROR("Error %d: malloc(%d) bytes for xattr value\n", rc,
453 		    XATTR_SIZE_MAX);
454 		goto out;
455 	}
456 
457 	fsize = PATH_MAX;
458 	file = malloc(fsize);
459 	if (file == NULL) {
460 		rc = ENOMEM;
461 		ERROR("Error %d: malloc(%d) bytes for file name\n", rc,
462 		    PATH_MAX);
463 		goto out;
464 	}
465 
466 	(void) gettimeofday(&start, NULL);
467 
468 	for (i = 1; i <= files; i++) {
469 		if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
470 			rc = EINVAL;
471 			ERROR("Error %d: path too long\n", rc);
472 			goto out;
473 		}
474 
475 		if (nth && ((i % nth) == 0))
476 			fprintf(stdout, "setxattr: %s\n", file);
477 
478 		for (j = 1; j <= xattrs; j++) {
479 			if (size_is_random)
480 				rnd_size = (random() % (size - 16)) + 16;
481 
482 			(void) sprintf(name, "user.%d", j);
483 			shift = sprintf(value, "size=%d ", rnd_size);
484 			memcpy(value + shift, xattrbytes,
485 			    sizeof (xattrbytes) - shift);
486 
487 			rc = lsetxattr(file, name, value, rnd_size, 0);
488 			if (rc == -1) {
489 				ERROR("Error %d: lsetxattr(%s, %s, ..., %d)\n",
490 				    errno, file, name, rnd_size);
491 				goto out;
492 			}
493 		}
494 	}
495 
496 	(void) gettimeofday(&stop, NULL);
497 	seconds = timeval_sub_seconds(&stop, &start);
498 	fprintf(stdout, "setxattr: %f seconds %f setxattrs/second\n",
499 	    seconds, (files * xattrs) / seconds);
500 
501 	rc = post_hook("post");
502 out:
503 	if (file)
504 		free(file);
505 
506 	if (value)
507 		free(value);
508 
509 	return (rc);
510 }
511 
512 static int
513 getxattrs(void)
514 {
515 	int i, j, rnd_size, shift, rc = 0;
516 	char name[XATTR_NAME_MAX];
517 	char *verify_value = NULL;
518 	const char *verify_string;
519 	char *value = NULL;
520 	const char *value_string;
521 	char *file = NULL;
522 	struct timeval start, stop;
523 	double seconds;
524 	size_t fsize;
525 
526 	verify_value = malloc(XATTR_SIZE_MAX);
527 	if (verify_value == NULL) {
528 		rc = ENOMEM;
529 		ERROR("Error %d: malloc(%d) bytes for xattr verify\n", rc,
530 		    XATTR_SIZE_MAX);
531 		goto out;
532 	}
533 
534 	value = malloc(XATTR_SIZE_MAX);
535 	if (value == NULL) {
536 		rc = ENOMEM;
537 		ERROR("Error %d: malloc(%d) bytes for xattr value\n", rc,
538 		    XATTR_SIZE_MAX);
539 		goto out;
540 	}
541 
542 	verify_string = value_is_random ? "<random>" : verify_value;
543 	value_string = value_is_random ? "<random>" : value;
544 
545 	fsize = PATH_MAX;
546 	file = malloc(fsize);
547 
548 	if (file == NULL) {
549 		rc = ENOMEM;
550 		ERROR("Error %d: malloc(%d) bytes for file name\n", rc,
551 		    PATH_MAX);
552 		goto out;
553 	}
554 
555 	(void) gettimeofday(&start, NULL);
556 
557 	for (i = 1; i <= files; i++) {
558 		if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
559 			rc = EINVAL;
560 			ERROR("Error %d: path too long\n", rc);
561 			goto out;
562 		}
563 
564 		if (nth && ((i % nth) == 0))
565 			fprintf(stdout, "getxattr: %s\n", file);
566 
567 		for (j = 1; j <= xattrs; j++) {
568 			(void) sprintf(name, "user.%d", j);
569 
570 			rc = lgetxattr(file, name, value, XATTR_SIZE_MAX);
571 			if (rc == -1) {
572 				ERROR("Error %d: lgetxattr(%s, %s, ..., %d)\n",
573 				    errno, file, name, XATTR_SIZE_MAX);
574 				goto out;
575 			}
576 
577 			if (!verify)
578 				continue;
579 
580 			sscanf(value, "size=%d [a-z]", &rnd_size);
581 			shift = sprintf(verify_value, "size=%d ",
582 			    rnd_size);
583 			memcpy(verify_value + shift, xattrbytes,
584 			    sizeof (xattrbytes) - shift);
585 
586 			if (rnd_size != rc ||
587 			    memcmp(verify_value, value, rnd_size)) {
588 				ERROR("Error %d: verify failed\n "
589 				    "verify: %s\n value:  %s\n", EINVAL,
590 				    verify_string, value_string);
591 				rc = 1;
592 				goto out;
593 			}
594 		}
595 	}
596 
597 	(void) gettimeofday(&stop, NULL);
598 	seconds = timeval_sub_seconds(&stop, &start);
599 	fprintf(stdout, "getxattr: %f seconds %f getxattrs/second\n",
600 	    seconds, (files * xattrs) / seconds);
601 
602 	rc = post_hook("post");
603 out:
604 	if (file)
605 		free(file);
606 
607 	if (value)
608 		free(value);
609 
610 	if (verify_value)
611 		free(verify_value);
612 
613 	return (rc);
614 }
615 
616 static int
617 unlink_files(void)
618 {
619 	int i, rc;
620 	char *file = NULL;
621 	struct timeval start, stop;
622 	double seconds;
623 	size_t fsize;
624 
625 	fsize = PATH_MAX;
626 	file = malloc(fsize);
627 	if (file == NULL) {
628 		rc = ENOMEM;
629 		ERROR("Error %d: malloc(%d) bytes for file name\n",
630 		    rc, PATH_MAX);
631 		goto out;
632 	}
633 
634 	(void) gettimeofday(&start, NULL);
635 
636 	for (i = 1; i <= files; i++) {
637 		if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
638 			rc = EINVAL;
639 			ERROR("Error %d: path too long\n", rc);
640 			goto out;
641 		}
642 
643 		if (nth && ((i % nth) == 0))
644 			fprintf(stdout, "unlink: %s\n", file);
645 
646 		rc = unlink(file);
647 		if ((rc == -1) && (errno != ENOENT)) {
648 			ERROR("Error %d: unlink(%s)\n", errno, file);
649 			free(file);
650 			return (errno);
651 		}
652 	}
653 
654 	(void) gettimeofday(&stop, NULL);
655 	seconds = timeval_sub_seconds(&stop, &start);
656 	fprintf(stdout, "unlink:   %f seconds %f unlinks/second\n",
657 	    seconds, files / seconds);
658 
659 	rc = post_hook("post");
660 out:
661 	if (file)
662 		free(file);
663 
664 	return (rc);
665 }
666 
667 int
668 main(int argc, char **argv)
669 {
670 	int rc;
671 
672 	rc = parse_args(argc, argv);
673 	if (rc)
674 		return (rc);
675 
676 	if (value_is_random) {
677 		size_t rndsz = sizeof (xattrbytes);
678 
679 		rc = get_random_bytes(xattrbytes, rndsz);
680 		if (rc < rndsz) {
681 			ERROR("Error %d: get_random_bytes() wanted %zd "
682 			    "got %d\n", errno, rndsz, rc);
683 			return (rc);
684 		}
685 	} else {
686 		memset(xattrbytes, 'x', sizeof (xattrbytes));
687 	}
688 
689 	if (phase == PHASE_ALL || phase == PHASE_CREATE) {
690 		rc = create_files();
691 		if (rc)
692 			return (rc);
693 	}
694 
695 	if (phase == PHASE_ALL || phase == PHASE_SETXATTR) {
696 		rc = setxattrs();
697 		if (rc)
698 			return (rc);
699 	}
700 
701 	if (phase == PHASE_ALL || phase == PHASE_GETXATTR) {
702 		rc = getxattrs();
703 		if (rc)
704 			return (rc);
705 	}
706 
707 	if (!keep_files && (phase == PHASE_ALL || phase == PHASE_UNLINK)) {
708 		rc = unlink_files();
709 		if (rc)
710 			return (rc);
711 	}
712 
713 	return (0);
714 }
715