tests: check verbose decoding of 32-bit and 64-bit struct stat

* tests/stat.c: New file.
* tests/stat32.c: Likewise.
* tests/stat32-v.test: New file.
* tests/stat64-v.test: Likewise.
* tests/Makefile.am (check_PROGRAMS): Add stat and stat32.
(stat_CFLAGS): Define.
(TESTS): Add stat32-v.test and stat64-v.test.
* tests/.gitignore: Add stat and stat32.
diff --git a/tests/.gitignore b/tests/.gitignore
index b53ef6a..af463d0 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -8,6 +8,8 @@
 set_ptracer_any
 sigaction
 stack-fcall
+stat
+stat32
 statfs
 uid
 uid16
diff --git a/tests/Makefile.am b/tests/Makefile.am
index e307c57..b88ed55 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -13,12 +13,15 @@
 	set_ptracer_any \
 	sigaction \
 	stack-fcall \
+	stat \
+	stat32 \
 	statfs \
 	uid \
 	uid16 \
 	uid32 \
 	uio
 
+stat_CFLAGS = $(AM_CFLAGS) -D_FILE_OFFSET_BITS=64
 statfs_CFLAGS = $(AM_CFLAGS) -D_FILE_OFFSET_BITS=64
 uio_CFLAGS = $(AM_CFLAGS) -D_FILE_OFFSET_BITS=64
 stack_fcall_SOURCES = stack-fcall.c \
@@ -33,6 +36,8 @@
 	scm_rights-fd.test \
 	sigaction.test \
 	stat.test \
+	stat32-v.test \
+	stat64-v.test \
 	statfs.test \
 	mmsg.test \
 	net.test \
