mirror of
https://github.com/linux-apfs/apfstests.git
synced 2026-05-01 15:01:44 -07:00
d1ba8b79a6
Add a statx test script that does the following:
(1) Creates one each of the various types of file object and creates a
hard link to the regular file.
Note that the creation of an AF_UNIX socket is done with netcat in a
bash coprocessing thread. This might be best done with another
in-house helper to avoid a dependency on nc.
(2) Invokes the C test program included in this patch after the creation
and hands it a list of things to check appropriate to each object.
(3) Asks the test program to check the creation time of each object
against that of the preceding object.
(4) Makes various tests on the timestamps of the hardlinked file.
The patch also creates a C[*] test program to do the actual stat checking.
The test program then does the following:
(1) Compares the output of statx() to that of fstatat().
(2) Optionally compares the timestamps to see that they're sensibly
ordered with respect to each other.
(3) Optionally compares the timestamps to those of a reference file.
(4) Optionally compares the timestamps to a specified time.
(5) Optionally compares selected stats to values specified on the command
line.
(6) Optionally compares all the stats to those of a reference file,
requiring them to be the same (hard link checking).
For example:
./src/stat_test /dev/null \
stx_type=char \
stx_rdev_major=3 \
stx_rdev_minor=8 \
stx_nlink=1 \
ref=/dev/zero \
ts=B,b
The test program can also be given a --check-statx parameter to give a
quick exit code-based answer on whether statx() exists within the kernel.
[*] Note that it proved much easier to do this in C than trying to do it in
shell script and trying parsing the output of xfs_io. Using xfs_io has
other pitfalls also: it wants to *open* the file, even if the file is
not an appropriate type for this or does not grant permission to do so.
I can get around this by opening O_PATH, but then xfs_io fails to
handle XFS files because it wants to issue ioctls on every fd it opens.
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Eryu Guan <eguan@redhat.com>
720 lines
19 KiB
C
720 lines
19 KiB
C
/* Perform various tests on stat and statx output
|
|
*
|
|
* Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public Licence
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the Licence, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include "statx.h"
|
|
|
|
static bool failed = false;
|
|
static bool is_verbose = 0;
|
|
static const char *prog;
|
|
static const char *testfile;
|
|
|
|
/* Reference data */
|
|
static struct statx ref;
|
|
static struct statx_timestamp origin;
|
|
static bool ref_set, origin_set;
|
|
|
|
/*
|
|
* Field IDs, sorted for bsearch() on field_list[].
|
|
*/
|
|
enum fields {
|
|
stx_atime_tv_nsec,
|
|
stx_atime_tv_sec,
|
|
stx_attributes,
|
|
stx_blksize,
|
|
stx_blocks,
|
|
stx_btime_tv_nsec,
|
|
stx_btime_tv_sec,
|
|
stx_ctime_tv_nsec,
|
|
stx_ctime_tv_sec,
|
|
stx_dev_major,
|
|
stx_dev_minor,
|
|
stx_gid,
|
|
stx_ino,
|
|
stx_mask,
|
|
stx_mode,
|
|
stx_mtime_tv_nsec,
|
|
stx_mtime_tv_sec,
|
|
stx_nlink,
|
|
stx_rdev_major,
|
|
stx_rdev_minor,
|
|
stx_size,
|
|
stx_type,
|
|
stx_uid,
|
|
nr__fields
|
|
};
|
|
|
|
struct field {
|
|
const char *name; /* Name on command line */
|
|
unsigned int mask_bit;
|
|
};
|
|
|
|
/*
|
|
* List of fields, sorted for bsearch().
|
|
*/
|
|
static const struct field field_list[nr__fields] = {
|
|
[stx_atime_tv_nsec] = { "stx_atime.tv_nsec", STATX_ATIME },
|
|
[stx_atime_tv_sec] = { "stx_atime.tv_sec", STATX_ATIME },
|
|
[stx_attributes] = { "stx_attributes", 0 },
|
|
[stx_blksize] = { "stx_blksize", 0 },
|
|
[stx_blocks] = { "stx_blocks", STATX_BLOCKS },
|
|
[stx_btime_tv_nsec] = { "stx_btime.tv_nsec", STATX_BTIME },
|
|
[stx_btime_tv_sec] = { "stx_btime.tv_sec", STATX_BTIME },
|
|
[stx_ctime_tv_nsec] = { "stx_ctime.tv_nsec", STATX_CTIME },
|
|
[stx_ctime_tv_sec] = { "stx_ctime.tv_sec", STATX_CTIME },
|
|
[stx_dev_major] = { "stx_dev_major", 0 },
|
|
[stx_dev_minor] = { "stx_dev_minor", 0 },
|
|
[stx_gid] = { "stx_gid", STATX_GID },
|
|
[stx_ino] = { "stx_ino", STATX_INO },
|
|
[stx_mask] = { "stx_mask", 0 },
|
|
[stx_mode] = { "stx_mode", STATX_MODE },
|
|
[stx_mtime_tv_nsec] = { "stx_mtime.tv_nsec", STATX_MTIME },
|
|
[stx_mtime_tv_sec] = { "stx_mtime.tv_sec", STATX_MTIME },
|
|
[stx_nlink] = { "stx_nlink", STATX_NLINK },
|
|
[stx_rdev_major] = { "stx_rdev_major", 0 },
|
|
[stx_rdev_minor] = { "stx_rdev_minor", 0 },
|
|
[stx_size] = { "stx_size", STATX_SIZE },
|
|
[stx_type] = { "stx_type", STATX_TYPE },
|
|
[stx_uid] = { "stx_uid", STATX_UID },
|
|
};
|
|
|
|
static int field_cmp(const void *_key, const void *_p)
|
|
{
|
|
const char *key = _key;
|
|
const struct field *p = _p;
|
|
return strcmp(key, p->name);
|
|
}
|
|
|
|
struct file_type {
|
|
const char *name;
|
|
mode_t mode;
|
|
};
|
|
|
|
/*
|
|
* List of file types.
|
|
*/
|
|
static const struct file_type file_types[] = {
|
|
{ "fifo", S_IFIFO },
|
|
{ "char", S_IFCHR },
|
|
{ "dir", S_IFDIR },
|
|
{ "block", S_IFBLK },
|
|
{ "file", S_IFREG },
|
|
{ "sym", S_IFLNK },
|
|
{ "sock", S_IFSOCK },
|
|
{ NULL }
|
|
};
|
|
|
|
static __attribute__((noreturn))
|
|
void format(void)
|
|
{
|
|
fprintf(stderr, "usage: %s --check-statx\n", prog);
|
|
fprintf(stderr, "usage: %s [-v] [-m<mask>] <testfile> [checks]\n", prog);
|
|
fprintf(stderr, "\t<mask> can be basic, all or a number; all is the default\n");
|
|
fprintf(stderr, "checks is a list of zero or more of:\n");
|
|
fprintf(stderr, "\tcmp_ref -- check that the reference file has identical stats\n");
|
|
fprintf(stderr, "\tref=<file> -- get reference stats from file\n");
|
|
fprintf(stderr, "\tstx_<field>=<val> -- statx field value check\n");
|
|
fprintf(stderr, "\tts=<a>,<b> -- timestamp a <= b, where a and b can each be one of:\n");
|
|
fprintf(stderr, "\t\t[abcm] -- the timestamps from testfile\n");
|
|
fprintf(stderr, "\t\t[ABCM] -- the timestamps from the reference file\n");
|
|
fprintf(stderr, "\t\t0 -- the origin timestamp\n");
|
|
fprintf(stderr, "\tts_origin=<sec>.<nsec> -- set the origin timestamp\n");
|
|
fprintf(stderr, "\tts_order -- check the timestamp order\n");
|
|
fprintf(stderr, "\t\t(for stx_type, fifo char dir, block, file, sym, sock can be used)\n");
|
|
exit(2);
|
|
}
|
|
|
|
static __attribute__((noreturn, format(printf, 1, 2)))
|
|
void bad_arg(const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
vfprintf(stderr, fmt, va);
|
|
va_end(va);
|
|
exit(2);
|
|
}
|
|
|
|
static __attribute__((format(printf, 1, 2)))
|
|
void verbose(const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
|
|
if (is_verbose) {
|
|
va_start(va, fmt);
|
|
fputs(" - ", stdout);
|
|
vprintf(fmt, va);
|
|
va_end(va);
|
|
}
|
|
}
|
|
|
|
static __attribute__((format(printf, 2, 3)))
|
|
void check(bool good, const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
|
|
if (!good) {
|
|
va_start(va, fmt);
|
|
fputs("[!] ", stdout);
|
|
vprintf(fmt, va);
|
|
va_end(va);
|
|
failed = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compare the contents of a statx struct with that of a stat struct and check
|
|
* that they're the same.
|
|
*/
|
|
static void cmp_statx(const struct statx *stx, const struct stat *st)
|
|
{
|
|
#define cmp(fmt, x) \
|
|
do { \
|
|
check(stx->stx_##x == st->st_##x, \
|
|
"stat.%s differs, "fmt" != "fmt"\n", \
|
|
#x, \
|
|
(unsigned long long)stx->stx_##x, \
|
|
(unsigned long long)st->st_##x); \
|
|
} while (0)
|
|
|
|
cmp("%llu", blksize);
|
|
cmp("%llu", nlink);
|
|
cmp("%llu", uid);
|
|
cmp("%llu", gid);
|
|
cmp("%llo", mode);
|
|
cmp("%llu", ino);
|
|
cmp("%llu", size);
|
|
cmp("%llu", blocks);
|
|
|
|
#define devcmp(x) \
|
|
do { \
|
|
check(stx->stx_##x##_major == major(st->st_##x), \
|
|
"stat.%s.major differs, %u != %u\n", \
|
|
#x, \
|
|
stx->stx_##x##_major, \
|
|
major(st->st_##x)); \
|
|
check(stx->stx_##x##_minor == minor(st->st_##x), \
|
|
"stat.%s.minor differs, %u != %u\n", \
|
|
#x, \
|
|
stx->stx_##x##_minor, \
|
|
minor(st->st_##x)); \
|
|
} while (0)
|
|
|
|
devcmp(dev);
|
|
devcmp(rdev);
|
|
|
|
#define timecmp(x) \
|
|
do { \
|
|
check(stx->stx_##x##time.tv_sec == st->st_##x##tim.tv_sec, \
|
|
"stat.%stime.tv_sec differs, %lld != %lld\n", \
|
|
#x, \
|
|
(long long)stx->stx_##x##time.tv_sec, \
|
|
(long long)st->st_##x##tim.tv_sec); \
|
|
check(stx->stx_##x##time.tv_nsec == st->st_##x##tim.tv_nsec, \
|
|
"stat.%stime.tv_nsec differs, %lld != %lld\n", \
|
|
#x, \
|
|
(long long)stx->stx_##x##time.tv_nsec, \
|
|
(long long)st->st_##x##tim.tv_nsec); \
|
|
} while (0)
|
|
|
|
timecmp(a);
|
|
timecmp(c);
|
|
timecmp(m);
|
|
}
|
|
|
|
/*
|
|
* Set origin timestamp from a "<sec>.<nsec>" string.
|
|
*/
|
|
static void set_origin_timestamp(const char *arg)
|
|
{
|
|
long long sec;
|
|
int nsec;
|
|
|
|
switch (sscanf(arg, "%lld.%d", &sec, &nsec)) {
|
|
case 0:
|
|
bad_arg("ts_origin= missing seconds value");
|
|
case 1:
|
|
bad_arg("ts_origin= missing nanoseconds value");
|
|
default:
|
|
origin.tv_sec = sec;
|
|
origin.tv_nsec = nsec;
|
|
origin_set = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get reference stats from a file.
|
|
*/
|
|
static void get_reference(const char *file)
|
|
{
|
|
int ret;
|
|
|
|
if (!*file)
|
|
bad_arg("ref= requires a filename\n");
|
|
|
|
memset(&ref, 0xfb, sizeof(ref));
|
|
ret = xfstests_statx(AT_FDCWD, file, AT_SYMLINK_NOFOLLOW,
|
|
STATX_ATIME | STATX_BTIME | STATX_CTIME | STATX_MTIME,
|
|
&ref);
|
|
switch (ret) {
|
|
case 0:
|
|
ref_set = true;
|
|
break;
|
|
case -1:
|
|
perror(file);
|
|
exit(1);
|
|
default:
|
|
fprintf(stderr, "Unexpected return %d from statx()\n", ret);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check a pair of timestamps.
|
|
*/
|
|
static void check_earlier(const struct statx_timestamp *A,
|
|
const struct statx_timestamp *B,
|
|
const char *A_name,
|
|
const char *B_name)
|
|
{
|
|
|
|
check((B->tv_sec - A->tv_sec) >= 0,
|
|
"%s.sec is before %s.sec (%lld < %lld)\n",
|
|
B_name, A_name, B->tv_sec, A->tv_sec);
|
|
|
|
if (B->tv_sec == A->tv_sec)
|
|
check((B->tv_nsec - A->tv_nsec) >= 0,
|
|
"%s.nsec is before %s.nsec (%d < %d)\n",
|
|
B_name, A_name, B->tv_nsec, A->tv_nsec);
|
|
}
|
|
|
|
/*
|
|
* Check that the timestamps are reasonably ordered.
|
|
*
|
|
* We require the following to hold true immediately after creation if the
|
|
* relevant timestamps exist on the filesystem:
|
|
*
|
|
* btime <= atime
|
|
* btime <= mtime <= ctime
|
|
*/
|
|
static void check_timestamp_order(const struct statx *stx)
|
|
{
|
|
if ((stx->stx_mask & (STATX_BTIME | STATX_ATIME)) == (STATX_BTIME | STATX_ATIME))
|
|
check_earlier(&stx->stx_btime, &stx->stx_atime, "btime", "atime");
|
|
if ((stx->stx_mask & (STATX_BTIME | STATX_MTIME)) == (STATX_BTIME | STATX_MTIME))
|
|
check_earlier(&stx->stx_btime, &stx->stx_mtime, "btime", "mtime");
|
|
if ((stx->stx_mask & (STATX_BTIME | STATX_CTIME)) == (STATX_BTIME | STATX_CTIME))
|
|
check_earlier(&stx->stx_btime, &stx->stx_ctime, "btime", "ctime");
|
|
if ((stx->stx_mask & (STATX_MTIME | STATX_CTIME)) == (STATX_MTIME | STATX_CTIME))
|
|
check_earlier(&stx->stx_mtime, &stx->stx_ctime, "mtime", "ctime");
|
|
}
|
|
|
|
/*
|
|
* Check that the second timestamp is the same as or after the first timestamp.
|
|
*/
|
|
static void check_timestamp(const struct statx *stx, char *arg)
|
|
{
|
|
const struct statx_timestamp *a, *b;
|
|
const char *an, *bn;
|
|
unsigned int mask;
|
|
|
|
if (strlen(arg) != 3 || arg[1] != ',')
|
|
bad_arg("ts= requires <a>,<b>\n");
|
|
|
|
switch (arg[0]) {
|
|
case 'a': a = &stx->stx_atime; an = "atime"; mask = STATX_ATIME; break;
|
|
case 'b': a = &stx->stx_btime; an = "btime"; mask = STATX_BTIME; break;
|
|
case 'c': a = &stx->stx_ctime; an = "ctime"; mask = STATX_CTIME; break;
|
|
case 'm': a = &stx->stx_mtime; an = "mtime"; mask = STATX_MTIME; break;
|
|
case 'A': a = &ref.stx_atime; an = "ref_a"; mask = STATX_ATIME; break;
|
|
case 'B': a = &ref.stx_btime; an = "ref_b"; mask = STATX_BTIME; break;
|
|
case 'C': a = &ref.stx_ctime; an = "ref_c"; mask = STATX_CTIME; break;
|
|
case 'M': a = &ref.stx_mtime; an = "ref_m"; mask = STATX_MTIME; break;
|
|
case '0': a = &origin; an = "origin"; mask = 0; break;
|
|
default:
|
|
bad_arg("ts= timestamp '%c' not supported\n", arg[0]);
|
|
}
|
|
|
|
if (arg[0] == '0') {
|
|
if (!origin_set)
|
|
bad_arg("ts= timestamp '%c' requires origin= first\n", arg[0]);
|
|
} else if (arg[0] <= 'Z') {
|
|
if (!ref_set)
|
|
bad_arg("ts= timestamp '%c' requires ref= first\n", arg[0]);
|
|
if (!(ref.stx_mask & mask))
|
|
return;
|
|
} else {
|
|
if (!(stx->stx_mask & mask))
|
|
return;
|
|
}
|
|
|
|
switch (arg[2]) {
|
|
case 'a': b = &stx->stx_atime; bn = "atime"; mask = STATX_ATIME; break;
|
|
case 'b': b = &stx->stx_btime; bn = "btime"; mask = STATX_BTIME; break;
|
|
case 'c': b = &stx->stx_ctime; bn = "ctime"; mask = STATX_CTIME; break;
|
|
case 'm': b = &stx->stx_mtime; bn = "mtime"; mask = STATX_MTIME; break;
|
|
case 'A': b = &ref.stx_atime; bn = "ref_a"; mask = STATX_ATIME; break;
|
|
case 'B': b = &ref.stx_btime; bn = "ref_b"; mask = STATX_BTIME; break;
|
|
case 'C': b = &ref.stx_ctime; bn = "ref_c"; mask = STATX_CTIME; break;
|
|
case 'M': b = &ref.stx_mtime; bn = "ref_m"; mask = STATX_MTIME; break;
|
|
case '0': b = &origin; bn = "origin"; mask = 0; break;
|
|
default:
|
|
bad_arg("ts= timestamp '%c' not supported\n", arg[2]);
|
|
}
|
|
|
|
if (arg[2] == '0') {
|
|
if (!origin_set)
|
|
bad_arg("ts= timestamp '%c' requires origin= first\n", arg[0]);
|
|
} else if (arg[2] <= 'Z') {
|
|
if (!ref_set)
|
|
bad_arg("ts= timestamp '%c' requires ref= first\n", arg[2]);
|
|
if (!(ref.stx_mask & mask))
|
|
return;
|
|
} else {
|
|
if (!(stx->stx_mask & mask))
|
|
return;
|
|
}
|
|
|
|
verbose("check %s <= %s\n", an, bn);
|
|
check_earlier(a, b, an, bn);
|
|
}
|
|
|
|
/*
|
|
* Compare to reference file.
|
|
*/
|
|
static void cmp_ref(const struct statx *stx, unsigned int mask)
|
|
{
|
|
#undef cmp
|
|
#define cmp(fmt, x) \
|
|
do { \
|
|
check(stx->x == ref.x, \
|
|
"attr '%s' differs from ref file, "fmt" != "fmt"\n", \
|
|
#x, \
|
|
(unsigned long long)stx->x, \
|
|
(unsigned long long)ref.x); \
|
|
} while (0)
|
|
|
|
cmp("%llx", stx_mask);
|
|
cmp("%llx", stx_attributes);
|
|
cmp("%llu", stx_blksize);
|
|
cmp("%llu", stx_attributes);
|
|
cmp("%llu", stx_nlink);
|
|
cmp("%llu", stx_uid);
|
|
cmp("%llu", stx_gid);
|
|
cmp("%llo", stx_mode);
|
|
cmp("%llu", stx_ino);
|
|
cmp("%llu", stx_size);
|
|
cmp("%llu", stx_blocks);
|
|
cmp("%lld", stx_atime.tv_sec);
|
|
cmp("%lld", stx_atime.tv_nsec);
|
|
cmp("%lld", stx_btime.tv_sec);
|
|
cmp("%lld", stx_btime.tv_nsec);
|
|
cmp("%lld", stx_ctime.tv_sec);
|
|
cmp("%lld", stx_ctime.tv_nsec);
|
|
cmp("%lld", stx_mtime.tv_sec);
|
|
cmp("%lld", stx_mtime.tv_nsec);
|
|
cmp("%llu", stx_rdev_major);
|
|
cmp("%llu", stx_rdev_minor);
|
|
cmp("%llu", stx_dev_major);
|
|
cmp("%llu", stx_dev_minor);
|
|
}
|
|
|
|
/*
|
|
* Check an field restriction. Specified on the command line as a key=val pair
|
|
* in the checks section. For instance:
|
|
*
|
|
* stx_type=char
|
|
* stx_mode=0644
|
|
*/
|
|
static void check_field(const struct statx *stx, char *arg)
|
|
{
|
|
const struct file_type *type;
|
|
const struct field *field;
|
|
unsigned long long ucheck, uval = 0;
|
|
long long scheck, sval = 0;
|
|
char *key, *val, *p;
|
|
|
|
verbose("check %s\n", arg);
|
|
|
|
key = arg;
|
|
val = strchr(key, '=');
|
|
if (!val || !val[1])
|
|
bad_arg("%s check requires value\n", key);
|
|
*(val++) = 0;
|
|
|
|
field = bsearch(key, field_list, nr__fields, sizeof(*field), field_cmp);
|
|
if (!field)
|
|
bad_arg("Field '%s' not supported\n", key);
|
|
|
|
/* Read the stat information specified by the key. */
|
|
switch ((enum fields)(field - field_list)) {
|
|
case stx_mask: uval = stx->stx_mask; break;
|
|
case stx_blksize: uval = stx->stx_blksize; break;
|
|
case stx_attributes: uval = stx->stx_attributes; break;
|
|
case stx_nlink: uval = stx->stx_nlink; break;
|
|
case stx_uid: uval = stx->stx_uid; break;
|
|
case stx_gid: uval = stx->stx_gid; break;
|
|
case stx_type: uval = stx->stx_mode & ~07777; break;
|
|
case stx_mode: uval = stx->stx_mode & 07777; break;
|
|
case stx_ino: uval = stx->stx_ino; break;
|
|
case stx_size: uval = stx->stx_size; break;
|
|
case stx_blocks: uval = stx->stx_blocks; break;
|
|
case stx_rdev_major: uval = stx->stx_rdev_major; break;
|
|
case stx_rdev_minor: uval = stx->stx_rdev_minor; break;
|
|
case stx_dev_major: uval = stx->stx_dev_major; break;
|
|
case stx_dev_minor: uval = stx->stx_dev_minor; break;
|
|
|
|
case stx_atime_tv_sec: sval = stx->stx_atime.tv_sec; break;
|
|
case stx_atime_tv_nsec: sval = stx->stx_atime.tv_nsec; break;
|
|
case stx_btime_tv_sec: sval = stx->stx_btime.tv_sec; break;
|
|
case stx_btime_tv_nsec: sval = stx->stx_btime.tv_nsec; break;
|
|
case stx_ctime_tv_sec: sval = stx->stx_ctime.tv_sec; break;
|
|
case stx_ctime_tv_nsec: sval = stx->stx_ctime.tv_nsec; break;
|
|
case stx_mtime_tv_sec: sval = stx->stx_mtime.tv_sec; break;
|
|
case stx_mtime_tv_nsec: sval = stx->stx_mtime.tv_nsec; break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Parse the specified value as signed or unsigned as
|
|
* appropriate and compare to the stat information.
|
|
*/
|
|
switch ((enum fields)(field - field_list)) {
|
|
case stx_mask:
|
|
case stx_attributes:
|
|
ucheck = strtoull(val, &p, 0);
|
|
if (*p)
|
|
bad_arg("Field '%s' requires unsigned integer\n", key);
|
|
check(uval == ucheck,
|
|
"%s differs, 0x%llx != 0x%llx\n", key, uval, ucheck);
|
|
break;
|
|
|
|
case stx_type:
|
|
for (type = file_types; type->name; type++) {
|
|
if (strcmp(type->name, val) == 0) {
|
|
ucheck = type->mode;
|
|
goto octal_check;
|
|
}
|
|
}
|
|
|
|
/* fall through */
|
|
|
|
case stx_mode:
|
|
ucheck = strtoull(val, &p, 0);
|
|
if (*p)
|
|
bad_arg("Field '%s' requires unsigned integer\n", key);
|
|
octal_check:
|
|
check(uval == ucheck,
|
|
"%s differs, 0%llo != 0%llo\n", key, uval, ucheck);
|
|
break;
|
|
|
|
case stx_blksize:
|
|
case stx_nlink:
|
|
case stx_uid:
|
|
case stx_gid:
|
|
case stx_ino:
|
|
case stx_size:
|
|
case stx_blocks:
|
|
case stx_rdev_major:
|
|
case stx_rdev_minor:
|
|
case stx_dev_major:
|
|
case stx_dev_minor:
|
|
ucheck = strtoull(val, &p, 0);
|
|
if (*p)
|
|
bad_arg("Field '%s' requires unsigned integer\n", key);
|
|
check(uval == ucheck,
|
|
"%s differs, %llu != %llu\n", key, uval, ucheck);
|
|
break;
|
|
|
|
case stx_atime_tv_sec:
|
|
case stx_atime_tv_nsec:
|
|
case stx_btime_tv_sec:
|
|
case stx_btime_tv_nsec:
|
|
case stx_ctime_tv_sec:
|
|
case stx_ctime_tv_nsec:
|
|
case stx_mtime_tv_sec:
|
|
case stx_mtime_tv_nsec:
|
|
scheck = strtoll(val, &p, 0);
|
|
if (*p)
|
|
bad_arg("Field '%s' requires integer\n", key);
|
|
check(sval == scheck,
|
|
"%s differs, %lld != %lld\n", key, sval, scheck);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Do the testing.
|
|
*/
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct statx stx;
|
|
struct stat st;
|
|
unsigned int mask = STATX_ALL;
|
|
unsigned int atflags = AT_STATX_SYNC_AS_STAT;
|
|
char *p;
|
|
int c, ret;
|
|
|
|
if (argc == 2 && strcmp(argv[1], "--check-statx") == 0) {
|
|
errno = 0;
|
|
return (xfstests_statx(AT_FDCWD, "/", 0, 0, &stx) == -1 &&
|
|
errno == ENOSYS) ? 1 : 0;
|
|
}
|
|
|
|
prog = argv[0];
|
|
while (c = getopt(argc, argv, "+DFm:v"),
|
|
c != -1
|
|
) {
|
|
switch (c) {
|
|
case 'F':
|
|
atflags &= ~AT_STATX_SYNC_TYPE;
|
|
atflags |= AT_STATX_FORCE_SYNC;
|
|
break;
|
|
case 'D':
|
|
atflags &= ~AT_STATX_SYNC_TYPE;
|
|
atflags |= AT_STATX_DONT_SYNC;
|
|
break;
|
|
case 'm':
|
|
if (strcmp(optarg, "basic") == 0) {
|
|
mask = STATX_BASIC_STATS;
|
|
} else if (strcmp(optarg, "all") == 0) {
|
|
mask = STATX_ALL;
|
|
} else {
|
|
mask = strtoul(optarg, &p, 0);
|
|
if (*p)
|
|
format();
|
|
}
|
|
break;
|
|
case 'v':
|
|
is_verbose = 1;
|
|
break;
|
|
default:
|
|
format();
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
if (argc < 1)
|
|
format();
|
|
testfile = argv[0];
|
|
argv += 1;
|
|
|
|
/* Gather the stats. We want both statx and stat so that we can
|
|
* compare what's in the buffers.
|
|
*/
|
|
verbose("call statx %s\n", testfile);
|
|
memset(&stx, 0xfb, sizeof(stx));
|
|
ret = xfstests_statx(AT_FDCWD, testfile, atflags | AT_SYMLINK_NOFOLLOW,
|
|
mask, &stx);
|
|
switch (ret) {
|
|
case 0:
|
|
break;
|
|
case -1:
|
|
perror(testfile);
|
|
exit(1);
|
|
default:
|
|
fprintf(stderr, "Unexpected return %d from statx()\n", ret);
|
|
exit(1);
|
|
}
|
|
|
|
verbose("call stat %s\n", testfile);
|
|
ret = fstatat(AT_FDCWD, testfile, &st, AT_SYMLINK_NOFOLLOW);
|
|
switch (ret) {
|
|
case 0:
|
|
break;
|
|
case -1:
|
|
perror(testfile);
|
|
exit(1);
|
|
default:
|
|
fprintf(stderr, "Unexpected return %d from stat()\n", ret);
|
|
exit(1);
|
|
}
|
|
|
|
verbose("compare statx and stat\n");
|
|
cmp_statx(&stx, &st);
|
|
|
|
/* Display the available timestamps */
|
|
verbose("begin time %llu.%09u\n", origin.tv_sec, origin.tv_nsec);
|
|
if (stx.stx_mask & STATX_BTIME)
|
|
verbose(" btime %llu.%09u\n", stx.stx_btime.tv_sec, stx.stx_btime.tv_nsec);
|
|
if (stx.stx_mask & STATX_ATIME)
|
|
verbose(" atime %llu.%09u\n", stx.stx_atime.tv_sec, stx.stx_atime.tv_nsec);
|
|
if (stx.stx_mask & STATX_MTIME)
|
|
verbose(" mtime %llu.%09u\n", stx.stx_mtime.tv_sec, stx.stx_mtime.tv_nsec);
|
|
if (stx.stx_mask & STATX_CTIME)
|
|
verbose(" ctime %llu.%09u\n", stx.stx_ctime.tv_sec, stx.stx_ctime.tv_nsec);
|
|
|
|
/* Handle additional checks the user specified */
|
|
for (; *argv; argv++) {
|
|
char *arg = *argv;
|
|
|
|
if (strcmp("cmp_ref", arg) == 0) {
|
|
/* cmp_ref - check ref file has same stats */
|
|
cmp_ref(&stx, mask);
|
|
continue;
|
|
}
|
|
|
|
if (strncmp(arg, "stx_", 4) == 0) {
|
|
/* stx_<field>=<n> - check field set to n */
|
|
check_field(&stx, *argv);
|
|
continue;
|
|
}
|
|
|
|
if (strncmp("ref=", arg, 4) == 0) {
|
|
/* ref=<file> - set reference stats from file */
|
|
get_reference(arg + 4);
|
|
continue;
|
|
}
|
|
|
|
if (strcmp("ts_order", arg) == 0) {
|
|
/* ts_order - check timestamp order */
|
|
check_timestamp_order(&stx);
|
|
continue;
|
|
}
|
|
|
|
if (strncmp("ts_origin=", arg, 10) == 0) {
|
|
/* ts_origin=<sec>.<nsec> - set origin timestamp */
|
|
set_origin_timestamp(arg + 10);
|
|
continue;
|
|
}
|
|
|
|
if (strncmp("ts=", arg, 3) == 0) {
|
|
/* ts=<a>,<b> - check timestamp b is same as a or after */
|
|
check_timestamp(&stx, arg + 3);
|
|
continue;
|
|
}
|
|
|
|
bad_arg("check '%s' not supported\n", arg);
|
|
}
|
|
|
|
if (failed) {
|
|
printf("Failed\n");
|
|
exit(1);
|
|
}
|
|
|
|
verbose("Success\n");
|
|
exit(0);
|
|
}
|