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