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