xref: /freebsd/sys/contrib/openzfs/tests/zfs-tests/cmd/xattrtest.c (revision 809922b01004daf627ad4b8d92c7f98eb579043c)
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 http://www.opensolaris.org/os/licensing.
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 char path[PATH_MAX] = "/tmp/xattrtest";
95 static char script[PATH_MAX] = "/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 			strncpy(path, optarg, PATH_MAX);
165 			path[PATH_MAX - 1] = '\0';
166 			break;
167 		case 'c':
168 			synccaches = 1;
169 			break;
170 		case 'd':
171 			dropcaches = 1;
172 			break;
173 		case 't':
174 			strncpy(script, optarg, PATH_MAX);
175 			script[PATH_MAX - 1] = '\0';
176 			break;
177 		case 'e':
178 			seed = strtol(optarg, NULL, 0);
179 			break;
180 		case 'r':
181 			size_is_random = 1;
182 			break;
183 		case 'R':
184 			value_is_random = 1;
185 			break;
186 		case 'k':
187 			keep_files = 1;
188 			break;
189 		case 'o':
190 			phase = strtol(optarg, NULL, 0);
191 			if (phase <= PHASE_ALL || phase >= PHASE_INVAL) {
192 				fprintf(stderr, "Error: the -o value must be "
193 				    "greater than %d and less than %d\n",
194 				    PHASE_ALL, PHASE_INVAL);
195 				rc = 1;
196 			}
197 			break;
198 		default:
199 			rc = 1;
200 			break;
201 		}
202 	}
203 
204 	if (rc != 0)
205 		return (rc);
206 
207 	srandom(seed);
208 
209 	if (verbose) {
210 		fprintf(stdout, "verbose:          %d\n", verbose);
211 		fprintf(stdout, "verify:           %d\n", verify);
212 		fprintf(stdout, "nth:              %d\n", nth);
213 		fprintf(stdout, "files:            %d\n", files);
214 		fprintf(stdout, "xattrs:           %d\n", xattrs);
215 		fprintf(stdout, "size:             %d\n", size);
216 		fprintf(stdout, "path:             %s\n", path);
217 		fprintf(stdout, "synccaches:       %d\n", synccaches);
218 		fprintf(stdout, "dropcaches:       %d\n", dropcaches);
219 		fprintf(stdout, "script:           %s\n", script);
220 		fprintf(stdout, "seed:             %ld\n", seed);
221 		fprintf(stdout, "random size:      %d\n", size_is_random);
222 		fprintf(stdout, "random value:     %d\n", value_is_random);
223 		fprintf(stdout, "keep:             %d\n", keep_files);
224 		fprintf(stdout, "only:             %d\n", phase);
225 		fprintf(stdout, "%s", "\n");
226 	}
227 
228 	return (rc);
229 }
230 
231 static int
232 drop_caches(void)
233 {
234 	char file[] = "/proc/sys/vm/drop_caches";
235 	int fd, rc;
236 
237 	fd = open(file, O_WRONLY);
238 	if (fd == -1) {
239 		ERROR("Error %d: open(\"%s\", O_WRONLY)\n", errno, file);
240 		return (errno);
241 	}
242 
243 	rc = write(fd, "3", 1);
244 	if ((rc == -1) || (rc != 1)) {
245 		ERROR("Error %d: write(%d, \"3\", 1)\n", errno, fd);
246 		(void) close(fd);
247 		return (errno);
248 	}
249 
250 	rc = close(fd);
251 	if (rc == -1) {
252 		ERROR("Error %d: close(%d)\n", errno, fd);
253 		return (errno);
254 	}
255 
256 	return (0);
257 }
258 
259 static int
260 run_process(const char *path, char *argv[])
261 {
262 	pid_t pid;
263 	int rc, devnull_fd;
264 
265 	pid = fork();
266 	if (pid == 0) {
267 		devnull_fd = open("/dev/null", O_WRONLY);
268 
269 		if (devnull_fd < 0)
270 			_exit(-1);
271 
272 		(void) dup2(devnull_fd, STDOUT_FILENO);
273 		(void) dup2(devnull_fd, STDERR_FILENO);
274 		close(devnull_fd);
275 
276 		(void) execvp(path, argv);
277 		_exit(-1);
278 	} else if (pid > 0) {
279 		int status;
280 
281 		while ((rc = waitpid(pid, &status, 0)) == -1 &&
282 		    errno == EINTR) { }
283 
284 		if (rc < 0 || !WIFEXITED(status))
285 			return (-1);
286 
287 		return (WEXITSTATUS(status));
288 	}
289 
290 	return (-1);
291 }
292 
293 static int
294 post_hook(char *phase)
295 {
296 	char *argv[3] = { script, phase, (char *)0 };
297 	int rc;
298 
299 	if (synccaches)
300 		sync();
301 
302 	if (dropcaches) {
303 		rc = drop_caches();
304 		if (rc)
305 			return (rc);
306 	}
307 
308 	rc = run_process(script, argv);
309 	if (rc)
310 		return (rc);
311 
312 	return (0);
313 }
314 
315 #define	USEC_PER_SEC	1000000
316 
317 static void
318 timeval_normalize(struct timeval *tv, time_t sec, suseconds_t usec)
319 {
320 	while (usec >= USEC_PER_SEC) {
321 		usec -= USEC_PER_SEC;
322 		sec++;
323 	}
324 
325 	while (usec < 0) {
326 		usec += USEC_PER_SEC;
327 		sec--;
328 	}
329 
330 	tv->tv_sec = sec;
331 	tv->tv_usec = usec;
332 }
333 
334 static void
335 timeval_sub(struct timeval *delta, struct timeval *tv1, struct timeval *tv2)
336 {
337 	timeval_normalize(delta,
338 	    tv1->tv_sec - tv2->tv_sec,
339 	    tv1->tv_usec - tv2->tv_usec);
340 }
341 
342 static double
343 timeval_sub_seconds(struct timeval *tv1, struct timeval *tv2)
344 {
345 	struct timeval delta;
346 
347 	timeval_sub(&delta, tv1, tv2);
348 	return ((double)delta.tv_usec / USEC_PER_SEC + delta.tv_sec);
349 }
350 
351 static int
352 create_files(void)
353 {
354 	int i, rc;
355 	char *file = NULL;
356 	struct timeval start, stop;
357 	double seconds;
358 	size_t fsize;
359 
360 	fsize = PATH_MAX;
361 	file = malloc(fsize);
362 	if (file == NULL) {
363 		rc = ENOMEM;
364 		ERROR("Error %d: malloc(%d) bytes for file name\n", rc,
365 		    PATH_MAX);
366 		goto out;
367 	}
368 
369 	(void) gettimeofday(&start, NULL);
370 
371 	for (i = 1; i <= files; i++) {
372 		if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
373 			rc = EINVAL;
374 			ERROR("Error %d: path too long\n", rc);
375 			goto out;
376 		}
377 
378 		if (nth && ((i % nth) == 0))
379 			fprintf(stdout, "create: %s\n", file);
380 
381 		rc = unlink(file);
382 		if ((rc == -1) && (errno != ENOENT)) {
383 			ERROR("Error %d: unlink(%s)\n", errno, file);
384 			rc = errno;
385 			goto out;
386 		}
387 
388 		rc = open(file, O_CREAT, 0644);
389 		if (rc == -1) {
390 			ERROR("Error %d: open(%s, O_CREATE, 0644)\n",
391 			    errno, file);
392 			rc = errno;
393 			goto out;
394 		}
395 
396 		rc = close(rc);
397 		if (rc == -1) {
398 			ERROR("Error %d: close(%d)\n", errno, rc);
399 			rc = errno;
400 			goto out;
401 		}
402 	}
403 
404 	(void) gettimeofday(&stop, NULL);
405 	seconds = timeval_sub_seconds(&stop, &start);
406 	fprintf(stdout, "create:   %f seconds %f creates/second\n",
407 	    seconds, files / seconds);
408 
409 	rc = post_hook("post");
410 out:
411 	if (file)
412 		free(file);
413 
414 	return (rc);
415 }
416 
417 static int
418 get_random_bytes(char *buf, size_t bytes)
419 {
420 	int rand;
421 	ssize_t bytes_read = 0;
422 
423 	rand = open("/dev/urandom", O_RDONLY);
424 
425 	if (rand < 0)
426 		return (rand);
427 
428 	while (bytes_read < bytes) {
429 		ssize_t rc = read(rand, buf + bytes_read, bytes - bytes_read);
430 		if (rc < 0)
431 			break;
432 		bytes_read += rc;
433 	}
434 
435 	(void) close(rand);
436 
437 	return (bytes_read);
438 }
439 
440 static int
441 setxattrs(void)
442 {
443 	int i, j, rnd_size = size, shift, rc = 0;
444 	char name[XATTR_NAME_MAX];
445 	char *value = NULL;
446 	char *file = NULL;
447 	struct timeval start, stop;
448 	double seconds;
449 	size_t fsize;
450 
451 	value = malloc(XATTR_SIZE_MAX);
452 	if (value == NULL) {
453 		rc = ENOMEM;
454 		ERROR("Error %d: malloc(%d) bytes for xattr value\n", rc,
455 		    XATTR_SIZE_MAX);
456 		goto out;
457 	}
458 
459 	fsize = PATH_MAX;
460 	file = malloc(fsize);
461 	if (file == NULL) {
462 		rc = ENOMEM;
463 		ERROR("Error %d: malloc(%d) bytes for file name\n", rc,
464 		    PATH_MAX);
465 		goto out;
466 	}
467 
468 	(void) gettimeofday(&start, NULL);
469 
470 	for (i = 1; i <= files; i++) {
471 		if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
472 			rc = EINVAL;
473 			ERROR("Error %d: path too long\n", rc);
474 			goto out;
475 		}
476 
477 		if (nth && ((i % nth) == 0))
478 			fprintf(stdout, "setxattr: %s\n", file);
479 
480 		for (j = 1; j <= xattrs; j++) {
481 			if (size_is_random)
482 				rnd_size = (random() % (size - 16)) + 16;
483 
484 			(void) sprintf(name, "user.%d", j);
485 			shift = sprintf(value, "size=%d ", rnd_size);
486 			memcpy(value + shift, xattrbytes,
487 			    sizeof (xattrbytes) - shift);
488 
489 			rc = lsetxattr(file, name, value, rnd_size, 0);
490 			if (rc == -1) {
491 				ERROR("Error %d: lsetxattr(%s, %s, ..., %d)\n",
492 				    errno, file, name, rnd_size);
493 				goto out;
494 			}
495 		}
496 	}
497 
498 	(void) gettimeofday(&stop, NULL);
499 	seconds = timeval_sub_seconds(&stop, &start);
500 	fprintf(stdout, "setxattr: %f seconds %f setxattrs/second\n",
501 	    seconds, (files * xattrs) / seconds);
502 
503 	rc = post_hook("post");
504 out:
505 	if (file)
506 		free(file);
507 
508 	if (value)
509 		free(value);
510 
511 	return (rc);
512 }
513 
514 static int
515 getxattrs(void)
516 {
517 	int i, j, rnd_size, shift, rc = 0;
518 	char name[XATTR_NAME_MAX];
519 	char *verify_value = NULL;
520 	char *verify_string;
521 	char *value = NULL;
522 	char *value_string;
523 	char *file = NULL;
524 	struct timeval start, stop;
525 	double seconds;
526 	size_t fsize;
527 
528 	verify_value = malloc(XATTR_SIZE_MAX);
529 	if (verify_value == NULL) {
530 		rc = ENOMEM;
531 		ERROR("Error %d: malloc(%d) bytes for xattr verify\n", rc,
532 		    XATTR_SIZE_MAX);
533 		goto out;
534 	}
535 
536 	value = malloc(XATTR_SIZE_MAX);
537 	if (value == NULL) {
538 		rc = ENOMEM;
539 		ERROR("Error %d: malloc(%d) bytes for xattr value\n", rc,
540 		    XATTR_SIZE_MAX);
541 		goto out;
542 	}
543 
544 	verify_string = value_is_random ? "<random>" : verify_value;
545 	value_string = value_is_random ? "<random>" : value;
546 
547 	fsize = PATH_MAX;
548 	file = malloc(fsize);
549 
550 	if (file == NULL) {
551 		rc = ENOMEM;
552 		ERROR("Error %d: malloc(%d) bytes for file name\n", rc,
553 		    PATH_MAX);
554 		goto out;
555 	}
556 
557 	(void) gettimeofday(&start, NULL);
558 
559 	for (i = 1; i <= files; i++) {
560 		if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
561 			rc = EINVAL;
562 			ERROR("Error %d: path too long\n", rc);
563 			goto out;
564 		}
565 
566 		if (nth && ((i % nth) == 0))
567 			fprintf(stdout, "getxattr: %s\n", file);
568 
569 		for (j = 1; j <= xattrs; j++) {
570 			(void) sprintf(name, "user.%d", j);
571 
572 			rc = lgetxattr(file, name, value, XATTR_SIZE_MAX);
573 			if (rc == -1) {
574 				ERROR("Error %d: lgetxattr(%s, %s, ..., %d)\n",
575 				    errno, file, name, XATTR_SIZE_MAX);
576 				goto out;
577 			}
578 
579 			if (!verify)
580 				continue;
581 
582 			sscanf(value, "size=%d [a-z]", &rnd_size);
583 			shift = sprintf(verify_value, "size=%d ",
584 			    rnd_size);
585 			memcpy(verify_value + shift, xattrbytes,
586 			    sizeof (xattrbytes) - shift);
587 
588 			if (rnd_size != rc ||
589 			    memcmp(verify_value, value, rnd_size)) {
590 				ERROR("Error %d: verify failed\n "
591 				    "verify: %s\n value:  %s\n", EINVAL,
592 				    verify_string, value_string);
593 				rc = 1;
594 				goto out;
595 			}
596 		}
597 	}
598 
599 	(void) gettimeofday(&stop, NULL);
600 	seconds = timeval_sub_seconds(&stop, &start);
601 	fprintf(stdout, "getxattr: %f seconds %f getxattrs/second\n",
602 	    seconds, (files * xattrs) / seconds);
603 
604 	rc = post_hook("post");
605 out:
606 	if (file)
607 		free(file);
608 
609 	if (value)
610 		free(value);
611 
612 	if (verify_value)
613 		free(verify_value);
614 
615 	return (rc);
616 }
617 
618 static int
619 unlink_files(void)
620 {
621 	int i, rc;
622 	char *file = NULL;
623 	struct timeval start, stop;
624 	double seconds;
625 	size_t fsize;
626 
627 	fsize = PATH_MAX;
628 	file = malloc(fsize);
629 	if (file == NULL) {
630 		rc = ENOMEM;
631 		ERROR("Error %d: malloc(%d) bytes for file name\n",
632 		    rc, PATH_MAX);
633 		goto out;
634 	}
635 
636 	(void) gettimeofday(&start, NULL);
637 
638 	for (i = 1; i <= files; i++) {
639 		if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
640 			rc = EINVAL;
641 			ERROR("Error %d: path too long\n", rc);
642 			goto out;
643 		}
644 
645 		if (nth && ((i % nth) == 0))
646 			fprintf(stdout, "unlink: %s\n", file);
647 
648 		rc = unlink(file);
649 		if ((rc == -1) && (errno != ENOENT)) {
650 			ERROR("Error %d: unlink(%s)\n", errno, file);
651 			free(file);
652 			return (errno);
653 		}
654 	}
655 
656 	(void) gettimeofday(&stop, NULL);
657 	seconds = timeval_sub_seconds(&stop, &start);
658 	fprintf(stdout, "unlink:   %f seconds %f unlinks/second\n",
659 	    seconds, files / seconds);
660 
661 	rc = post_hook("post");
662 out:
663 	if (file)
664 		free(file);
665 
666 	return (rc);
667 }
668 
669 int
670 main(int argc, char **argv)
671 {
672 	int rc;
673 
674 	rc = parse_args(argc, argv);
675 	if (rc)
676 		return (rc);
677 
678 	if (value_is_random) {
679 		size_t rndsz = sizeof (xattrbytes);
680 
681 		rc = get_random_bytes(xattrbytes, rndsz);
682 		if (rc < rndsz) {
683 			ERROR("Error %d: get_random_bytes() wanted %zd "
684 			    "got %d\n", errno, rndsz, rc);
685 			return (rc);
686 		}
687 	} else {
688 		memset(xattrbytes, 'x', sizeof (xattrbytes));
689 	}
690 
691 	if (phase == PHASE_ALL || phase == PHASE_CREATE) {
692 		rc = create_files();
693 		if (rc)
694 			return (rc);
695 	}
696 
697 	if (phase == PHASE_ALL || phase == PHASE_SETXATTR) {
698 		rc = setxattrs();
699 		if (rc)
700 			return (rc);
701 	}
702 
703 	if (phase == PHASE_ALL || phase == PHASE_GETXATTR) {
704 		rc = getxattrs();
705 		if (rc)
706 			return (rc);
707 	}
708 
709 	if (!keep_files && (phase == PHASE_ALL || phase == PHASE_UNLINK)) {
710 		rc = unlink_files();
711 		if (rc)
712 			return (rc);
713 	}
714 
715 	return (0);
716 }
717