198c1332fcc985a7c140e20c23470ad2521351ab
[xfstests-dev.git] / src / stat_test.c
1 /* Perform various tests on stat and statx output
2  *
3  * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
4  * Written by David Howells (dhowells@redhat.com)
5  *
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.
10  */
11
12 #include <stdarg.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <stdbool.h>
16 #include <string.h>
17 #include <unistd.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/sysmacros.h>
23 #include "statx.h"
24
25 static bool failed = false;
26 static bool is_verbose = 0;
27 static const char *prog;
28 static const char *testfile;
29
30 /* Reference data */
31 static struct statx ref;
32 static struct statx_timestamp origin;
33 static bool ref_set, origin_set;
34
35 /*
36  * Field IDs, sorted for bsearch() on field_list[].
37  */
38 enum fields {
39         stx_atime_tv_nsec,
40         stx_atime_tv_sec,
41         stx_attributes,
42         stx_blksize,
43         stx_blocks,
44         stx_btime_tv_nsec,
45         stx_btime_tv_sec,
46         stx_ctime_tv_nsec,
47         stx_ctime_tv_sec,
48         stx_dev_major,
49         stx_dev_minor,
50         stx_gid,
51         stx_ino,
52         stx_mask,
53         stx_mode,
54         stx_mtime_tv_nsec,
55         stx_mtime_tv_sec,
56         stx_nlink,
57         stx_rdev_major,
58         stx_rdev_minor,
59         stx_size,
60         stx_type,
61         stx_uid,
62         nr__fields
63 };
64
65 struct field {
66         const char *name;               /* Name on command line */
67         unsigned int mask_bit;
68 };
69
70 /*
71  * List of fields, sorted for bsearch().
72  */
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 },
97 };
98
99 static int field_cmp(const void *_key, const void *_p)
100 {
101         const char *key = _key;
102         const struct field *p = _p;
103         return strcmp(key, p->name);
104 }
105
106 /*
107  * Sorted list of attribute flags for bsearch().
108  */
109 struct attr_name {
110         const char      *name;
111         __u64           attr_flag;
112 };
113
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 },
121 };
122
123 static int attr_name_cmp(const void *_key, const void *_p)
124 {
125         const char *key = _key;
126         const struct attr_name *p = _p;
127         return strcmp(key, p->name);
128 }
129
130 struct file_type {
131         const char *name;
132         mode_t mode;
133 };
134
135 /*
136  * List of file types.
137  */
138 static const struct file_type file_types[] = {
139         { "fifo",       S_IFIFO },
140         { "char",       S_IFCHR },
141         { "dir",        S_IFDIR },
142         { "block",      S_IFBLK },
143         { "file",       S_IFREG },
144         { "sym",        S_IFLNK },
145         { "sock",       S_IFSOCK },
146         { NULL }
147 };
148
149 static __attribute__((noreturn))
150 void format(void)
151 {
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");
173         exit(2);
174 }
175
176 static __attribute__((noreturn, format(printf, 1, 2)))
177 void bad_arg(const char *fmt, ...)
178 {
179         va_list va;
180
181         va_start(va, fmt);
182         vfprintf(stderr, fmt, va);
183         va_end(va);
184         exit(2);
185 }
186
187 static __attribute__((format(printf, 1, 2)))
188 void verbose(const char *fmt, ...)
189 {
190         va_list va;
191
192         if (is_verbose) {
193                 va_start(va, fmt);
194                 fputs(" - ", stdout);
195                 vprintf(fmt, va);
196                 va_end(va);
197         }
198 }
199
200 static __attribute__((format(printf, 2, 3)))
201 void check(bool good, const char *fmt, ...)
202 {
203         va_list va;
204
205         if (!good) {
206                 va_start(va, fmt);
207                 fputs("[!] ", stdout);
208                 vprintf(fmt, va);
209                 va_end(va);
210                 failed = true;
211         }
212 }
213
214 /*
215  * Compare the contents of a statx struct with that of a stat struct and check
216  * that they're the same.
217  */
218 static void cmp_statx(const struct statx *stx, const struct stat *st)
219 {
220 #define cmp(fmt, x)                                                   \
221         do {                                                          \
222                 check(stx->stx_##x == st->st_##x,                     \
223                       "stat.%s differs, "fmt" != "fmt"\n",            \
224                       #x,                                             \
225                       (unsigned long long)stx->stx_##x,               \
226                       (unsigned long long)st->st_##x);                \
227         } while (0)
228
229         cmp("%llu", blksize);
230         cmp("%llu", nlink);
231         cmp("%llu", uid);
232         cmp("%llu", gid);
233         cmp("%llo", mode);
234         cmp("%llu", ino);
235         cmp("%llu", size);
236         cmp("%llu", blocks);
237
238 #define devcmp(x) \
239         do {                                                            \
240                 check(stx->stx_##x##_major == major(st->st_##x),        \
241                       "stat.%s.major differs, %u != %u\n",              \
242                       #x,                                               \
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",              \
247                       #x,                                               \
248                       stx->stx_##x##_minor,                             \
249                       minor(st->st_##x));                               \
250         } while (0)
251
252         devcmp(dev);
253         devcmp(rdev);
254
255 #define timecmp(x) \
256         do {                                                            \
257                 check(stx->stx_##x##time.tv_sec == st->st_##x##tim.tv_sec, \
258                       "stat.%stime.tv_sec differs, %lld != %lld\n",     \
259                       #x,                                               \
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",    \
264                       #x,                                               \
265                       (long long)stx->stx_##x##time.tv_nsec,            \
266                       (long long)st->st_##x##tim.tv_nsec);              \
267         } while (0)
268
269         timecmp(a);
270         timecmp(c);
271         timecmp(m);
272 }
273
274 /*
275  * Set origin timestamp from a "<sec>.<nsec>" string.
276  */
277 static void set_origin_timestamp(const char *arg)
278 {
279         long long sec;
280         int nsec;
281
282         switch (sscanf(arg, "%lld.%d", &sec, &nsec)) {
283         case 0:
284                 bad_arg("ts_origin= missing seconds value");
285         case 1:
286                 bad_arg("ts_origin= missing nanoseconds value");
287         default:
288                 origin.tv_sec = sec;
289                 origin.tv_nsec = nsec;
290                 origin_set = true;
291                 break;
292         }
293 }
294
295 /*
296  * Get reference stats from a file.
297  */
298 static void get_reference(const char *file)
299 {
300         int ret;
301
302         if (!*file)
303                 bad_arg("ref= requires a filename\n");
304
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,
308                              &ref);
309         switch (ret) {
310         case 0:
311                 ref_set = true;
312                 break;
313         case -1:
314                 perror(file);
315                 exit(1);
316         default:
317                 fprintf(stderr, "Unexpected return %d from statx()\n", ret);
318                 exit(1);
319         }
320 }
321
322 /*
323  * Check a pair of timestamps.
324  */
325 static void check_earlier(const struct statx_timestamp *A,
326                           const struct statx_timestamp *B,
327                           const char *A_name,
328                           const char *B_name)
329 {
330
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);
334
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);
339 }
340
341 /*
342  * Check that the timestamps are reasonably ordered.
343  *
344  * We require the following to hold true immediately after creation if the
345  * relevant timestamps exist on the filesystem:
346  *
347  *      btime <= atime
348  *      btime <= mtime <= ctime
349  */
350 static void check_timestamp_order(const struct statx *stx)
351 {
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");
360 }
361
362 /*
363  * Check that the second timestamp is the same as or after the first timestamp.
364  */
365 static void check_timestamp(const struct statx *stx, char *arg)
366 {
367         const struct statx_timestamp *a, *b;
368         const char *an, *bn;
369         unsigned int mask;
370
371         if (strlen(arg) != 3 || arg[1] != ',')
372                 bad_arg("ts= requires <a>,<b>\n");
373
374         switch (arg[0]) {
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;
384         default:
385                 bad_arg("ts= timestamp '%c' not supported\n", arg[0]);
386         }
387
388         if (arg[0] == '0') {
389                 if (!origin_set)
390                         bad_arg("ts= timestamp '%c' requires origin= first\n", arg[0]);
391         } else if (arg[0] <= 'Z') {
392                 if (!ref_set)
393                         bad_arg("ts= timestamp '%c' requires ref= first\n", arg[0]);
394                 if (!(ref.stx_mask & mask))
395                         return;
396         } else {
397                 if (!(stx->stx_mask & mask))
398                         return;
399         }
400
401         switch (arg[2]) {
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;
411         default:
412                 bad_arg("ts= timestamp '%c' not supported\n", arg[2]);
413         }
414
415         if (arg[2] == '0') {
416                 if (!origin_set)
417                         bad_arg("ts= timestamp '%c' requires origin= first\n", arg[0]);
418         } else if (arg[2] <= 'Z') {
419                 if (!ref_set)
420                         bad_arg("ts= timestamp '%c' requires ref= first\n", arg[2]);
421                 if (!(ref.stx_mask & mask))
422                         return;
423         } else {
424                 if (!(stx->stx_mask & mask))
425                         return;
426         }
427
428         verbose("check %s <= %s\n", an, bn);
429         check_earlier(a, b, an, bn);
430 }
431
432 /*
433  * Compare to reference file.
434  */
435 static void cmp_ref(const struct statx *stx, unsigned int mask)
436 {
437 #undef cmp
438 #define cmp(fmt, x)                                                     \
439         do {                                                            \
440                 check(stx->x == ref.x,                                  \
441                       "attr '%s' differs from ref file, "fmt" != "fmt"\n", \
442                       #x,                                               \
443                       (unsigned long long)stx->x,                       \
444                       (unsigned long long)ref.x);                       \
445         } while (0)
446
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);
470 }
471
472 /*
473  * Check an field restriction.  Specified on the command line as a key=val pair
474  * in the checks section.  For instance:
475  *
476  *      stx_type=char
477  *      stx_mode=0644
478  */
479 static void check_field(const struct statx *stx, char *arg)
480 {
481         const struct file_type *type;
482         const struct field *field;
483         unsigned long long ucheck, uval = 0;
484         long long scheck, sval = 0;
485         char *key, *val, *p;
486
487         verbose("check %s\n", arg);
488
489         key = arg;
490         val = strchr(key, '=');
491         if (!val || !val[1])
492                 bad_arg("%s check requires value\n", key);
493         *(val++) = 0;
494
495         field = bsearch(key, field_list, nr__fields, sizeof(*field), field_cmp);
496         if (!field)
497                 bad_arg("Field '%s' not supported\n", key);
498
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;
516
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;
525         default:
526                 break;
527         }
528
529         /* Parse the specified value as signed or unsigned as
530          * appropriate and compare to the stat information.
531          */
532         switch ((enum fields)(field - field_list)) {
533         case stx_mask:
534         case stx_attributes:
535                 ucheck = strtoull(val, &p, 0);
536                 if (*p)
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);
540                 break;
541
542         case stx_type:
543                 for (type = file_types; type->name; type++) {
544                         if (strcmp(type->name, val) == 0) {
545                                 ucheck = type->mode;
546                                 goto octal_check;
547                         }
548                 }
549
550                 /* fall through */
551
552         case stx_mode:
553                 ucheck = strtoull(val, &p, 0);
554                 if (*p)
555                         bad_arg("Field '%s' requires unsigned integer\n", key);
556         octal_check:
557                 check(uval == ucheck,
558                       "%s differs, 0%llo != 0%llo\n", key, uval, ucheck);
559                 break;
560
561         case stx_blksize:
562         case stx_nlink:
563         case stx_uid:
564         case stx_gid:
565         case stx_ino:
566         case stx_size:
567         case stx_blocks:
568         case stx_rdev_major:
569         case stx_rdev_minor:
570         case stx_dev_major:
571         case stx_dev_minor:
572                 ucheck = strtoull(val, &p, 0);
573                 if (*p)
574                         bad_arg("Field '%s' requires unsigned integer\n", key);
575                 check(uval == ucheck,
576                       "%s differs, %llu != %llu\n", key, uval, ucheck);
577                 break;
578
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);
588                 if (*p)
589                         bad_arg("Field '%s' requires integer\n", key);
590                 check(sval == scheck,
591                       "%s differs, %lld != %lld\n", key, sval, scheck);
592                 break;
593
594         default:
595                 break;
596         }
597 }
598
599 /*
600  * Check attributes in stx_attributes.  When stx_attributes_mask gets in
601  * upstream, we will need to consider that also.
602  */
603 static void check_attribute(const struct statx *stx, char *arg)
604 {
605         const struct attr_name *p;
606         __u64 attr;
607         bool set;
608
609         verbose("check attr %s\n", arg);
610         switch (arg[0]) {
611         case '+': set = true;   break;
612         case '-': set = false;  break;
613         default:
614                 bad_arg("attr flag must be marked + (set) or - (unset)\n");
615         }
616         arg++;
617
618         p = bsearch(arg, attr_list, sizeof(attr_list) / sizeof(attr_list[0]),
619                     sizeof(attr_list[0]), attr_name_cmp);
620         if (!p)
621                 bad_arg("Unrecognised attr name '%s'\n", arg);
622
623         attr = p->attr_flag;
624         if (set) {
625                 check((stx->stx_attributes & attr) == attr,
626                       "Attribute %s should be set\n", arg);
627         } else {
628                 check((stx->stx_attributes & attr) == 0,
629                       "Attribute %s should be unset\n", arg);
630         }
631 }
632
633 /*
634  * Do the testing.
635  */
636 int main(int argc, char **argv)
637 {
638         struct statx stx;
639         struct stat st;
640         unsigned int mask = STATX_ALL;
641         unsigned int atflags = AT_STATX_SYNC_AS_STAT;
642         char *p;
643         int c, ret;
644
645         if (argc == 2 && strcmp(argv[1], "--check-statx") == 0) {
646                 errno = 0;
647                 return (xfstests_statx(AT_FDCWD, "/", 0, 0, &stx) == -1 &&
648                         errno == ENOSYS) ? 1 : 0;
649         }
650
651         prog = argv[0];
652         while (c = getopt(argc, argv, "+DFm:v"),
653                c != -1
654                ) {
655                 switch (c) {
656                 case 'F':
657                         atflags &= ~AT_STATX_SYNC_TYPE;
658                         atflags |= AT_STATX_FORCE_SYNC;
659                         break;
660                 case 'D':
661                         atflags &= ~AT_STATX_SYNC_TYPE;
662                         atflags |= AT_STATX_DONT_SYNC;
663                         break;
664                 case 'm':
665                         if (strcmp(optarg, "basic") == 0) {
666                                 mask = STATX_BASIC_STATS;
667                         } else if (strcmp(optarg, "all") == 0) {
668                                 mask = STATX_ALL;
669                         } else {
670                                 mask = strtoul(optarg, &p, 0);
671                                 if (*p)
672                                         format();
673                         }
674                         break;
675                 case 'v':
676                         is_verbose = 1;
677                         break;
678                 default:
679                         format();
680                 }
681         }
682
683         argc -= optind;
684         argv += optind;
685         if (argc < 1)
686                 format();
687         testfile = argv[0];
688         argv += 1;
689
690         /* Gather the stats.  We want both statx and stat so that we can
691          * compare what's in the buffers.
692          */
693         verbose("call statx %s\n", testfile);
694         memset(&stx, 0xfb, sizeof(stx));
695         ret = xfstests_statx(AT_FDCWD, testfile, atflags | AT_SYMLINK_NOFOLLOW,
696                              mask, &stx);
697         switch (ret) {
698         case 0:
699                 break;
700         case -1:
701                 perror(testfile);
702                 exit(1);
703         default:
704                 fprintf(stderr, "Unexpected return %d from statx()\n", ret);
705                 exit(1);
706         }
707
708         verbose("call stat %s\n", testfile);
709         ret = fstatat(AT_FDCWD, testfile, &st, AT_SYMLINK_NOFOLLOW);
710         switch (ret) {
711         case 0:
712                 break;
713         case -1:
714                 perror(testfile);
715                 exit(1);
716         default:
717                 fprintf(stderr, "Unexpected return %d from stat()\n", ret);
718                 exit(1);
719         }
720
721         verbose("compare statx and stat\n");
722         cmp_statx(&stx, &st);
723
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);
734
735         /* Handle additional checks the user specified */
736         for (; *argv; argv++) {
737                 char *arg = *argv;
738
739                 if (strncmp("attr=", arg, 5) == 0) {
740                         /* attr=[+-]<attr> - check attribute flag */
741                         check_attribute(&stx, arg + 5);
742                         continue;
743                 }
744
745                 if (strcmp("cmp_ref", arg) == 0) {
746                         /* cmp_ref - check ref file has same stats */
747                         cmp_ref(&stx, mask);
748                         continue;
749                 }
750
751                 if (strncmp(arg, "stx_", 4) == 0) {
752                         /* stx_<field>=<n> - check field set to n */
753                         check_field(&stx, *argv);
754                         continue;
755                 }
756
757                 if (strncmp("ref=", arg, 4) == 0) {
758                         /* ref=<file> - set reference stats from file */
759                         get_reference(arg + 4);
760                         continue;
761                 }
762
763                 if (strcmp("ts_order", arg) == 0) {
764                         /* ts_order - check timestamp order */
765                         check_timestamp_order(&stx);
766                         continue;
767                 }
768
769                 if (strncmp("ts_origin=", arg, 10) == 0) {
770                         /* ts_origin=<sec>.<nsec> - set origin timestamp */
771                         set_origin_timestamp(arg + 10);
772                         continue;
773                 }
774
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);
778                         continue;
779                 }
780
781                 bad_arg("check '%s' not supported\n", arg);
782         }
783
784         if (failed) {
785                 printf("Failed\n");
786                 exit(1);
787         }
788
789         verbose("Success\n");
790         exit(0);
791 }