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
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 char path[PATH_MAX] = "/tmp/xattrtest";
95static char script[PATH_MAX] = "/bin/true";
96static char xattrbytes[XATTR_SIZE_MAX];
97
98static int
99usage(int argc, char **argv)
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	    argv[0]);
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(argc, argv));
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
231static int
232drop_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
259static int
260run_process(const char *path, char *argv[])
261{
262	pid_t pid;
263	int rc, devnull_fd;
264
265	pid = vfork();
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
293static int
294post_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
317static void
318timeval_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
334static void
335timeval_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
342static double
343timeval_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
351static int
352create_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");
410out:
411	if (file)
412		free(file);
413
414	return (rc);
415}
416
417static int
418get_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
440static int
441setxattrs(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");
504out:
505	if (file)
506		free(file);
507
508	if (value)
509		free(value);
510
511	return (rc);
512}
513
514static int
515getxattrs(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");
605out:
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
618static int
619unlink_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");
662out:
663	if (file)
664		free(file);
665
666	return (rc);
667}
668
669int
670main(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