* configure.ac (AC_CHECK_HEADERS): Add linux/dqblk_xfs.h, sys/quota.h.
* tests/.gitignore: Add quotactl, quotactl-xfs
* tests/Makefile.am (check_PROGRAMS): Likewise.
  (DECODER_TESTS): Add quotactl.test, quotactl-v.test,
  quotactl-xfs.test, quotactl-xfs-v.test.
  (EXTRA_DIST): quotactl.h
* quotactl-v.test: New file.
* quotactl-xfs-v.test: Likewise.
* quotactl-xfs.c: Likewise.
* quotactl-xfs.test: Likewise.
* quotactl.c: Likewise.
* quotactl.h: Likewise.
* quotactl.test: Likewise.
---
 configure.ac              |    3 +
 tests/.gitignore          |    2 +
 tests/Makefile.am         |    7 +
 tests/quotactl-v.test     |   10 ++
 tests/quotactl-xfs-v.test |   10 ++
 tests/quotactl-xfs.c      |  384 +++++++++++++++++++++++++++++++++++++++++++++
 tests/quotactl-xfs.test   |    6 +
 tests/quotactl.c          |  330 ++++++++++++++++++++++++++++++++++++++
 tests/quotactl.h          |  148 +++++++++++++++++
 tests/quotactl.test       |    6 +
 10 files changed, 906 insertions(+)
 create mode 100755 tests/quotactl-v.test
 create mode 100755 tests/quotactl-xfs-v.test
 create mode 100644 tests/quotactl-xfs.c
 create mode 100755 tests/quotactl-xfs.test
 create mode 100644 tests/quotactl.c
 create mode 100644 tests/quotactl.h
 create mode 100755 tests/quotactl.test

