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