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