diff --git a/configure.ac b/configure.ac
index dc84a49..5c924e7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -353,6 +353,7 @@ AC_CHECK_HEADERS(m4_normalize([
        elf.h
        inttypes.h
        linux/bsg.h
+       linux/dqblk_xfs.h
        linux/falloc.h
        linux/fiemap.h
        linux/filter.h
@@ -362,6 +363,7 @@ AC_CHECK_HEADERS(m4_normalize([
        linux/mmtimer.h
        linux/msg.h
        linux/perf_event.h
+       linux/quota.h
        linux/seccomp.h
        linux/securebits.h
        linux/sem.h
@@ -378,6 +380,7 @@ AC_CHECK_HEADERS(m4_normalize([
        sys/fanotify.h
        sys/ipc.h
        sys/msg.h
+       sys/quota.h
        sys/reg.h
        sys/sem.h
        sys/shm.h
diff --git a/tests/.gitignore b/tests/.gitignore
index 91fb111..bdb2d9a 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -186,6 +186,8 @@ prlimit64
 pselect6
 ptrace
 pwritev
+quotactl
+quotactl-xfs
 read-write
 readahead
 readdir
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 75d3171..ac579c7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -243,6 +243,8 @@ check_PROGRAMS = \
        pselect6 \
        ptrace \
        pwritev \
+       quotactl \
+       quotactl-xfs \
        read-write \
        readahead \
        readdir \
@@ -573,6 +575,10 @@ DECODER_TESTS = \
        pselect6.test \
        ptrace.test \
        pwritev.test \
+       quotactl.test \
+       quotactl-v.test \
+       quotactl-xfs.test \
+       quotactl-xfs-v.test \
        read-write.test \
        readahead.test \
        readdir.test \
@@ -753,6 +759,7 @@ EXTRA_DIST = init.sh run.sh match.awk \
             pipe.expected \
             ppoll.expected \
             ppoll-v.expected \
+            quotactl.h \
             setfsugid.c \
             setreugid.c \
             setresugid.c \
diff --git a/tests/quotactl-v.test b/tests/quotactl-v.test
new file mode 100755
index 0000000..0eb5206
--- /dev/null
+++ b/tests/quotactl-v.test
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Check non-abbreviated quotactl syscall decoding.
+
+. "${srcdir=.}/init.sh"
+
+NAME=quotactl
+PROG_ARGS="-v"
+
+run_strace_match_diff -v -a1
diff --git a/tests/quotactl-xfs-v.test b/tests/quotactl-xfs-v.test
new file mode 100755
index 0000000..0a35b1d
--- /dev/null
+++ b/tests/quotactl-xfs-v.test
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Check non-abbreviated quotactl syscall decoding.
+
+. "${srcdir=.}/init.sh"
+
+NAME=quotactl-xfs
+PROG_ARGS="-v"
+
+run_strace_match_diff -v -a1 -e trace=quotactl
diff --git a/tests/quotactl-xfs.c b/tests/quotactl-xfs.c
new file mode 100644
index 0000000..bfbc83b
--- /dev/null
+++ b/tests/quotactl-xfs.c
@@ -0,0 +1,384 @@
+#include "tests.h"
+
+#include <asm/unistd.h>
+
+#if defined(__NR_quotactl) && \
+       (defined(HAVE_LINUX_QUOTA_H) || defined(HAVE_SYS_QUOTA_H)) && \
+       defined(HAVE_LINUX_DQBLK_XFS_H)
+
+# include <stdio.h>
+# include <string.h>
+# include <unistd.h>
+
+# include <linux/dqblk_xfs.h>
+
+# include "xlat.h"
+
+# include "quotactl.h"
+
+# if !defined(Q_GETNEXTQUOTA)
+#  define Q_XGETNEXTQUOTA XQM_CMD(0x9)
+# endif /* !defined(Q_GETNEXTQUOTA) */
+
+# ifndef Q_XGETQSTATV
+
+#  define Q_XGETQSTATV       XQM_CMD(8)
+#  define FS_QSTATV_VERSION1 1
+
+struct fs_qfilestatv {
+       __u64 qfs_ino;        /* inode number */
+       __u64 qfs_nblks;      /* number of BBs 512-byte-blks */
+       __u32 qfs_nextents;   /* number of extents */
+       __u32 qfs_pad;        /* pad for 8-byte alignment */
+};
+
+struct fs_quota_statv {
+       __s8                  qs_version;      /* version for future changes */
+       __u8                  qs_pad1;         /* pad for 16bit alignment */
+       __u16                 qs_flags;        /* XFS_QUOTA_.* flags */
+       __u32                 qs_incoredqs;    /* number of dquots incore */
+       struct fs_qfilestatv  qs_uquota;       /* user quota information */
+       struct fs_qfilestatv  qs_gquota;       /* group quota information */
+       struct fs_qfilestatv  qs_pquota;       /* project quota information */
+       __s32                 qs_btimelimit;   /* limit for blks timer */
+       __s32                 qs_itimelimit;   /* limit for inodes timer */
+       __s32                 qs_rtbtimelimit; /* limit for rt blks timer */
+       __u16                 qs_bwarnlimit;   /* limit for num warnings */
+       __u16                 qs_iwarnlimit;   /* limit for num warnings */
+       __u64                 qs_pad2[8];      /* for future proofing */
+};
+
+# endif /* !FS_QSTATV_VERSION1 */
+
+static struct xlat xfs_types[] = {
+       XLAT_PAIR(1 << 0, "XFS_USER_QUOTA"),
+       XLAT_PAIR(1 << 1, "XFS_PROJ_QUOTA"),
+       XLAT_PAIR(1 << 2, "XFS_GROUP_QUOTA"),
+       XLAT_END
+};
+
+static struct xlat xfs_flags[] = {
+       XLAT_PAIR(1 << 0, "XFS_QUOTA_UDQ_ACCT"),
+       XLAT_PAIR(1 << 1, "XFS_QUOTA_UDQ_ENFD"),
+       XLAT_PAIR(1 << 2, "XFS_QUOTA_GDQ_ACCT"),
+       XLAT_PAIR(1 << 3, "XFS_QUOTA_GDQ_ENFD"),
+       XLAT_PAIR(1 << 4, "XFS_QUOTA_PDQ_ACCT"),
+       XLAT_PAIR(1 << 5, "XFS_QUOTA_PDQ_ENFD"),
+       XLAT_END
+};
+
+
+static int verbose = 0;
+
+void
+print_xdisk_quota(int rc, void *ptr, void *arg)
+{
+       struct fs_disk_quota *dq = (struct fs_disk_quota *) ptr;
+       long out_arg = (long) arg;
+
+       if (((rc != 0) && out_arg) || (out_arg > 1)) {
+               printf("%p", dq);
+               return;
+       }
+
+       printf("{d_version=%" PRId8 ", ", dq->d_version);
+       printf("d_flags=");
+       printflags(xfs_types, (uint8_t)dq->d_flags, "XFS_???_QUOTA");
+
+       printf(", d_fieldmask=%#" PRIx16 ", "
+               "d_id=%" PRIu32 ", "
+               "d_blk_hardlimit=%" PRI__u64 ", "
+               "d_blk_softlimit=%" PRI__u64 ", "
+               "d_ino_hardlimit=%" PRI__u64 ", "
+               "d_ino_softlimit=%" PRI__u64 ", "
+               "d_bcount=%" PRI__u64 ", "
+               "d_icount=%" PRI__u64 ", ",
+               dq->d_fieldmask,
+               dq->d_id,
+               dq->d_blk_hardlimit,
+               dq->d_blk_softlimit,
+               dq->d_ino_hardlimit,
+               dq->d_ino_softlimit,
+               dq->d_bcount,
+               dq->d_icount);
+
+       if (!verbose) {
+               printf("...}");
+               return;
+       }
+
+       printf("d_itimer=%" PRId32 ", "
+               "d_btimer=%" PRId32 ", "
+               "d_iwarns=%" PRIu16 ", "
+               "d_bwarns=%" PRIu16 ", "
+               "d_rtb_hardlimit=%" PRI__u64 ", "
+               "d_rtb_softlimit=%" PRI__u64 ", "
+               "d_rtbcount=%" PRI__u64 ", "
+               "d_rtbtimer=%" PRId32 ", "
+               "d_rtbwarns=%" PRIu16 "}",
+               dq->d_itimer,
+               dq->d_btimer,
+               dq->d_iwarns,
+               dq->d_bwarns,
+               dq->d_rtb_hardlimit,
+               dq->d_rtb_softlimit,
+               dq->d_rtbcount,
+               dq->d_rtbtimer,
+               dq->d_rtbwarns);
+}
+
+void
+print_xquota_stat(int rc, void *ptr, void *arg)
+{
+       struct fs_quota_stat *qs = (struct fs_quota_stat *) ptr;
+       long out_arg = (long) arg;
+
+       if (((rc != 0) && out_arg) || (out_arg > 1)) {
+               printf("%p", qs);
+               return;
+       }
+
+       printf("{qs_version=%" PRId8 ", ", qs->qs_version);
+
+       if (!verbose) {
+               printf("...}");
+               return;
+       }
+
+       printf("qs_flags=");
+       printflags(xfs_flags, qs->qs_flags, "XFS_QUOTA_???");
+       printf(", qs_uquota={"
+                       "qfs_ino=%" PRI__u64 ", "
+                       "qfs_nblks=%" PRI__u64 ", "
+                       "qfs_nextents=%" PRIu32 "}, "
+               "qs_gquota={"
+                       "qfs_ino=%" PRI__u64 ", "
+                       "qfs_nblks=%" PRI__u64 ", "
+                       "qfs_nextents=%" PRIu32 "}, "
+               "qs_incoredqs=%" PRIu32 ", "
+               "qs_btimelimit=%" PRId32 ", "
+               "qs_itimelimit=%" PRId32 ", "
+               "qs_rtbtimelimit=%" PRId32 ", "
+               "qs_bwarnlimit=%" PRIu16 ", "
+               "qs_iwarnlimit=%" PRIu16 "}",
+               qs->qs_uquota.qfs_ino,
+               qs->qs_uquota.qfs_nblks,
+               qs->qs_uquota.qfs_nextents,
+               qs->qs_gquota.qfs_ino,
+               qs->qs_gquota.qfs_nblks,
+               qs->qs_gquota.qfs_nextents,
+               qs->qs_incoredqs,
+               qs->qs_btimelimit,
+               qs->qs_itimelimit,
+               qs->qs_rtbtimelimit,
+               qs->qs_bwarnlimit,
+               qs->qs_iwarnlimit);
+}
+
+void
+print_xquota_statv(int rc, void *ptr, void *arg)
+{
+       struct fs_quota_statv *qs = (struct fs_quota_statv *) ptr;
+       long out_arg = (long) arg;
+
+       if (((rc != 0) && out_arg) || (out_arg > 1)) {
+               printf("%p", qs);
+               return;
+       }
+
+       printf("{qs_version=%" PRId8 ", ", qs->qs_version);
+
+       if (!verbose) {
+               printf("...}");
+               return;
+       }
+
+       printf("qs_flags=");
+       printflags(xfs_flags, qs->qs_flags, "XFS_QUOTA_???");
+       printf(", qs_incoredqs=%" PRIu32 ", "
+               "qs_uquota={"
+                       "qfs_ino=%" PRI__u64 ", "
+                       "qfs_nblks=%" PRI__u64 ", "
+                       "qfs_nextents=%" PRIu32 "}, "
+               "qs_gquota={"
+                       "qfs_ino=%" PRI__u64 ", "
+                       "qfs_nblks=%" PRI__u64 ", "
+                       "qfs_nextents=%" PRIu32 "}, "
+               "qs_pquota={"
+                       "qfs_ino=%" PRI__u64 ", "
+                       "qfs_nblks=%" PRI__u64 ", "
+                       "qfs_nextents=%" PRIu32 "}, "
+               "qs_btimelimit=%" PRId32 ", "
+               "qs_itimelimit=%" PRId32 ", "
+               "qs_rtbtimelimit=%" PRId32 ", "
+               "qs_bwarnlimit=%" PRIu16 ", "
+               "qs_iwarnlimit=%" PRIu16 "}",
+               qs->qs_incoredqs,
+               qs->qs_uquota.qfs_ino,
+               qs->qs_uquota.qfs_nblks,
+               qs->qs_uquota.qfs_nextents,
+               qs->qs_gquota.qfs_ino,
+               qs->qs_gquota.qfs_nblks,
+               qs->qs_gquota.qfs_nextents,
+               qs->qs_pquota.qfs_ino,
+               qs->qs_pquota.qfs_nblks,
+               qs->qs_pquota.qfs_nextents,
+               qs->qs_btimelimit,
+               qs->qs_itimelimit,
+               qs->qs_rtbtimelimit,
+               qs->qs_bwarnlimit,
+               qs->qs_iwarnlimit);
+}
+
+int
+main(int argc, char *argv[])
+{
+       char bogus_special_str[sizeof(void *) * 2 + sizeof("0x")];
+       char bogus_addr_str[sizeof(void *) * 2 + sizeof("0x")];
+       char unterminated_str[sizeof(void *) * 2 + sizeof("0x")];
+
+       long rc;
+       struct fs_disk_quota *xdq = tail_alloc(sizeof(*xdq));
+       struct fs_quota_stat *xqstat = tail_alloc(sizeof(*xqstat));
+       struct fs_quota_statv *xqstatv = tail_alloc(sizeof(*xqstatv));
+       uint32_t *flags = tail_alloc(sizeof(*flags));
+       char *unterminated = tail_memdup(unterminated_data,
+               sizeof(unterminated_data));
+
+       if ((argc > 1) && !strcmp(argv[1], "-v"))
+               verbose = 1;
+
+       snprintf(bogus_special_str, sizeof(bogus_special_str), "%p",
+               bogus_special);
+       snprintf(bogus_addr_str, sizeof(bogus_addr_str), "%p",
+               bogus_addr);
+       snprintf(unterminated_str, sizeof(unterminated_str), "%p",
+               unterminated);
+
+
+       /* Q_XQUOTAON */
+
+       *flags = 0xdeadbeef;
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_STR,
+               ARG_STR(QCMD(Q_XQUOTAON, USRQUOTA)),
+               ARG_STR("/dev/bogus/"), flags, "[XFS_QUOTA_UDQ_ACCT|"
+               "XFS_QUOTA_UDQ_ENFD|XFS_QUOTA_GDQ_ACCT|XFS_QUOTA_GDQ_ENFD|"
+               "XFS_QUOTA_PDQ_ENFD|0xdeadbec0]");
+
+       rc = syscall(__NR_quotactl, QCMD(Q_XQUOTAON, 0xfacefeed), bogus_dev,
+               bogus_id, bogus_addr);
+       printf("quotactl(QCMD(Q_XQUOTAON, %#x /* ???QUOTA */), %s, "
+               "%p) = %s\n",
+               QCMD_TYPE(QCMD(Q_XQUOTAON, 0xfacefeed)),
+               bogus_dev_str, bogus_addr, sprintrc(rc));
+
+
+       /* Q_XQUOTAOFF */
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_STR,
+               ARG_STR(QCMD(Q_XQUOTAOFF, USRQUOTA)),
+               bogus_special, bogus_special_str,
+               bogus_addr, bogus_addr_str);
+       check_quota(CQF_ID_SKIP | CQF_ADDR_STR,
+               ARG_STR(QCMD(Q_XQUOTAOFF, GRPQUOTA)),
+               ARG_STR("/dev/bogus/"),
+               ARG_STR(NULL));
+       check_quota(CQF_ID_SKIP | CQF_ADDR_STR,
+               QCMD(Q_XQUOTAOFF, 3), "QCMD(Q_XQUOTAOFF, 0x3 /* ???QUOTA */)",
+               ARG_STR("/dev/bogus/"), flags, "[XFS_QUOTA_UDQ_ACCT|"
+               "XFS_QUOTA_UDQ_ENFD|XFS_QUOTA_GDQ_ACCT|XFS_QUOTA_GDQ_ENFD|"
+               "XFS_QUOTA_PDQ_ENFD|0xdeadbec0]");
+
+
+       /* Q_XGETQUOTA */
+
+       /* Trying our best to get successful result */
+       check_quota(CQF_ADDR_CB, ARG_STR(QCMD(Q_GETQUOTA, USRQUOTA)),
+               ARG_STR("/dev/sda1"), getuid(), xdq, print_xdisk_quota,
+               (intptr_t)1);
+
+       check_quota(CQF_ADDR_CB, ARG_STR(QCMD(Q_GETQUOTA, GRPQUOTA)),
+               ARG_STR(NULL), -1, xdq, print_xdisk_quota, (intptr_t)2);
+
+
+       /* Q_XGETNEXTQUOTA */
+
+       check_quota(CQF_ADDR_CB, ARG_STR(QCMD(Q_XGETNEXTQUOTA, USRQUOTA)),
+               ARG_STR("/dev/sda1"), 0, xdq, print_xdisk_quota,
+               (intptr_t)1);
+
+
+       /* Q_XSETQLIM */
+
+       check_quota(CQF_NONE, ARG_STR(QCMD(Q_XSETQLIM, PRJQUOTA)),
+               bogus_special, bogus_special_str, 0, bogus_addr);
+
+       fill_memory_ex((char *)xdq, sizeof(*xdq), 0x8e);
+
+       check_quota(CQF_ADDR_CB, ARG_STR(QCMD(Q_XSETQLIM, PRJQUOTA)),
+               bogus_dev, bogus_dev_str, 3141592653U,
+               xdq, print_xdisk_quota, (intptr_t)0);
+
+
+       /* Q_XGETQSTAT */
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_CB,
+               ARG_STR(QCMD(Q_XGETQSTAT, USRQUOTA)),
+               ARG_STR("/dev/sda1"), xqstat, print_xquota_stat, (intptr_t)1);
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_CB,
+               ARG_STR(QCMD(Q_XGETQSTATV, PRJQUOTA)),
+               unterminated, unterminated_str,
+               xqstat + 1, print_xquota_stat, (intptr_t)2);
+
+
+       /* Q_XGETQSTATV */
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_CB,
+               ARG_STR(QCMD(Q_XGETQSTAT, USRQUOTA)),
+               ARG_STR("/dev/sda1"), xqstatv, print_xquota_statv, 1);
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_CB,
+               ARG_STR(QCMD(Q_XGETQSTATV, GRPQUOTA)),
+               ARG_STR(NULL), xqstatv, print_xquota_statv, (intptr_t)2);
+
+
+       /* Q_XQUOTARM */
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_STR,
+               ARG_STR(QCMD(Q_XQUOTARM, PRJQUOTA)),
+               bogus_special, bogus_special_str, ARG_STR(NULL));
+       check_quota(CQF_ID_SKIP,
+               ARG_STR(QCMD(Q_XQUOTARM, USRQUOTA)),
+               unterminated, unterminated_str, flags + 1);
+
+       *flags = 0xdeadbeef;
+       check_quota(CQF_ID_SKIP | CQF_ADDR_STR,
+               ARG_STR(QCMD(Q_XQUOTARM, GRPQUOTA)),
+               ARG_STR(NULL), flags, "[XFS_USER_QUOTA|""XFS_PROJ_QUOTA|"
+               "XFS_GROUP_QUOTA|0xdeadbee8]");
+
+
+       /* Q_XQUOTASYNC */
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_SKIP,
+               ARG_STR(QCMD(Q_XQUOTASYNC, USRQUOTA)),
+               bogus_special, bogus_special_str);
+       check_quota(CQF_ID_SKIP | CQF_ADDR_SKIP,
+               QCMD(Q_XQUOTASYNC, 0xfff),
+               "QCMD(Q_XQUOTASYNC, 0xff /* ???QUOTA */)",
+               ARG_STR(NULL));
+
+       puts("+++ exited with 0 +++");
+
+       return 0;
+}
+
+#else
+
+SKIP_MAIN_UNDEFINED("__NR_quotactl && "
+       "(HAVE_LINUX_QUOTA_H || HAVE_SYS_QUOTA_H) && "
+       "HAVE_LINUX_DQBLK_XFS_H");
+
+#endif
diff --git a/tests/quotactl-xfs.test b/tests/quotactl-xfs.test
new file mode 100755
index 0000000..b101a8c
--- /dev/null
+++ b/tests/quotactl-xfs.test
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# Check quotactl syscall decoding.
+
+. "${srcdir=.}/init.sh"
+run_strace_match_diff -a1 -e trace=quotactl
diff --git a/tests/quotactl.c b/tests/quotactl.c
new file mode 100644
index 0000000..1efb15c
--- /dev/null
+++ b/tests/quotactl.c
@@ -0,0 +1,330 @@
+#include "tests.h"
+
+#include <asm/unistd.h>
+
+#if defined(__NR_quotactl) && \
+       (defined(HAVE_LINUX_QUOTA_H) || defined(HAVE_SYS_QUOTA_H))
+
+# include <inttypes.h>
+# include <stdint.h>
+# include <stdio.h>
+# include <string.h>
+# include <unistd.h>
+
+# include "xlat.h"
+# include "xlat/quota_formats.h"
+
+# include "quotactl.h"
+
+# if !defined(HAVE_LINUX_QUOTA_H)
+/* Some dirty hacks in order to make sys/quota.h usable as a backup */
+
+#  define if_dqblk dqblk
+#  define if_nextdqblk nextdqblk
+#  define if_dqinfo dqinfo
+
+# endif /* !defined(HAVE_LINUX_QUOTA_H) */
+
+# if !defined(Q_GETNEXTQUOTA)
+
+#  define Q_GETNEXTQUOTA 0x800009
+
+struct if_nextdqblk {
+       __u64 dqb_bhardlimit;
+       __u64 dqb_bsoftlimit;
+       __u64 dqb_curspace;
+       __u64 dqb_ihardlimit;
+       __u64 dqb_isoftlimit;
+       __u64 dqb_curinodes;
+       __u64 dqb_btime;
+       __u64 dqb_itime;
+       __u32 dqb_valid;
+       __u32 dqb_id;
+};
+# endif /* !defined(Q_GETNEXTQUOTA) */
+
+/* From documentation */
+static const struct xlat dqblk_valid_vals[] = {
+       XLAT_PAIR(1,  "QIF_BLIMITS"),
+       XLAT_PAIR(2,  "QIF_SPACE"),
+       XLAT_PAIR(4,  "QIF_ILIMITS"),
+       XLAT_PAIR(8,  "QIF_INODES"),
+       XLAT_PAIR(16, "QIF_BTIME"),
+       XLAT_PAIR(32, "QIF_ITIME"),
+       XLAT_END
+};
+
+static const struct xlat dqinfo_flags_vals[] = {
+       XLAT_PAIR(1 << 0,  "DQF_ROOT_SQUASH"),
+       XLAT_PAIR(1 << 16, "DQF_SYS_FILE"),
+       XLAT_END
+};
+
+static const struct xlat dqinfo_valid_vals[] = {
+       XLAT_PAIR(1, "IIF_BGRACE"),
+       XLAT_PAIR(2, "IIF_IGRACE"),
+       XLAT_PAIR(4, "IIF_FLAGS"),
+       XLAT_END
+};
+
+
+static int verbose = 0;
+
+void
+print_dqblk(long rc, void *db_void, void *arg)
+{
+       struct if_dqblk *db = (struct if_dqblk *) db_void;
+       long out_arg = (long) arg;
+
+       if (((rc != 0) && out_arg) || (out_arg > 1)) {
+               printf("%p", db);
+               return;
+       }
+
+       printf("{dqb_bhardlimit=%" PRI__u64 ", "
+               "dqb_bsoftlimit=%" PRI__u64 ", "
+               "dqb_curspace=%" PRI__u64 ", "
+               "dqb_ihardlimit=%" PRI__u64 ", "
+               "dqb_isoftlimit=%" PRI__u64 ", "
+               "dqb_curinodes=%" PRI__u64 ", ",
+               db->dqb_bhardlimit,
+               db->dqb_bsoftlimit,
+               db->dqb_curspace,
+               db->dqb_ihardlimit,
+               db->dqb_isoftlimit,
+               db->dqb_curinodes);
+
+       if (!verbose) {
+               printf("...}");
+               return;
+       }
+
+       printf("dqb_btime=%" PRI__u64 ", "
+               "dqb_itime=%" PRI__u64 ", "
+               "dqb_valid=",
+               db->dqb_btime,
+               db->dqb_itime);
+
+       printflags(dqblk_valid_vals, db->dqb_valid, "QIF_???");
+       printf("}");
+}
+
+void
+print_nextdqblk(long rc, void *db_void, void *arg)
+{
+       struct if_nextdqblk *db = (struct if_nextdqblk *) db_void;
+       long out_arg = (long) arg;
+
+       if (((rc != 0) && out_arg) || (out_arg > 1)) {
+               printf("%p", db);
+               return;
+       }
+
+       printf("{dqb_bhardlimit=%" PRI__u64 ", "
+               "dqb_bsoftlimit=%" PRI__u64 ", "
+               "dqb_curspace=%" PRI__u64 ", "
+               "dqb_ihardlimit=%" PRI__u64 ", "
+               "dqb_isoftlimit=%" PRI__u64 ", "
+               "dqb_curinodes=%" PRI__u64 ", ",
+               db->dqb_bhardlimit,
+               db->dqb_bsoftlimit,
+               db->dqb_curspace,
+               db->dqb_ihardlimit,
+               db->dqb_isoftlimit,
+               db->dqb_curinodes);
+
+       if (!verbose) {
+               printf("dqb_id=%u, ...}", db->dqb_id);
+               return;
+       }
+
+       printf("dqb_btime=%" PRI__u64 ", "
+               "dqb_itime=%" PRI__u64 ", "
+               "dqb_valid=",
+               db->dqb_btime,
+               db->dqb_itime);
+
+       printflags(dqblk_valid_vals, db->dqb_valid, "QIF_???");
+       printf("dqb_id=%u}", db->dqb_id);
+}
+
+void
+print_dqinfo(long rc, void *di_void, void *arg)
+{
+       struct if_dqinfo *di = (struct if_dqinfo *) di_void;
+       long out_arg = (long) arg;
+
+       if (((rc != 0) && out_arg) || (out_arg > 1)) {
+               printf("%p", di);
+               return;
+       }
+
+       printf("{dqi_bgrace=%" PRI__u64 ", "
+               "dqi_igrace=%" PRI__u64 ", "
+               "dqi_flags=",
+               di->dqi_bgrace, di->dqi_igrace);
+
+       printflags(dqinfo_flags_vals, di->dqi_flags, "DQF_???");
+       printf(", dqi_valid=");
+       printflags(dqinfo_valid_vals, di->dqi_valid, "IIF_???");
+       printf("}");
+}
+
+
+int
+main(int argc, char *argv[])
+{
+       char bogus_special_str[sizeof(void *) * 2 + sizeof("0x")];
+       char unterminated_str[sizeof(void *) * 2 + sizeof("0x")];
+
+       long rc;
+       char *unterminated = tail_memdup(unterminated_data,
+               sizeof(unterminated_data));
+       struct if_dqblk *dqblk = tail_alloc(sizeof(*dqblk));
+       struct if_dqinfo *dqinfo = tail_alloc(sizeof(*dqinfo));
+       uint32_t *fmt = tail_alloc(sizeof(*fmt));
+       struct if_nextdqblk *nextdqblk = tail_alloc(sizeof(*nextdqblk));
+
+
+       if ((argc > 1) && !strcmp(argv[1], "-v"))
+               verbose = 1;
+
+       snprintf(bogus_special_str, sizeof(bogus_special_str), "%p",
+               bogus_special);
+       snprintf(unterminated_str, sizeof(unterminated_str), "%p",
+               unterminated);
+
+
+       /* Invalid commands */
+
+       rc = syscall(__NR_quotactl, bogus_cmd, bogus_special, bogus_id,
+               bogus_addr);
+       printf("quotactl(QCMD(%#x /* Q_??? */, %#x /* ???QUOTA */), %p, %u, "
+               "%p) = %s\n",
+               QCMD_CMD(bogus_cmd), QCMD_TYPE(bogus_cmd), bogus_special,
+               bogus_id, bogus_addr, sprintrc(rc));
+
+       rc = syscall(__NR_quotactl, 0, NULL, -1, NULL);
+       printf("quotactl(QCMD(0 /* Q_??? */, USRQUOTA), NULL, -1, NULL) = %s\n",
+               sprintrc(rc));
+
+
+       /* Q_QUOTAON */
+
+       check_quota(CQF_ID_STR | CQF_ADDR_STR,
+               ARG_STR(QCMD(Q_QUOTAON, USRQUOTA)),
+               ARG_STR("/dev/bogus/"), ARG_STR(QFMT_VFS_OLD),
+               ARG_STR("/tmp/bogus/"));
+
+       rc = syscall(__NR_quotactl, QCMD(Q_QUOTAON, 0xfacefeed), bogus_dev,
+               bogus_id, bogus_addr);
+       printf("quotactl(QCMD(Q_QUOTAON, %#x /* ???QUOTA */), %s, "
+               "%#x /* QFMT_VFS_??? */, %p) = %s\n",
+               QCMD_TYPE(QCMD(Q_QUOTAON, 0xfacefeed)),
+               bogus_dev_str, bogus_id, bogus_addr, sprintrc(rc));
+
+
+       /* Q_QUOTAOFF */
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_SKIP,
+               ARG_STR(QCMD(Q_QUOTAOFF, USRQUOTA)),
+               bogus_special, bogus_special_str);
+       check_quota(CQF_ID_SKIP | CQF_ADDR_SKIP,
+               ARG_STR(QCMD(Q_QUOTAOFF, GRPQUOTA)),
+               ARG_STR("/dev/bogus/"));
+       check_quota(CQF_ID_SKIP | CQF_ADDR_SKIP,
+               ARG_STR(QCMD(Q_QUOTAOFF, PRJQUOTA)), ARG_STR(NULL));
+       check_quota(CQF_ID_SKIP | CQF_ADDR_SKIP,
+               QCMD(Q_QUOTAOFF, 3), "QCMD(Q_QUOTAOFF, 0x3 /* ???QUOTA */)",
+               ARG_STR(NULL));
+
+
+       /* Q_GETQUOTA */
+
+       /* Trying our best to get successful result */
+       check_quota(CQF_ADDR_CB, ARG_STR(QCMD(Q_GETQUOTA, USRQUOTA)),
+               ARG_STR("/dev/sda1"), getuid(), dqblk, print_dqblk,
+               (intptr_t)1);
+
+       check_quota(CQF_ADDR_CB, ARG_STR(QCMD(Q_GETQUOTA, GRPQUOTA)),
+               ARG_STR(NULL), -1, dqblk, print_dqblk, (intptr_t)2);
+
+
+       /* Q_GETNEXTQUOTA */
+
+       check_quota(CQF_ADDR_CB, ARG_STR(QCMD(Q_GETNEXTQUOTA, USRQUOTA)),
+               ARG_STR("/dev/sda1"), 0, nextdqblk, print_nextdqblk,
+               (intptr_t)1);
+
+
+       /* Q_SETQUOTA */
+
+       fill_memory((char *)dqblk, sizeof(*dqblk));
+
+       check_quota(CQF_NONE, ARG_STR(QCMD(Q_SETQUOTA, PRJQUOTA)),
+               bogus_special, bogus_special_str, 0, bogus_addr);
+
+       check_quota(CQF_ADDR_CB, ARG_STR(QCMD(Q_SETQUOTA, PRJQUOTA)),
+               ARG_STR("/dev/bogus/"), 3141592653U, dqblk, print_dqblk,
+               (intptr_t)0);
+
+
+       /* Q_GETINFO */
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_CB,
+               ARG_STR(QCMD(Q_GETINFO, GRPQUOTA)),
+               ARG_STR("/dev/sda1"), dqinfo, print_dqinfo, (intptr_t)1);
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_CB,
+               ARG_STR(QCMD(Q_GETINFO, GRPQUOTA)),
+               bogus_special, bogus_special_str, dqinfo,
+               print_dqinfo, (intptr_t)2);
+
+       /* Q_SETINFO */
+
+       fill_memory((char *)dqinfo, sizeof(*dqinfo));
+       /* In order to check flag printing correctness */
+       dqinfo->dqi_flags = 0xdeadabcd;
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_STR,
+               ARG_STR(QCMD(Q_SETINFO, PRJQUOTA)),
+               bogus_special, bogus_special_str, ARG_STR(NULL));
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_CB,
+               ARG_STR(QCMD(Q_SETINFO, USRQUOTA)),
+               ARG_STR("/dev/bogus/"), dqinfo, print_dqinfo, (intptr_t)0);
+
+
+       /* Q_GETFMT */
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_STR,
+               ARG_STR(QCMD(Q_GETFMT, PRJQUOTA)),
+               bogus_special, bogus_special_str, ARG_STR(NULL));
+       check_quota(CQF_ID_SKIP,
+               ARG_STR(QCMD(Q_GETFMT, USRQUOTA)),
+               unterminated, unterminated_str, fmt + 1);
+       check_quota(CQF_ID_SKIP,
+               ARG_STR(QCMD(Q_GETFMT, GRPQUOTA)),
+               ARG_STR("/dev/sda1"), fmt);
+
+
+       /* Q_SYNC */
+
+       check_quota(CQF_ID_SKIP | CQF_ADDR_SKIP,
+               ARG_STR(QCMD(Q_SYNC, USRQUOTA)),
+               bogus_special, bogus_special_str);
+       check_quota(CQF_ID_SKIP | CQF_ADDR_SKIP,
+               QCMD(Q_SYNC, 0xfff), "QCMD(Q_SYNC, 0xff /* ???QUOTA */)",
+               ARG_STR(NULL));
+
+       puts("+++ exited with 0 +++");
+
+       return 0;
+}
+
+#else
+
+SKIP_MAIN_UNDEFINED("__NR_quotactl && "
+       "(HAVE_LINUX_QUOTA_H || HAVE_SYS_QUOTA_H)");
+
+#endif
diff --git a/tests/quotactl.h b/tests/quotactl.h
new file mode 100644
index 0000000..a3ecb80
--- /dev/null
+++ b/tests/quotactl.h
@@ -0,0 +1,148 @@
+#ifndef STRACE_TESTS_QUOTACTL_H
+#define STRACE_TESTS_QUOTACTL_H
+
+/* Common definitions for Linux and XFS quota tests */
+
+# include <inttypes.h>
+# include <stdarg.h>
+# include <stdio.h>
+
+# ifdef HAVE_LINUX_QUOTA_H
+/* Broken in CentOS 5: has extern spinlock_t dq_data_lock; declaration */
+#  include <linux/quota.h>
+# else
+#  include <linux/types.h>
+/* Broken in some new glibc versions: have Q_GETNEXTQUOTA definition but no
+ * struct nextdqblk defined. Fixed in glibc-2.24-106-g4d72808. */
+#  include <sys/quota.h>
+# endif
+
+# ifndef QCMD_CMD
+#  define QCMD_CMD(_val) ((unsigned) (_val) >> SUBCMDSHIFT)
+# endif /* !QCMD_CMD */
+
+# ifndef QCMD_TYPE
+#  define QCMD_TYPE(_val) ((unsigned) (_val) & SUBCMDMASK)
+# endif /* !QCMD_TYPE */
+
+# ifndef PRJQUOTA
+#  define PRJQUOTA 2
+# endif
+
+# define ARG_STR(_arg) (_arg), #_arg
+
+typedef void (*print_cb)(long rc, void *addr, void *arg);
+
+enum check_quotactl_flag_bits {
+       CQF_ID_SKIP_BIT,
+       CQF_ID_STR_BIT,
+       CQF_ADDR_SKIP_BIT,
+       CQF_ADDR_STR_BIT,
+       CQF_ADDR_CB_BIT,
+};
+
+enum check_quotactl_flags {
+       CQF_NONE,
+       CQF_ID_SKIP   = 1 << CQF_ID_SKIP_BIT,
+       CQF_ID_STR    = 1 << CQF_ID_STR_BIT,
+       CQF_ADDR_SKIP = 1 << CQF_ADDR_SKIP_BIT,
+       CQF_ADDR_STR  = 1 << CQF_ADDR_STR_BIT,
+       CQF_ADDR_CB   = 1 << CQF_ADDR_CB_BIT,
+};
+
+
+static inline void
+fill_memory_ex(char *ptr, size_t size, unsigned char start)
+{
+       size_t i;
+
+       for (i = 0; i < size; i++) {
+               ptr[i] = start + i % 80;
+       }
+}
+
+static inline void
+fill_memory(char *ptr, size_t size)
+{
+       fill_memory_ex(ptr, size, 0x80);
+}
+
+static inline void
+check_quota(uint32_t flags, int cmd, const char *cmd_str,
+       const char *special, const char *special_str, ...)
+{
+       long rc;
+       const char *addr_str = NULL;
+       const char *id_str = NULL;
+       void *addr = NULL;
+       print_cb addr_cb = NULL;
+       void *addr_cb_arg = NULL;
+       uint32_t id = -1;
+
+       va_list ap;
+
+       va_start(ap, special_str);
+
+       if (!(flags & CQF_ID_SKIP)) {
+               id = va_arg(ap, uint32_t);
+
+               if (flags & CQF_ID_STR)
+                       id_str = va_arg(ap, const char *);
+       }
+
+       if (!(flags & CQF_ADDR_SKIP)) {
+               addr = va_arg(ap, void *);
+
+               if (flags & CQF_ADDR_CB) {
+                       addr_cb = va_arg(ap, print_cb);
+                       addr_cb_arg = va_arg(ap, void *);
+               } else if (flags & CQF_ADDR_STR) {
+                       addr_str = va_arg(ap, const char *);
+               }
+       }
+
+       va_end(ap);
+
+       rc = syscall(__NR_quotactl, cmd, special, id, addr);
+       printf("quotactl(%s, %s", cmd_str, special_str);
+
+       if (!(flags & CQF_ID_SKIP)) {
+               if (flags & CQF_ID_STR) {
+                       printf(", %s", id_str);
+               } else {
+                       if (id == (uint32_t)-1)
+                               printf(", -1");
+                       else
+                               printf(", %u", id);
+               }
+       }
+
+       if (!(flags & CQF_ADDR_SKIP)) {
+               if (flags & CQF_ADDR_CB) {
+                       printf(", ");
+                       addr_cb(rc, addr, addr_cb_arg);
+               } else if (flags & CQF_ADDR_STR) {
+                       printf(", %s", addr_str);
+               } else {
+                       printf(", %p", addr);
+               }
+       }
+
+       printf(") = %s\n", sprintrc(rc));
+}
+
+
+static const int bogus_cmd = 0xbadc0ded;
+static const char * const bogus_special =
+       (const char *) (unsigned long) 0xfffffca7ffffc0deULL;
+static const int bogus_id = 0xca7faced;
+static void * const bogus_addr =
+       (void *) (unsigned long) 0xffffda7affffdeadULL;
+
+/* It is invalid anyway due to the flash in the end */
+static const char *bogus_dev = "/dev/bogus/";
+static const char *bogus_dev_str = "\"/dev/bogus/\"";
+
+static const char unterminated_data[] = { '\1', '\2', '\3' };
+
+#endif /* !STRACE_TESTS_QUOTACTL_H */
diff --git a/tests/quotactl.test b/tests/quotactl.test
new file mode 100755
index 0000000..4ebf7c7
--- /dev/null
+++ b/tests/quotactl.test
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# Check quotactl syscall decoding.
+
+. "${srcdir=.}/init.sh"
+run_strace_match_diff -a1
-- 
1.7.10.4


------------------------------------------------------------------------------
_______________________________________________
Strace-devel mailing list
Strace-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/strace-devel

Reply via email to