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
52static const char shortopts[] = "hvycdn:f:x:s:p:t:e:rRko:";
53static 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
73enum phases {
74	PHASE_ALL = 0,
75	PHASE_CREATE,
76	PHASE_SETXATTR,
77	PHASE_GETXATTR,
78	PHASE_UNLINK,
79	PHASE_INVAL
80};
81
82static int verbose = 0;
83static int verify = 0;
84static int synccaches = 0;
85static int dropcaches = 0;
86static int nth = 0;
87static int files = 1000;
88static int xattrs = 1;
89static int size = 6;
90static int size_is_random = 0;
91static int value_is_random = 0;
92static int keep_files = 0;
93static int phase = PHASE_ALL;
94static const char *path = "/tmp/xattrtest";
95static const char *script = "/bin/true";
96static char xattrbytes[XATTR_SIZE_MAX];
97
98static int
99usage(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
129static int
130parse_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
229static int
230drop_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
257static int
258run_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
291static int
292post_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
315static void
316timeval_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
332static void
333timeval_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
340static double
341timeval_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
349static int
350create_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");
408out:
409	if (file)
410		free(file);
411
412	return (rc);
413}
414
415static int
416get_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
438static int
439setxattrs(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");
502out:
503	if (file)
504		free(file);
505
506	if (value)
507		free(value);
508
509	return (rc);
510}
511
512static int
513getxattrs(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");
603out:
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
616static int
617unlink_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");
660out:
661	if (file)
662		free(file);
663
664	return (rc);
665}
666
667int
668main(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