diff --git a/tests/stat.c b/tests/stat.c
new file mode 100644
index 0000000..21e37fb
--- /dev/null
+++ b/tests/stat.c
@@ -0,0 +1,171 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <assert.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+
+#if defined MAJOR_IN_SYSMACROS
+# include <sys/sysmacros.h>
+#elif defined MAJOR_IN_MKDEV
+# include <sys/mkdev.h>
+#else
+# include <sys/types.h>
+#endif
+
+#undef STAT_FNAME
+#undef NR_stat
+
+#if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64
+# include <sys/stat.h>
+# define STAT_FNAME "stat(64)?"
+#else
+# include <sys/syscall.h>
+# if defined __NR_stat
+#  define NR_stat __NR_stat
+#  define STAT_FNAME "stat"
+# elif defined __NR_newstat
+#  define NR_stat __NR_newstat
+#  define STAT_FNAME "newstat"
+# endif
+# ifdef STAT_FNAME
+/* for S_IFMT */
+#  define stat libc_stat
+#  define stat64 libc_stat64
+#  include <sys/stat.h>
+#  undef stat
+#  undef stat64
+#  undef st_atime
+#  undef st_mtime
+#  undef st_ctime
+
+#  undef dev_t
+#  undef ino_t
+#  undef mode_t
+#  undef nlink_t
+#  undef uid_t
+#  undef gid_t
+#  undef off_t
+#  undef loff_t
+#  define dev_t __kernel_dev_t
+#  define ino_t __kernel_ino_t
+#  define mode_t __kernel_mode_t
+#  define nlink_t __kernel_nlink_t
+#  define uid_t __kernel_uid_t
+#  define gid_t __kernel_gid_t
+#  define off_t __kernel_off_t
+#  define loff_t __kernel_loff_t
+#  include <asm/stat.h>
+#  endif /* STAT_FNAME */
+#endif /* _FILE_OFFSET_BITS */
+
+#ifdef STAT_FNAME
+
+static void
+print_ftype(unsigned int mode)
+{
+	if (S_ISREG(mode))
+		printf("S_IFREG");
+	else if (S_ISDIR(mode))
+		printf("S_IFDIR");
+	else if (S_ISCHR(mode))
+		printf("S_IFCHR");
+	else if (S_ISBLK(mode))
+		printf("S_IFBLK");
+	else
+		printf("%#o", mode & S_IFMT);
+}
+
+static void
+print_perms(unsigned int mode)
+{
+	printf("%#o", mode & ~S_IFMT);
+}
+
+static void
+print_time(time_t t)
+{
+	if (!t) {
+		printf("0");
+		return;
+	}
+
+	struct tm *p = localtime(&t);
+
+	if (p)
+		printf("%02d/%02d/%02d-%02d:%02d:%02d",
+		       p->tm_year + 1900, p->tm_mon + 1, p->tm_mday,
+		       p->tm_hour, p->tm_min, p->tm_sec);
+	else
+		printf("%llu", (unsigned long long) t);
+}
+
+int
+main(int ac, const char **av)
+{
+	assert(ac == 2);
+	struct stat stb;
+
+#ifdef NR_stat
+	if (sizeof(stb.st_size) > 4)
+		return 77;
+	assert(syscall(NR_stat, av[1], &stb) == 0);
+#else
+	assert(stat(av[1], &stb) == 0);
+#endif
+
+	printf(STAT_FNAME "\\(\"%s\", \\{", av[1]);
+	printf("st_dev=makedev\\(%u, %u\\)",
+	       (unsigned int) major(stb.st_dev),
+	       (unsigned int) minor(stb.st_dev));
+	printf(", st_ino=%llu", (unsigned long long) stb.st_ino);
+	printf(", st_mode=");
+		print_ftype(stb.st_mode);
+		printf("\\|");
+		print_perms(stb.st_mode);
+	printf(", st_nlink=%u", (unsigned int) stb.st_nlink);
+	printf(", st_uid=%u", (unsigned int) stb.st_uid);
+	printf(", st_gid=%u", (unsigned int) stb.st_gid);
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+	printf(", st_blksize=%u", (unsigned int) stb.st_blksize);
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+	printf(", st_blocks=%u", (unsigned int) stb.st_blocks);
+#endif
+
+	switch (stb.st_mode & S_IFMT) {
+	case S_IFCHR: case S_IFBLK:
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+		printf(", st_rdev=makedev\\(%u, %u\\)",
+		       (unsigned int) major(stb.st_rdev),
+		       (unsigned int) minor(stb.st_rdev));
+#else
+		printf(", st_size=makedev\\(%u, %u\\)",
+		       (unsigned int) major(stb.st_size),
+		       (unsigned int) minor(stb.st_size));
+#endif
+		break;
+	default:
+		printf(", st_size=%llu", (unsigned long long) stb.st_size);
+	}
+
+	printf(", st_atime=");
+		print_time(stb.st_atime);
+	printf(", st_mtime=");
+		print_time(stb.st_mtime);
+	printf(", st_ctime=");
+		print_time(stb.st_ctime);
+	printf("(, st_flags=[0-9]+)?");
+	printf("(, st_fstype=[^,]*)?");
+	printf("(, st_gen=[0-9]+)?");
+	printf("\\}\\) += 0\n");
+	return 0;
+}
+
+#else /* !STAT_FNAME */
+int main(void)
+{
+	return 77;
+}
+#endif
diff --git a/tests/stat32-v.test b/tests/stat32-v.test
new file mode 100755
index 0000000..74373c3
--- /dev/null
+++ b/tests/stat32-v.test
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# Check verbose decoding of 32-bit stat syscall.
+
+. "${srcdir=.}/init.sh"
+
+check_prog dd
+check_prog grep
+check_prog touch
+
+OUT="$LOG.out"
+size=233811181
+sample=stat32_sample
+
+umask 022
+truncate_cmd="dd seek=$size obs=1 count=0 if=/dev/null of=$sample"
+$truncate_cmd > "$OUT" 2>&1 || {
+	cat "$OUT"
+	framework_skip_ 'failed to create a large sparse file'
+}
+
+./stat32 $sample > /dev/null || {
+	if [ $? -eq 77 ]; then
+		framework_skip_ '32-bit stat syscall is not available'
+	else
+		fail_ 'stat32 failed'
+	fi
+}
+
+touch -t 0102030405 $sample
+
+for f in $sample . /dev/null; do
+	args="-v -efile ./stat32 $f"
+	$STRACE -o "$LOG" $args > "$OUT" &&
+	LC_ALL=C grep -E -x -f "$OUT" "$LOG" > /dev/null || {
+		cat "$OUT" "$LOG"
+		fail_ "$STRACE $args output mismatch"
+	}
+done
+
+rm -f $sample "$OUT"
+
+exit 0
diff --git a/tests/stat32.c b/tests/stat32.c
new file mode 100644
index 0000000..a074251
--- /dev/null
+++ b/tests/stat32.c
@@ -0,0 +1 @@
+#include "stat.c"
diff --git a/tests/stat64-v.test b/tests/stat64-v.test
new file mode 100755
index 0000000..7e08e25
--- /dev/null
+++ b/tests/stat64-v.test
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# Check verbose decoding of 64-bit stat syscall.
+
+. "${srcdir=.}/init.sh"
+
+check_prog dd
+check_prog grep
+check_prog touch
+
+OUT="$LOG.out"
+size=46118400000
+sample=stat64_sample
+
+umask 022
+truncate_cmd="dd seek=$size obs=1 count=0 if=/dev/null of=$sample"
+$truncate_cmd > "$OUT" 2>&1 || {
+	cat "$OUT"
+	framework_skip_ 'failed to create a large sparse file'
+}
+
+./stat $sample > /dev/null ||
+	fail_ 'stat failed'
+
+touch -t 0102030405 $sample
+
+for f in $sample . /dev/null; do
+	args="-v -efile ./stat $f"
+	$STRACE -o "$LOG" $args > "$OUT" &&
+	LC_ALL=C grep -E -x -f "$OUT" "$LOG" > /dev/null || {
+		cat "$OUT" "$LOG"
+		fail_ "$STRACE $args output mismatch"
+	}
+done
+
+rm -f $sample "$OUT"
+
+exit 0