1 // SPDX-License-Identifier: GPL-2.0+
2 /* Perform various tests on stat and statx output
3 * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
4 * Written by David Howells (dhowells@redhat.com)
15 #include <sys/types.h>
17 #include <sys/sysmacros.h>
20 static bool failed = false;
21 static bool is_verbose = 0;
22 static const char *prog;
23 static const char *testfile;
26 static struct statx ref;
27 static struct statx_timestamp origin;
28 static bool ref_set, origin_set;
31 * Field IDs, sorted for bsearch() on field_list[].
61 const char *name; /* Name on command line */
62 unsigned int mask_bit;
66 * List of fields, sorted for bsearch().
68 static const struct field field_list[nr__fields] = {
69 [stx_atime_tv_nsec] = { "stx_atime.tv_nsec", STATX_ATIME },
70 [stx_atime_tv_sec] = { "stx_atime.tv_sec", STATX_ATIME },
71 [stx_attributes] = { "stx_attributes", 0 },
72 [stx_blksize] = { "stx_blksize", 0 },
73 [stx_blocks] = { "stx_blocks", STATX_BLOCKS },
74 [stx_btime_tv_nsec] = { "stx_btime.tv_nsec", STATX_BTIME },
75 [stx_btime_tv_sec] = { "stx_btime.tv_sec", STATX_BTIME },
76 [stx_ctime_tv_nsec] = { "stx_ctime.tv_nsec", STATX_CTIME },
77 [stx_ctime_tv_sec] = { "stx_ctime.tv_sec", STATX_CTIME },
78 [stx_dev_major] = { "stx_dev_major", 0 },
79 [stx_dev_minor] = { "stx_dev_minor", 0 },
80 [stx_gid] = { "stx_gid", STATX_GID },
81 [stx_ino] = { "stx_ino", STATX_INO },
82 [stx_mask] = { "stx_mask", 0 },
83 [stx_mode] = { "stx_mode", STATX_MODE },
84 [stx_mtime_tv_nsec] = { "stx_mtime.tv_nsec", STATX_MTIME },
85 [stx_mtime_tv_sec] = { "stx_mtime.tv_sec", STATX_MTIME },
86 [stx_nlink] = { "stx_nlink", STATX_NLINK },
87 [stx_rdev_major] = { "stx_rdev_major", 0 },
88 [stx_rdev_minor] = { "stx_rdev_minor", 0 },
89 [stx_size] = { "stx_size", STATX_SIZE },
90 [stx_type] = { "stx_type", STATX_TYPE },
91 [stx_uid] = { "stx_uid", STATX_UID },
94 static int field_cmp(const void *_key, const void *_p)
96 const char *key = _key;
97 const struct field *p = _p;
98 return strcmp(key, p->name);
102 * Sorted list of attribute flags for bsearch().
109 static const struct attr_name attr_list[] = {
110 { "append", STATX_ATTR_APPEND },
111 { "automount", STATX_ATTR_AUTOMOUNT },
112 { "compressed", STATX_ATTR_COMPRESSED },
113 { "encrypted", STATX_ATTR_ENCRYPTED },
114 { "immutable", STATX_ATTR_IMMUTABLE },
115 { "nodump", STATX_ATTR_NODUMP },
118 static int attr_name_cmp(const void *_key, const void *_p)
120 const char *key = _key;
121 const struct attr_name *p = _p;
122 return strcmp(key, p->name);
131 * List of file types.
133 static const struct file_type file_types[] = {
137 { "block", S_IFBLK },
140 { "sock", S_IFSOCK },
144 static __attribute__((noreturn))
147 fprintf(stderr, "usage: %s --check-statx\n", prog);
148 fprintf(stderr, "usage: %s [-v] [-m<mask>] <testfile> [checks]\n", prog);
149 fprintf(stderr, "\t<mask> can be basic, all or a number; all is the default\n");
150 fprintf(stderr, "checks is a list of zero or more of:\n");
151 fprintf(stderr, "\tattr=[+-]<name> -- check an attribute in stx_attributes\n");
152 fprintf(stderr, "\t\tappend -- The file is marked as append only\n");
153 fprintf(stderr, "\t\tautomount -- The object is an automount point\n");
154 fprintf(stderr, "\t\tcompressed -- The file is marked as compressed\n");
155 fprintf(stderr, "\t\tencrypted -- The file is marked as encrypted\n");
156 fprintf(stderr, "\t\timmutable -- The file is marked as immutable\n");
157 fprintf(stderr, "\t\tnodump -- The file is marked as no-dump\n");
158 fprintf(stderr, "\tcmp_ref -- check that the reference file has identical stats\n");
159 fprintf(stderr, "\tref=<file> -- get reference stats from file\n");
160 fprintf(stderr, "\tstx_<field>=<val> -- statx field value check\n");
161 fprintf(stderr, "\tts=<a>,<b> -- timestamp a <= b, where a and b can each be one of:\n");
162 fprintf(stderr, "\t\t[abcm] -- the timestamps from testfile\n");
163 fprintf(stderr, "\t\t[ABCM] -- the timestamps from the reference file\n");
164 fprintf(stderr, "\t\t0 -- the origin timestamp\n");
165 fprintf(stderr, "\tts_origin=<sec>.<nsec> -- set the origin timestamp\n");
166 fprintf(stderr, "\tts_order -- check the timestamp order\n");
167 fprintf(stderr, "\t\t(for stx_type, fifo char dir, block, file, sym, sock can be used)\n");
171 static __attribute__((noreturn, format(printf, 1, 2)))
172 void bad_arg(const char *fmt, ...)
177 vfprintf(stderr, fmt, va);
182 static __attribute__((format(printf, 1, 2)))
183 void verbose(const char *fmt, ...)
189 fputs(" - ", stdout);
195 static __attribute__((format(printf, 2, 3)))
196 void check(bool good, const char *fmt, ...)
202 fputs("[!] ", stdout);
210 * Compare the contents of a statx struct with that of a stat struct and check
211 * that they're the same.
213 static void cmp_statx(const struct statx *stx, const struct stat *st)
215 #define cmp(fmt, x) \
217 check(stx->stx_##x == st->st_##x, \
218 "stat.%s differs, "fmt" != "fmt"\n", \
220 (unsigned long long)stx->stx_##x, \
221 (unsigned long long)st->st_##x); \
224 cmp("%llu", blksize);
235 check(stx->stx_##x##_major == major(st->st_##x), \
236 "stat.%s.major differs, %u != %u\n", \
238 stx->stx_##x##_major, \
239 major(st->st_##x)); \
240 check(stx->stx_##x##_minor == minor(st->st_##x), \
241 "stat.%s.minor differs, %u != %u\n", \
243 stx->stx_##x##_minor, \
244 minor(st->st_##x)); \
252 check(stx->stx_##x##time.tv_sec == st->st_##x##tim.tv_sec, \
253 "stat.%stime.tv_sec differs, %lld != %lld\n", \
255 (long long)stx->stx_##x##time.tv_sec, \
256 (long long)st->st_##x##tim.tv_sec); \
257 check(stx->stx_##x##time.tv_nsec == st->st_##x##tim.tv_nsec, \
258 "stat.%stime.tv_nsec differs, %lld != %lld\n", \
260 (long long)stx->stx_##x##time.tv_nsec, \
261 (long long)st->st_##x##tim.tv_nsec); \
270 * Set origin timestamp from a "<sec>.<nsec>" string.
272 static void set_origin_timestamp(const char *arg)
277 switch (sscanf(arg, "%lld.%d", &sec, &nsec)) {
279 bad_arg("ts_origin= missing seconds value");
281 bad_arg("ts_origin= missing nanoseconds value");
284 origin.tv_nsec = nsec;
291 * Get reference stats from a file.
293 static void get_reference(const char *file, unsigned int mask)
298 bad_arg("ref= requires a filename\n");
300 memset(&ref, 0xfb, sizeof(ref));
301 ret = xfstests_statx(AT_FDCWD, file, AT_SYMLINK_NOFOLLOW, mask, &ref);
310 fprintf(stderr, "Unexpected return %d from statx()\n", ret);
316 * Check a pair of timestamps.
318 static void check_earlier(const struct statx_timestamp *A,
319 const struct statx_timestamp *B,
324 check((B->tv_sec - A->tv_sec) >= 0,
325 "%s.sec is before %s.sec (%lld < %lld)\n",
326 B_name, A_name, B->tv_sec, A->tv_sec);
328 if (B->tv_sec == A->tv_sec)
329 check((B->tv_nsec - A->tv_nsec) >= 0,
330 "%s.nsec is before %s.nsec (%d < %d)\n",
331 B_name, A_name, B->tv_nsec, A->tv_nsec);
335 * Check that the timestamps are reasonably ordered.
337 * We require the following to hold true immediately after creation if the
338 * relevant timestamps exist on the filesystem:
341 * btime <= mtime <= ctime
343 static void check_timestamp_order(const struct statx *stx)
345 if ((stx->stx_mask & (STATX_BTIME | STATX_ATIME)) == (STATX_BTIME | STATX_ATIME))
346 check_earlier(&stx->stx_btime, &stx->stx_atime, "btime", "atime");
347 if ((stx->stx_mask & (STATX_BTIME | STATX_MTIME)) == (STATX_BTIME | STATX_MTIME))
348 check_earlier(&stx->stx_btime, &stx->stx_mtime, "btime", "mtime");
349 if ((stx->stx_mask & (STATX_BTIME | STATX_CTIME)) == (STATX_BTIME | STATX_CTIME))
350 check_earlier(&stx->stx_btime, &stx->stx_ctime, "btime", "ctime");
351 if ((stx->stx_mask & (STATX_MTIME | STATX_CTIME)) == (STATX_MTIME | STATX_CTIME))
352 check_earlier(&stx->stx_mtime, &stx->stx_ctime, "mtime", "ctime");
356 * Check that the second timestamp is the same as or after the first timestamp.
358 static void check_timestamp(const struct statx *stx, char *arg)
360 const struct statx_timestamp *a, *b;
364 if (strlen(arg) != 3 || arg[1] != ',')
365 bad_arg("ts= requires <a>,<b>\n");
368 case 'a': a = &stx->stx_atime; an = "atime"; mask = STATX_ATIME; break;
369 case 'b': a = &stx->stx_btime; an = "btime"; mask = STATX_BTIME; break;
370 case 'c': a = &stx->stx_ctime; an = "ctime"; mask = STATX_CTIME; break;
371 case 'm': a = &stx->stx_mtime; an = "mtime"; mask = STATX_MTIME; break;
372 case 'A': a = &ref.stx_atime; an = "ref_a"; mask = STATX_ATIME; break;
373 case 'B': a = &ref.stx_btime; an = "ref_b"; mask = STATX_BTIME; break;
374 case 'C': a = &ref.stx_ctime; an = "ref_c"; mask = STATX_CTIME; break;
375 case 'M': a = &ref.stx_mtime; an = "ref_m"; mask = STATX_MTIME; break;
376 case '0': a = &origin; an = "origin"; mask = 0; break;
378 bad_arg("ts= timestamp '%c' not supported\n", arg[0]);
383 bad_arg("ts= timestamp '%c' requires origin= first\n", arg[0]);
384 } else if (arg[0] <= 'Z') {
386 bad_arg("ts= timestamp '%c' requires ref= first\n", arg[0]);
387 if (!(ref.stx_mask & mask))
390 if (!(stx->stx_mask & mask))
395 case 'a': b = &stx->stx_atime; bn = "atime"; mask = STATX_ATIME; break;
396 case 'b': b = &stx->stx_btime; bn = "btime"; mask = STATX_BTIME; break;
397 case 'c': b = &stx->stx_ctime; bn = "ctime"; mask = STATX_CTIME; break;
398 case 'm': b = &stx->stx_mtime; bn = "mtime"; mask = STATX_MTIME; break;
399 case 'A': b = &ref.stx_atime; bn = "ref_a"; mask = STATX_ATIME; break;
400 case 'B': b = &ref.stx_btime; bn = "ref_b"; mask = STATX_BTIME; break;
401 case 'C': b = &ref.stx_ctime; bn = "ref_c"; mask = STATX_CTIME; break;
402 case 'M': b = &ref.stx_mtime; bn = "ref_m"; mask = STATX_MTIME; break;
403 case '0': b = &origin; bn = "origin"; mask = 0; break;
405 bad_arg("ts= timestamp '%c' not supported\n", arg[2]);
410 bad_arg("ts= timestamp '%c' requires origin= first\n", arg[0]);
411 } else if (arg[2] <= 'Z') {
413 bad_arg("ts= timestamp '%c' requires ref= first\n", arg[2]);
414 if (!(ref.stx_mask & mask))
417 if (!(stx->stx_mask & mask))
421 verbose("check %s <= %s\n", an, bn);
422 check_earlier(a, b, an, bn);
426 * Compare to reference file.
428 static void cmp_ref(const struct statx *stx, unsigned int mask)
431 #define cmp(fmt, x) \
433 check(stx->x == ref.x, \
434 "attr '%s' differs from ref file, "fmt" != "fmt"\n", \
436 (unsigned long long)stx->x, \
437 (unsigned long long)ref.x); \
440 cmp("%llx", stx_mask);
441 cmp("%llx", stx_attributes);
442 cmp("%llu", stx_blksize);
443 cmp("%llu", stx_attributes);
444 cmp("%llu", stx_nlink);
445 cmp("%llu", stx_uid);
446 cmp("%llu", stx_gid);
447 cmp("%llo", stx_mode);
448 cmp("%llu", stx_ino);
449 cmp("%llu", stx_size);
450 cmp("%llu", stx_blocks);
451 cmp("%lld", stx_atime.tv_sec);
452 cmp("%lld", stx_atime.tv_nsec);
453 cmp("%lld", stx_btime.tv_sec);
454 cmp("%lld", stx_btime.tv_nsec);
455 cmp("%lld", stx_ctime.tv_sec);
456 cmp("%lld", stx_ctime.tv_nsec);
457 cmp("%lld", stx_mtime.tv_sec);
458 cmp("%lld", stx_mtime.tv_nsec);
459 cmp("%llu", stx_rdev_major);
460 cmp("%llu", stx_rdev_minor);
461 cmp("%llu", stx_dev_major);
462 cmp("%llu", stx_dev_minor);
466 * Check an field restriction. Specified on the command line as a key=val pair
467 * in the checks section. For instance:
472 static void check_field(const struct statx *stx, char *arg)
474 const struct file_type *type;
475 const struct field *field;
476 unsigned long long ucheck, uval = 0;
477 long long scheck, sval = 0;
480 verbose("check %s\n", arg);
483 val = strchr(key, '=');
485 bad_arg("%s check requires value\n", key);
488 field = bsearch(key, field_list, nr__fields, sizeof(*field), field_cmp);
490 bad_arg("Field '%s' not supported\n", key);
492 /* Read the stat information specified by the key. */
493 switch ((enum fields)(field - field_list)) {
494 case stx_mask: uval = stx->stx_mask; break;
495 case stx_blksize: uval = stx->stx_blksize; break;
496 case stx_attributes: uval = stx->stx_attributes; break;
497 case stx_nlink: uval = stx->stx_nlink; break;
498 case stx_uid: uval = stx->stx_uid; break;
499 case stx_gid: uval = stx->stx_gid; break;
500 case stx_type: uval = stx->stx_mode & ~07777; break;
501 case stx_mode: uval = stx->stx_mode & 07777; break;
502 case stx_ino: uval = stx->stx_ino; break;
503 case stx_size: uval = stx->stx_size; break;
504 case stx_blocks: uval = stx->stx_blocks; break;
505 case stx_rdev_major: uval = stx->stx_rdev_major; break;
506 case stx_rdev_minor: uval = stx->stx_rdev_minor; break;
507 case stx_dev_major: uval = stx->stx_dev_major; break;
508 case stx_dev_minor: uval = stx->stx_dev_minor; break;
510 case stx_atime_tv_sec: sval = stx->stx_atime.tv_sec; break;
511 case stx_atime_tv_nsec: sval = stx->stx_atime.tv_nsec; break;
512 case stx_btime_tv_sec: sval = stx->stx_btime.tv_sec; break;
513 case stx_btime_tv_nsec: sval = stx->stx_btime.tv_nsec; break;
514 case stx_ctime_tv_sec: sval = stx->stx_ctime.tv_sec; break;
515 case stx_ctime_tv_nsec: sval = stx->stx_ctime.tv_nsec; break;
516 case stx_mtime_tv_sec: sval = stx->stx_mtime.tv_sec; break;
517 case stx_mtime_tv_nsec: sval = stx->stx_mtime.tv_nsec; break;
522 /* Parse the specified value as signed or unsigned as
523 * appropriate and compare to the stat information.
525 switch ((enum fields)(field - field_list)) {
528 ucheck = strtoull(val, &p, 0);
530 bad_arg("Field '%s' requires unsigned integer\n", key);
531 check(uval == ucheck,
532 "%s differs, 0x%llx != 0x%llx\n", key, uval, ucheck);
536 for (type = file_types; type->name; type++) {
537 if (strcmp(type->name, val) == 0) {
546 ucheck = strtoull(val, &p, 0);
548 bad_arg("Field '%s' requires unsigned integer\n", key);
550 check(uval == ucheck,
551 "%s differs, 0%llo != 0%llo\n", key, uval, ucheck);
565 ucheck = strtoull(val, &p, 0);
567 bad_arg("Field '%s' requires unsigned integer\n", key);
568 check(uval == ucheck,
569 "%s differs, %llu != %llu\n", key, uval, ucheck);
572 case stx_atime_tv_sec:
573 case stx_atime_tv_nsec:
574 case stx_btime_tv_sec:
575 case stx_btime_tv_nsec:
576 case stx_ctime_tv_sec:
577 case stx_ctime_tv_nsec:
578 case stx_mtime_tv_sec:
579 case stx_mtime_tv_nsec:
580 scheck = strtoll(val, &p, 0);
582 bad_arg("Field '%s' requires integer\n", key);
583 check(sval == scheck,
584 "%s differs, %lld != %lld\n", key, sval, scheck);
593 * Check attributes in stx_attributes. When stx_attributes_mask gets in
594 * upstream, we will need to consider that also.
596 static void check_attribute(const struct statx *stx, char *arg)
598 const struct attr_name *p;
602 verbose("check attr %s\n", arg);
604 case '+': set = true; break;
605 case '-': set = false; break;
607 bad_arg("attr flag must be marked + (set) or - (unset)\n");
611 p = bsearch(arg, attr_list, sizeof(attr_list) / sizeof(attr_list[0]),
612 sizeof(attr_list[0]), attr_name_cmp);
614 bad_arg("Unrecognised attr name '%s'\n", arg);
618 check((stx->stx_attributes & attr) == attr,
619 "Attribute %s should be set\n", arg);
621 check((stx->stx_attributes & attr) == 0,
622 "Attribute %s should be unset\n", arg);
629 int main(int argc, char **argv)
633 unsigned int mask = STATX_ALL;
634 unsigned int atflags = AT_STATX_SYNC_AS_STAT;
638 if (argc == 2 && strcmp(argv[1], "--check-statx") == 0) {
640 return (xfstests_statx(AT_FDCWD, "/", 0, 0, &stx) == -1 &&
641 errno == ENOSYS) ? 1 : 0;
645 while (c = getopt(argc, argv, "+DFm:v"),
650 atflags &= ~AT_STATX_SYNC_TYPE;
651 atflags |= AT_STATX_FORCE_SYNC;
654 atflags &= ~AT_STATX_SYNC_TYPE;
655 atflags |= AT_STATX_DONT_SYNC;
658 if (strcmp(optarg, "basic") == 0) {
659 mask = STATX_BASIC_STATS;
660 } else if (strcmp(optarg, "all") == 0) {
663 mask = strtoul(optarg, &p, 0);
683 /* Gather the stats. We want both statx and stat so that we can
684 * compare what's in the buffers.
686 verbose("call statx %s\n", testfile);
687 memset(&stx, 0xfb, sizeof(stx));
688 ret = xfstests_statx(AT_FDCWD, testfile, atflags | AT_SYMLINK_NOFOLLOW,
697 fprintf(stderr, "Unexpected return %d from statx()\n", ret);
701 verbose("call stat %s\n", testfile);
702 ret = fstatat(AT_FDCWD, testfile, &st, AT_SYMLINK_NOFOLLOW);
710 fprintf(stderr, "Unexpected return %d from stat()\n", ret);
714 verbose("compare statx and stat\n");
715 cmp_statx(&stx, &st);
717 /* Display the available timestamps */
718 verbose("begin time %llu.%09u\n", origin.tv_sec, origin.tv_nsec);
719 if (stx.stx_mask & STATX_BTIME)
720 verbose(" btime %llu.%09u\n", stx.stx_btime.tv_sec, stx.stx_btime.tv_nsec);
721 if (stx.stx_mask & STATX_ATIME)
722 verbose(" atime %llu.%09u\n", stx.stx_atime.tv_sec, stx.stx_atime.tv_nsec);
723 if (stx.stx_mask & STATX_MTIME)
724 verbose(" mtime %llu.%09u\n", stx.stx_mtime.tv_sec, stx.stx_mtime.tv_nsec);
725 if (stx.stx_mask & STATX_CTIME)
726 verbose(" ctime %llu.%09u\n", stx.stx_ctime.tv_sec, stx.stx_ctime.tv_nsec);
728 /* Handle additional checks the user specified */
729 for (; *argv; argv++) {
732 if (strncmp("attr=", arg, 5) == 0) {
733 /* attr=[+-]<attr> - check attribute flag */
734 check_attribute(&stx, arg + 5);
738 if (strcmp("cmp_ref", arg) == 0) {
739 /* cmp_ref - check ref file has same stats */
744 if (strncmp(arg, "stx_", 4) == 0) {
745 /* stx_<field>=<n> - check field set to n */
746 check_field(&stx, *argv);
750 if (strncmp("ref=", arg, 4) == 0) {
751 /* ref=<file> - set reference stats from file */
752 get_reference(arg + 4, mask);
756 if (strcmp("ts_order", arg) == 0) {
757 /* ts_order - check timestamp order */
758 check_timestamp_order(&stx);
762 if (strncmp("ts_origin=", arg, 10) == 0) {
763 /* ts_origin=<sec>.<nsec> - set origin timestamp */
764 set_origin_timestamp(arg + 10);
768 if (strncmp("ts=", arg, 3) == 0) {
769 /* ts=<a>,<b> - check timestamp b is same as a or after */
770 check_timestamp(&stx, arg + 3);
774 bad_arg("check '%s' not supported\n", arg);
782 verbose("Success\n");