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