1 /* 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)
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public Licence
8 * as published by the Free Software Foundation; either version
9 * 2 of the Licence, or (at your option) any later version.
20 #include <sys/types.h>
22 #include <sys/sysmacros.h>
25 static bool failed = false;
26 static bool is_verbose = 0;
27 static const char *prog;
28 static const char *testfile;
31 static struct statx ref;
32 static struct statx_timestamp origin;
33 static bool ref_set, origin_set;
36 * Field IDs, sorted for bsearch() on field_list[].
66 const char *name; /* Name on command line */
67 unsigned int mask_bit;
71 * List of fields, sorted for bsearch().
73 static const struct field field_list[nr__fields] = {
74 [stx_atime_tv_nsec] = { "stx_atime.tv_nsec", STATX_ATIME },
75 [stx_atime_tv_sec] = { "stx_atime.tv_sec", STATX_ATIME },
76 [stx_attributes] = { "stx_attributes", 0 },
77 [stx_blksize] = { "stx_blksize", 0 },
78 [stx_blocks] = { "stx_blocks", STATX_BLOCKS },
79 [stx_btime_tv_nsec] = { "stx_btime.tv_nsec", STATX_BTIME },
80 [stx_btime_tv_sec] = { "stx_btime.tv_sec", STATX_BTIME },
81 [stx_ctime_tv_nsec] = { "stx_ctime.tv_nsec", STATX_CTIME },
82 [stx_ctime_tv_sec] = { "stx_ctime.tv_sec", STATX_CTIME },
83 [stx_dev_major] = { "stx_dev_major", 0 },
84 [stx_dev_minor] = { "stx_dev_minor", 0 },
85 [stx_gid] = { "stx_gid", STATX_GID },
86 [stx_ino] = { "stx_ino", STATX_INO },
87 [stx_mask] = { "stx_mask", 0 },
88 [stx_mode] = { "stx_mode", STATX_MODE },
89 [stx_mtime_tv_nsec] = { "stx_mtime.tv_nsec", STATX_MTIME },
90 [stx_mtime_tv_sec] = { "stx_mtime.tv_sec", STATX_MTIME },
91 [stx_nlink] = { "stx_nlink", STATX_NLINK },
92 [stx_rdev_major] = { "stx_rdev_major", 0 },
93 [stx_rdev_minor] = { "stx_rdev_minor", 0 },
94 [stx_size] = { "stx_size", STATX_SIZE },
95 [stx_type] = { "stx_type", STATX_TYPE },
96 [stx_uid] = { "stx_uid", STATX_UID },
99 static int field_cmp(const void *_key, const void *_p)
101 const char *key = _key;
102 const struct field *p = _p;
103 return strcmp(key, p->name);
107 * Sorted list of attribute flags for bsearch().
114 static const struct attr_name attr_list[] = {
115 { "append", STATX_ATTR_APPEND },
116 { "automount", STATX_ATTR_AUTOMOUNT },
117 { "compressed", STATX_ATTR_COMPRESSED },
118 { "encrypted", STATX_ATTR_ENCRYPTED },
119 { "immutable", STATX_ATTR_IMMUTABLE },
120 { "nodump", STATX_ATTR_NODUMP },
123 static int attr_name_cmp(const void *_key, const void *_p)
125 const char *key = _key;
126 const struct attr_name *p = _p;
127 return strcmp(key, p->name);
136 * List of file types.
138 static const struct file_type file_types[] = {
142 { "block", S_IFBLK },
145 { "sock", S_IFSOCK },
149 static __attribute__((noreturn))
152 fprintf(stderr, "usage: %s --check-statx\n", prog);
153 fprintf(stderr, "usage: %s [-v] [-m<mask>] <testfile> [checks]\n", prog);
154 fprintf(stderr, "\t<mask> can be basic, all or a number; all is the default\n");
155 fprintf(stderr, "checks is a list of zero or more of:\n");
156 fprintf(stderr, "\tattr=[+-]<name> -- check an attribute in stx_attributes\n");
157 fprintf(stderr, "\t\tappend -- The file is marked as append only\n");
158 fprintf(stderr, "\t\tautomount -- The object is an automount point\n");
159 fprintf(stderr, "\t\tcompressed -- The file is marked as compressed\n");
160 fprintf(stderr, "\t\tencrypted -- The file is marked as encrypted\n");
161 fprintf(stderr, "\t\timmutable -- The file is marked as immutable\n");
162 fprintf(stderr, "\t\tnodump -- The file is marked as no-dump\n");
163 fprintf(stderr, "\tcmp_ref -- check that the reference file has identical stats\n");
164 fprintf(stderr, "\tref=<file> -- get reference stats from file\n");
165 fprintf(stderr, "\tstx_<field>=<val> -- statx field value check\n");
166 fprintf(stderr, "\tts=<a>,<b> -- timestamp a <= b, where a and b can each be one of:\n");
167 fprintf(stderr, "\t\t[abcm] -- the timestamps from testfile\n");
168 fprintf(stderr, "\t\t[ABCM] -- the timestamps from the reference file\n");
169 fprintf(stderr, "\t\t0 -- the origin timestamp\n");
170 fprintf(stderr, "\tts_origin=<sec>.<nsec> -- set the origin timestamp\n");
171 fprintf(stderr, "\tts_order -- check the timestamp order\n");
172 fprintf(stderr, "\t\t(for stx_type, fifo char dir, block, file, sym, sock can be used)\n");
176 static __attribute__((noreturn, format(printf, 1, 2)))
177 void bad_arg(const char *fmt, ...)
182 vfprintf(stderr, fmt, va);
187 static __attribute__((format(printf, 1, 2)))
188 void verbose(const char *fmt, ...)
194 fputs(" - ", stdout);
200 static __attribute__((format(printf, 2, 3)))
201 void check(bool good, const char *fmt, ...)
207 fputs("[!] ", stdout);
215 * Compare the contents of a statx struct with that of a stat struct and check
216 * that they're the same.
218 static void cmp_statx(const struct statx *stx, const struct stat *st)
220 #define cmp(fmt, x) \
222 check(stx->stx_##x == st->st_##x, \
223 "stat.%s differs, "fmt" != "fmt"\n", \
225 (unsigned long long)stx->stx_##x, \
226 (unsigned long long)st->st_##x); \
229 cmp("%llu", blksize);
240 check(stx->stx_##x##_major == major(st->st_##x), \
241 "stat.%s.major differs, %u != %u\n", \
243 stx->stx_##x##_major, \
244 major(st->st_##x)); \
245 check(stx->stx_##x##_minor == minor(st->st_##x), \
246 "stat.%s.minor differs, %u != %u\n", \
248 stx->stx_##x##_minor, \
249 minor(st->st_##x)); \
257 check(stx->stx_##x##time.tv_sec == st->st_##x##tim.tv_sec, \
258 "stat.%stime.tv_sec differs, %lld != %lld\n", \
260 (long long)stx->stx_##x##time.tv_sec, \
261 (long long)st->st_##x##tim.tv_sec); \
262 check(stx->stx_##x##time.tv_nsec == st->st_##x##tim.tv_nsec, \
263 "stat.%stime.tv_nsec differs, %lld != %lld\n", \
265 (long long)stx->stx_##x##time.tv_nsec, \
266 (long long)st->st_##x##tim.tv_nsec); \
275 * Set origin timestamp from a "<sec>.<nsec>" string.
277 static void set_origin_timestamp(const char *arg)
282 switch (sscanf(arg, "%lld.%d", &sec, &nsec)) {
284 bad_arg("ts_origin= missing seconds value");
286 bad_arg("ts_origin= missing nanoseconds value");
289 origin.tv_nsec = nsec;
296 * Get reference stats from a file.
298 static void get_reference(const char *file)
303 bad_arg("ref= requires a filename\n");
305 memset(&ref, 0xfb, sizeof(ref));
306 ret = xfstests_statx(AT_FDCWD, file, AT_SYMLINK_NOFOLLOW,
307 STATX_ATIME | STATX_BTIME | STATX_CTIME | STATX_MTIME,
317 fprintf(stderr, "Unexpected return %d from statx()\n", ret);
323 * Check a pair of timestamps.
325 static void check_earlier(const struct statx_timestamp *A,
326 const struct statx_timestamp *B,
331 check((B->tv_sec - A->tv_sec) >= 0,
332 "%s.sec is before %s.sec (%lld < %lld)\n",
333 B_name, A_name, B->tv_sec, A->tv_sec);
335 if (B->tv_sec == A->tv_sec)
336 check((B->tv_nsec - A->tv_nsec) >= 0,
337 "%s.nsec is before %s.nsec (%d < %d)\n",
338 B_name, A_name, B->tv_nsec, A->tv_nsec);
342 * Check that the timestamps are reasonably ordered.
344 * We require the following to hold true immediately after creation if the
345 * relevant timestamps exist on the filesystem:
348 * btime <= mtime <= ctime
350 static void check_timestamp_order(const struct statx *stx)
352 if ((stx->stx_mask & (STATX_BTIME | STATX_ATIME)) == (STATX_BTIME | STATX_ATIME))
353 check_earlier(&stx->stx_btime, &stx->stx_atime, "btime", "atime");
354 if ((stx->stx_mask & (STATX_BTIME | STATX_MTIME)) == (STATX_BTIME | STATX_MTIME))
355 check_earlier(&stx->stx_btime, &stx->stx_mtime, "btime", "mtime");
356 if ((stx->stx_mask & (STATX_BTIME | STATX_CTIME)) == (STATX_BTIME | STATX_CTIME))
357 check_earlier(&stx->stx_btime, &stx->stx_ctime, "btime", "ctime");
358 if ((stx->stx_mask & (STATX_MTIME | STATX_CTIME)) == (STATX_MTIME | STATX_CTIME))
359 check_earlier(&stx->stx_mtime, &stx->stx_ctime, "mtime", "ctime");
363 * Check that the second timestamp is the same as or after the first timestamp.
365 static void check_timestamp(const struct statx *stx, char *arg)
367 const struct statx_timestamp *a, *b;
371 if (strlen(arg) != 3 || arg[1] != ',')
372 bad_arg("ts= requires <a>,<b>\n");
375 case 'a': a = &stx->stx_atime; an = "atime"; mask = STATX_ATIME; break;
376 case 'b': a = &stx->stx_btime; an = "btime"; mask = STATX_BTIME; break;
377 case 'c': a = &stx->stx_ctime; an = "ctime"; mask = STATX_CTIME; break;
378 case 'm': a = &stx->stx_mtime; an = "mtime"; mask = STATX_MTIME; break;
379 case 'A': a = &ref.stx_atime; an = "ref_a"; mask = STATX_ATIME; break;
380 case 'B': a = &ref.stx_btime; an = "ref_b"; mask = STATX_BTIME; break;
381 case 'C': a = &ref.stx_ctime; an = "ref_c"; mask = STATX_CTIME; break;
382 case 'M': a = &ref.stx_mtime; an = "ref_m"; mask = STATX_MTIME; break;
383 case '0': a = &origin; an = "origin"; mask = 0; break;
385 bad_arg("ts= timestamp '%c' not supported\n", arg[0]);
390 bad_arg("ts= timestamp '%c' requires origin= first\n", arg[0]);
391 } else if (arg[0] <= 'Z') {
393 bad_arg("ts= timestamp '%c' requires ref= first\n", arg[0]);
394 if (!(ref.stx_mask & mask))
397 if (!(stx->stx_mask & mask))
402 case 'a': b = &stx->stx_atime; bn = "atime"; mask = STATX_ATIME; break;
403 case 'b': b = &stx->stx_btime; bn = "btime"; mask = STATX_BTIME; break;
404 case 'c': b = &stx->stx_ctime; bn = "ctime"; mask = STATX_CTIME; break;
405 case 'm': b = &stx->stx_mtime; bn = "mtime"; mask = STATX_MTIME; break;
406 case 'A': b = &ref.stx_atime; bn = "ref_a"; mask = STATX_ATIME; break;
407 case 'B': b = &ref.stx_btime; bn = "ref_b"; mask = STATX_BTIME; break;
408 case 'C': b = &ref.stx_ctime; bn = "ref_c"; mask = STATX_CTIME; break;
409 case 'M': b = &ref.stx_mtime; bn = "ref_m"; mask = STATX_MTIME; break;
410 case '0': b = &origin; bn = "origin"; mask = 0; break;
412 bad_arg("ts= timestamp '%c' not supported\n", arg[2]);
417 bad_arg("ts= timestamp '%c' requires origin= first\n", arg[0]);
418 } else if (arg[2] <= 'Z') {
420 bad_arg("ts= timestamp '%c' requires ref= first\n", arg[2]);
421 if (!(ref.stx_mask & mask))
424 if (!(stx->stx_mask & mask))
428 verbose("check %s <= %s\n", an, bn);
429 check_earlier(a, b, an, bn);
433 * Compare to reference file.
435 static void cmp_ref(const struct statx *stx, unsigned int mask)
438 #define cmp(fmt, x) \
440 check(stx->x == ref.x, \
441 "attr '%s' differs from ref file, "fmt" != "fmt"\n", \
443 (unsigned long long)stx->x, \
444 (unsigned long long)ref.x); \
447 cmp("%llx", stx_mask);
448 cmp("%llx", stx_attributes);
449 cmp("%llu", stx_blksize);
450 cmp("%llu", stx_attributes);
451 cmp("%llu", stx_nlink);
452 cmp("%llu", stx_uid);
453 cmp("%llu", stx_gid);
454 cmp("%llo", stx_mode);
455 cmp("%llu", stx_ino);
456 cmp("%llu", stx_size);
457 cmp("%llu", stx_blocks);
458 cmp("%lld", stx_atime.tv_sec);
459 cmp("%lld", stx_atime.tv_nsec);
460 cmp("%lld", stx_btime.tv_sec);
461 cmp("%lld", stx_btime.tv_nsec);
462 cmp("%lld", stx_ctime.tv_sec);
463 cmp("%lld", stx_ctime.tv_nsec);
464 cmp("%lld", stx_mtime.tv_sec);
465 cmp("%lld", stx_mtime.tv_nsec);
466 cmp("%llu", stx_rdev_major);
467 cmp("%llu", stx_rdev_minor);
468 cmp("%llu", stx_dev_major);
469 cmp("%llu", stx_dev_minor);
473 * Check an field restriction. Specified on the command line as a key=val pair
474 * in the checks section. For instance:
479 static void check_field(const struct statx *stx, char *arg)
481 const struct file_type *type;
482 const struct field *field;
483 unsigned long long ucheck, uval = 0;
484 long long scheck, sval = 0;
487 verbose("check %s\n", arg);
490 val = strchr(key, '=');
492 bad_arg("%s check requires value\n", key);
495 field = bsearch(key, field_list, nr__fields, sizeof(*field), field_cmp);
497 bad_arg("Field '%s' not supported\n", key);
499 /* Read the stat information specified by the key. */
500 switch ((enum fields)(field - field_list)) {
501 case stx_mask: uval = stx->stx_mask; break;
502 case stx_blksize: uval = stx->stx_blksize; break;
503 case stx_attributes: uval = stx->stx_attributes; break;
504 case stx_nlink: uval = stx->stx_nlink; break;
505 case stx_uid: uval = stx->stx_uid; break;
506 case stx_gid: uval = stx->stx_gid; break;
507 case stx_type: uval = stx->stx_mode & ~07777; break;
508 case stx_mode: uval = stx->stx_mode & 07777; break;
509 case stx_ino: uval = stx->stx_ino; break;
510 case stx_size: uval = stx->stx_size; break;
511 case stx_blocks: uval = stx->stx_blocks; break;
512 case stx_rdev_major: uval = stx->stx_rdev_major; break;
513 case stx_rdev_minor: uval = stx->stx_rdev_minor; break;
514 case stx_dev_major: uval = stx->stx_dev_major; break;
515 case stx_dev_minor: uval = stx->stx_dev_minor; break;
517 case stx_atime_tv_sec: sval = stx->stx_atime.tv_sec; break;
518 case stx_atime_tv_nsec: sval = stx->stx_atime.tv_nsec; break;
519 case stx_btime_tv_sec: sval = stx->stx_btime.tv_sec; break;
520 case stx_btime_tv_nsec: sval = stx->stx_btime.tv_nsec; break;
521 case stx_ctime_tv_sec: sval = stx->stx_ctime.tv_sec; break;
522 case stx_ctime_tv_nsec: sval = stx->stx_ctime.tv_nsec; break;
523 case stx_mtime_tv_sec: sval = stx->stx_mtime.tv_sec; break;
524 case stx_mtime_tv_nsec: sval = stx->stx_mtime.tv_nsec; break;
529 /* Parse the specified value as signed or unsigned as
530 * appropriate and compare to the stat information.
532 switch ((enum fields)(field - field_list)) {
535 ucheck = strtoull(val, &p, 0);
537 bad_arg("Field '%s' requires unsigned integer\n", key);
538 check(uval == ucheck,
539 "%s differs, 0x%llx != 0x%llx\n", key, uval, ucheck);
543 for (type = file_types; type->name; type++) {
544 if (strcmp(type->name, val) == 0) {
553 ucheck = strtoull(val, &p, 0);
555 bad_arg("Field '%s' requires unsigned integer\n", key);
557 check(uval == ucheck,
558 "%s differs, 0%llo != 0%llo\n", key, uval, ucheck);
572 ucheck = strtoull(val, &p, 0);
574 bad_arg("Field '%s' requires unsigned integer\n", key);
575 check(uval == ucheck,
576 "%s differs, %llu != %llu\n", key, uval, ucheck);
579 case stx_atime_tv_sec:
580 case stx_atime_tv_nsec:
581 case stx_btime_tv_sec:
582 case stx_btime_tv_nsec:
583 case stx_ctime_tv_sec:
584 case stx_ctime_tv_nsec:
585 case stx_mtime_tv_sec:
586 case stx_mtime_tv_nsec:
587 scheck = strtoll(val, &p, 0);
589 bad_arg("Field '%s' requires integer\n", key);
590 check(sval == scheck,
591 "%s differs, %lld != %lld\n", key, sval, scheck);
600 * Check attributes in stx_attributes. When stx_attributes_mask gets in
601 * upstream, we will need to consider that also.
603 static void check_attribute(const struct statx *stx, char *arg)
605 const struct attr_name *p;
609 verbose("check attr %s\n", arg);
611 case '+': set = true; break;
612 case '-': set = false; break;
614 bad_arg("attr flag must be marked + (set) or - (unset)\n");
618 p = bsearch(arg, attr_list, sizeof(attr_list) / sizeof(attr_list[0]),
619 sizeof(attr_list[0]), attr_name_cmp);
621 bad_arg("Unrecognised attr name '%s'\n", arg);
625 check((stx->stx_attributes & attr) == attr,
626 "Attribute %s should be set\n", arg);
628 check((stx->stx_attributes & attr) == 0,
629 "Attribute %s should be unset\n", arg);
636 int main(int argc, char **argv)
640 unsigned int mask = STATX_ALL;
641 unsigned int atflags = AT_STATX_SYNC_AS_STAT;
645 if (argc == 2 && strcmp(argv[1], "--check-statx") == 0) {
647 return (xfstests_statx(AT_FDCWD, "/", 0, 0, &stx) == -1 &&
648 errno == ENOSYS) ? 1 : 0;
652 while (c = getopt(argc, argv, "+DFm:v"),
657 atflags &= ~AT_STATX_SYNC_TYPE;
658 atflags |= AT_STATX_FORCE_SYNC;
661 atflags &= ~AT_STATX_SYNC_TYPE;
662 atflags |= AT_STATX_DONT_SYNC;
665 if (strcmp(optarg, "basic") == 0) {
666 mask = STATX_BASIC_STATS;
667 } else if (strcmp(optarg, "all") == 0) {
670 mask = strtoul(optarg, &p, 0);
690 /* Gather the stats. We want both statx and stat so that we can
691 * compare what's in the buffers.
693 verbose("call statx %s\n", testfile);
694 memset(&stx, 0xfb, sizeof(stx));
695 ret = xfstests_statx(AT_FDCWD, testfile, atflags | AT_SYMLINK_NOFOLLOW,
704 fprintf(stderr, "Unexpected return %d from statx()\n", ret);
708 verbose("call stat %s\n", testfile);
709 ret = fstatat(AT_FDCWD, testfile, &st, AT_SYMLINK_NOFOLLOW);
717 fprintf(stderr, "Unexpected return %d from stat()\n", ret);
721 verbose("compare statx and stat\n");
722 cmp_statx(&stx, &st);
724 /* Display the available timestamps */
725 verbose("begin time %llu.%09u\n", origin.tv_sec, origin.tv_nsec);
726 if (stx.stx_mask & STATX_BTIME)
727 verbose(" btime %llu.%09u\n", stx.stx_btime.tv_sec, stx.stx_btime.tv_nsec);
728 if (stx.stx_mask & STATX_ATIME)
729 verbose(" atime %llu.%09u\n", stx.stx_atime.tv_sec, stx.stx_atime.tv_nsec);
730 if (stx.stx_mask & STATX_MTIME)
731 verbose(" mtime %llu.%09u\n", stx.stx_mtime.tv_sec, stx.stx_mtime.tv_nsec);
732 if (stx.stx_mask & STATX_CTIME)
733 verbose(" ctime %llu.%09u\n", stx.stx_ctime.tv_sec, stx.stx_ctime.tv_nsec);
735 /* Handle additional checks the user specified */
736 for (; *argv; argv++) {
739 if (strncmp("attr=", arg, 5) == 0) {
740 /* attr=[+-]<attr> - check attribute flag */
741 check_attribute(&stx, arg + 5);
745 if (strcmp("cmp_ref", arg) == 0) {
746 /* cmp_ref - check ref file has same stats */
751 if (strncmp(arg, "stx_", 4) == 0) {
752 /* stx_<field>=<n> - check field set to n */
753 check_field(&stx, *argv);
757 if (strncmp("ref=", arg, 4) == 0) {
758 /* ref=<file> - set reference stats from file */
759 get_reference(arg + 4);
763 if (strcmp("ts_order", arg) == 0) {
764 /* ts_order - check timestamp order */
765 check_timestamp_order(&stx);
769 if (strncmp("ts_origin=", arg, 10) == 0) {
770 /* ts_origin=<sec>.<nsec> - set origin timestamp */
771 set_origin_timestamp(arg + 10);
775 if (strncmp("ts=", arg, 3) == 0) {
776 /* ts=<a>,<b> - check timestamp b is same as a or after */
777 check_timestamp(&stx, arg + 3);
781 bad_arg("check '%s' not supported\n", arg);
789 verbose("Success\n");