All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests
@ 2021-03-16 10:36 Christian Brauner
  2021-03-16 10:36 ` [PATCH v9 1/4] generic/631: add test for detached mount propagation Christian Brauner
                   ` (4 more replies)
  0 siblings, 5 replies; 22+ messages in thread
From: Christian Brauner @ 2021-03-16 10:36 UTC (permalink / raw)
  To: Christoph Hellwig, Darrick J . Wong, fstests
  Cc: David Howells, Christian Brauner

Hey everyone,

/* v9 */
Rebased onto current master.
Hopefully we can merge this soon so we can have regular testing asap and
the rebasing is getting a bit tedious too. ;)

ubuntu@f1-vm:~/src/git/xfstests$ sudo ./check generic/631
FSTYP         -- xfs (non-debug)
PLATFORM      -- Linux/x86_64 f1-vm 5.12.0-rc3-inode-helpers #343 SMP Mon Mar 15 12:57:02 UTC 2021
MKFS_OPTIONS  -- -f -bsize=4096 /dev/loop1
MOUNT_OPTIONS -- /dev/loop1 /mnt/scratch

generic/631 9s ...  11s
Ran: generic/631
Passed all 1 tests

ubuntu@f1-vm:~/src/git/xfstests$ sudo ./check generic/632
FSTYP         -- xfs (non-debug)
PLATFORM      -- Linux/x86_64 f1-vm 5.12.0-rc3-inode-helpers #343 SMP Mon Mar 15 12:57:02 UTC 2021
MKFS_OPTIONS  -- -f -bsize=4096 /dev/loop1
MOUNT_OPTIONS -- /dev/loop1 /mnt/scratch

generic/632      15s
Ran: generic/632
Passed all 1 tests

ubuntu@f1-vm:~/src/git/xfstests$ sudo ./check xfs/529
FSTYP         -- xfs (non-debug)
PLATFORM      -- Linux/x86_64 f1-vm 5.12.0-rc3-inode-helpers #343 SMP Mon Mar 15 12:57:02 UTC 2021
MKFS_OPTIONS  -- -f -bsize=4096 /dev/loop1
MOUNT_OPTIONS -- /dev/loop1 /mnt/scratch

xfs/529 25s ...  43s
Ran: xfs/529
Passed all 1 tests

ubuntu@f1-vm:~/src/git/xfstests$ sudo ./check xfs/530
FSTYP         -- xfs (non-debug)
PLATFORM      -- Linux/x86_64 f1-vm 5.12.0-rc3-inode-helpers #343 SMP Mon Mar 15 12:57:02 UTC 2021
MKFS_OPTIONS  -- -f -bsize=4096 /dev/loop1
MOUNT_OPTIONS -- /dev/loop1 /mnt/scratch

xfs/530  24s
Ran: xfs/530
Passed all 1 tests

/* v8 */
This adds the testsuite for idmapped mounts. It has been expanded quite
a bit too since the last version:
- Add a testcase to verify that detached mounts in the new mount api
  work correctly.
- Add a full testsuite for idmapped mounts.
- Add two quota tests verifying xfs behavior.

Sorry that it took so long to resend this testsuite but I had to chase
and fix a bug in the new mount API (completely independent of idmapped
mounts) before I could add quota tests because they would otherwise run
danger to render the host mount namespace unuseable due to that bug
(cf.  https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d3110f256d126b44d34c1f662310cd295877c447)
All tests here pass for me on a kernel with idmapped mounts. The xfs
specific quota tests should go on top of the fixes that Darrick is
currently carrying for 5.12 and which he sent out a little while ago.

Thanks!
Christian

Christian Brauner (4):
  generic/631: add test for detached mount propagation
  generic/632: add fstests for idmapped mounts
  xfs/529: quotas and idmapped mounts
  xfs/530: quotas on idmapped mounts

 .gitignore                            |    3 +
 common/rc                             |   25 +
 configure.ac                          |    2 +
 include/builddefs.in                  |    1 +
 m4/Makefile                           |    1 +
 m4/package_libcap.m4                  |    4 +
 src/Makefile                          |    8 +-
 src/detached_mounts_propagation.c     |  189 +
 src/feature.c                         |   40 +-
 src/idmapped-mounts/Makefile          |   41 +
 src/idmapped-mounts/idmapped-mounts.c | 8627 +++++++++++++++++++++++++
 src/idmapped-mounts/missing.h         |  151 +
 src/idmapped-mounts/mount-idmapped.c  |  428 ++
 src/idmapped-mounts/utils.c           |  134 +
 src/idmapped-mounts/utils.h           |   30 +
 tests/generic/631                     |   41 +
 tests/generic/631.out                 |    2 +
 tests/generic/632                     |   42 +
 tests/generic/632.out                 |    2 +
 tests/generic/group                   |    2 +
 tests/xfs/529                         |  373 ++
 tests/xfs/529.out                     |  657 ++
 tests/xfs/530                         |  274 +
 tests/xfs/530.out                     |  129 +
 tests/xfs/group                       |    2 +
 25 files changed, 11203 insertions(+), 5 deletions(-)
 create mode 100644 m4/package_libcap.m4
 create mode 100644 src/detached_mounts_propagation.c
 create mode 100644 src/idmapped-mounts/Makefile
 create mode 100644 src/idmapped-mounts/idmapped-mounts.c
 create mode 100644 src/idmapped-mounts/missing.h
 create mode 100644 src/idmapped-mounts/mount-idmapped.c
 create mode 100644 src/idmapped-mounts/utils.c
 create mode 100644 src/idmapped-mounts/utils.h
 create mode 100644 tests/generic/631
 create mode 100644 tests/generic/631.out
 create mode 100644 tests/generic/632
 create mode 100644 tests/generic/632.out
 create mode 100644 tests/xfs/529
 create mode 100644 tests/xfs/529.out
 create mode 100644 tests/xfs/530
 create mode 100644 tests/xfs/530.out


base-commit: f6ddaf130d5b0817278afe441fdde52f464f321b
-- 
2.27.0


^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v9 1/4] generic/631: add test for detached mount propagation
  2021-03-16 10:36 [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests Christian Brauner
@ 2021-03-16 10:36 ` Christian Brauner
  2021-03-18  6:23   ` Christoph Hellwig
  2021-03-21 14:28   ` Eryu Guan
  2021-03-16 10:36 ` [PATCH v9 3/4] xfs/529: quotas and idmapped mounts Christian Brauner
                   ` (3 subsequent siblings)
  4 siblings, 2 replies; 22+ messages in thread
From: Christian Brauner @ 2021-03-16 10:36 UTC (permalink / raw)
  To: Christoph Hellwig, Darrick J . Wong, fstests
  Cc: David Howells, Christian Brauner

Regression test to verify that creating a series of detached mounts,
attaching them to the filesystem, and unmounting them does not trigger an
integer overflow in ns->mounts causing the kernel to block any new mounts in
count_mounts() and returning ENOSPC because it falsely assumes that the
maximum number of mounts in the mount namespace has been reached, i.e. it
thinks it can't fit the new mounts into the mount namespace anymore.

The test is written in a way that it will leave the host's mount
namespace intact so we are sure to never make the host's mount namespace
unuseable!

Link: https://git.kernel.org/torvalds/c/ee2e3f50629f17b0752b55b2566c15ce8dafb557
Cc: Christoph Hellwig <hch@lst.de>
Cc: David Howells <dhowells@redhat.com>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
/* v1 */
patch not present

/* v2 */
patch not present

/* v3 */
patch not present

/* v4 */
patch not present

/* v5 */
patch not present

/* v6 */
patch not present

/* v7 */
patch not present

/* v8 */
patch introduced

/* v9 */
- Christian Brauner <christian.brauner@ubuntu.com>:
  - Rebased on current master.
---
 .gitignore                        |   1 +
 src/Makefile                      |   3 +-
 src/detached_mounts_propagation.c | 252 ++++++++++++++++++++++++++++++
 tests/generic/631                 |  41 +++++
 tests/generic/631.out             |   2 +
 tests/generic/group               |   1 +
 6 files changed, 299 insertions(+), 1 deletion(-)
 create mode 100644 src/detached_mounts_propagation.c
 create mode 100644 tests/generic/631
 create mode 100644 tests/generic/631.out

diff --git a/.gitignore b/.gitignore
index f3bc0a4f..72bd40a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,6 +62,7 @@
 /src/cloner
 /src/dbtest
 /src/deduperace
+/src/detached_mounts_propagation
 /src/devzero
 /src/dio-interleaved
 /src/dio-invalidate-cache
diff --git a/src/Makefile b/src/Makefile
index 3d729a34..99019e6e 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -29,7 +29,8 @@ LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
 	attr-list-by-handle-cursor-test listxattr dio-interleaved t_dir_type \
 	dio-invalidate-cache stat_test t_encrypted_d_revalidate \
 	attr_replace_test swapon mkswap t_attr_corruption t_open_tmpfiles \
-	fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail
+	fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail \
+	detached_mounts_propagation
 
 SUBDIRS = log-writes perf
 
diff --git a/src/detached_mounts_propagation.c b/src/detached_mounts_propagation.c
new file mode 100644
index 00000000..4a588e46
--- /dev/null
+++ b/src/detached_mounts_propagation.c
@@ -0,0 +1,252 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/*
+ * Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
+ * All Rights Reserved.
+ *
+ * Regression test to verify that creating a series of detached mounts,
+ * attaching them to the filesystem, and unmounting them does not trigger an
+ * integer overflow in ns->mounts causing the kernel to block any new mounts in
+ * count_mounts() and returning ENOSPC because it falsely assumes that the
+ * maximum number of mounts in the mount namespace has been reached, i.e. it
+ * thinks it can't fit the new mounts into the mount namespace anymore.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* open_tree() */
+#ifndef OPEN_TREE_CLONE
+#define OPEN_TREE_CLONE 1
+#endif
+
+#ifndef OPEN_TREE_CLOEXEC
+#define OPEN_TREE_CLOEXEC O_CLOEXEC
+#endif
+
+#ifndef __NR_open_tree
+	#if defined __alpha__
+		#define __NR_open_tree 538
+	#elif defined _MIPS_SIM
+		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
+			#define __NR_open_tree 4428
+		#endif
+		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
+			#define __NR_open_tree 6428
+		#endif
+		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
+			#define __NR_open_tree 5428
+		#endif
+	#elif defined __ia64__
+		#define __NR_open_tree (428 + 1024)
+	#else
+		#define __NR_open_tree 428
+	#endif
+#endif
+
+/* move_mount() */
+#ifndef MOVE_MOUNT_F_EMPTY_PATH
+#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
+#endif
+
+#ifndef __NR_move_mount
+	#if defined __alpha__
+		#define __NR_move_mount 539
+	#elif defined _MIPS_SIM
+		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
+			#define __NR_move_mount 4429
+		#endif
+		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
+			#define __NR_move_mount 6429
+		#endif
+		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
+			#define __NR_move_mount 5429
+		#endif
+	#elif defined __ia64__
+		#define __NR_move_mount (428 + 1024)
+	#else
+		#define __NR_move_mount 429
+	#endif
+#endif
+
+static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
+{
+	return syscall(__NR_open_tree, dfd, filename, flags);
+}
+
+static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
+				 const char *to_pathname, unsigned int flags)
+{
+	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
+}
+
+static bool is_shared_mountpoint(const char *path)
+{
+	bool shared = false;
+	FILE *f = NULL;
+	char *line = NULL;
+	int i;
+	size_t len = 0;
+
+	f = fopen("/proc/self/mountinfo", "re");
+	if (!f)
+		return 0;
+
+	while (getline(&line, &len, f) > 0) {
+		char *slider1, *slider2;
+
+		for (slider1 = line, i = 0; slider1 && i < 4; i++)
+			slider1 = strchr(slider1 + 1, ' ');
+
+		if (!slider1)
+			continue;
+
+		slider2 = strchr(slider1 + 1, ' ');
+		if (!slider2)
+			continue;
+
+		*slider2 = '\0';
+		if (strcmp(slider1 + 1, path) == 0) {
+			/* This is the path. Is it shared? */
+			slider1 = strchr(slider2 + 1, ' ');
+			if (slider1 && strstr(slider1, "shared:")) {
+				shared = true;
+				break;
+			}
+		}
+	}
+	fclose(f);
+	free(line);
+
+	return shared;
+}
+
+static void usage(void)
+{
+	const char *text = "mount-new [--recursive] <base-dir>\n";
+	fprintf(stderr, "%s", text);
+	_exit(EXIT_SUCCESS);
+}
+
+#define exit_usage(format, ...)                              \
+	({                                                   \
+		fprintf(stderr, format "\n", ##__VA_ARGS__); \
+		usage();                                     \
+	})
+
+#define exit_log(format, ...)                                \
+	({                                                   \
+		fprintf(stderr, format "\n", ##__VA_ARGS__); \
+		exit(EXIT_FAILURE);                          \
+	})
+
+static const struct option longopts[] = {
+	{"help",	no_argument,		0,	'a'},
+	{ NULL,		no_argument,		0,	 0 },
+};
+
+int main(int argc, char *argv[])
+{
+	int exit_code = EXIT_SUCCESS, index = 0;
+	int dfd, fd_tree, new_argc, ret;
+	char *base_dir;
+	char *const *new_argv;
+	char target[PATH_MAX];
+
+	while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
+		switch (ret) {
+		case 'a':
+			/* fallthrough */
+		default:
+			usage();
+		}
+	}
+
+	new_argv = &argv[optind];
+	new_argc = argc - optind;
+	if (new_argc < 1)
+		exit_usage("Missing base directory\n");
+	base_dir = new_argv[0];
+
+	if (*base_dir != '/')
+		exit_log("Please specify an absolute path");
+
+	/* Ensure that target is a shared mountpoint. */
+	if (!is_shared_mountpoint(base_dir))
+		exit_log("Please ensure that \"%s\" is a shared mountpoint", base_dir);
+
+	ret = unshare(CLONE_NEWNS);
+	if (ret < 0)
+		exit_log("%m - Failed to create new mount namespace");
+
+	ret = mount(NULL, base_dir, NULL, MS_REC | MS_SHARED, NULL);
+	if (ret < 0)
+		exit_log("%m - Failed to make base_dir shared mountpoint");
+
+	dfd = open(base_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	if (dfd < 0)
+		exit_log("%m - Failed to open base directory \"%s\"", base_dir);
+
+	ret = mkdirat(dfd, "detached-move-mount", 0755);
+	if (ret < 0)
+		exit_log("%m - Failed to create required temporary directories");
+
+	ret = snprintf(target, sizeof(target), "%s/detached-move-mount", base_dir);
+	if (ret < 0 || (size_t)ret >= sizeof(target))
+		exit_log("%m - Failed to assemble target path");
+
+	/*
+	 * Having a mount table with 10000 mounts is already quite excessive
+	 * and shoult account even for weird test systems.
+	 */
+	for (size_t i = 0; i < 10000; i++) {
+		fd_tree = sys_open_tree(dfd, "detached-move-mount",
+					OPEN_TREE_CLONE |
+					OPEN_TREE_CLOEXEC |
+					AT_EMPTY_PATH);
+		if (fd_tree < 0) {
+			if (errno == ENOSYS) /* New mount API not (fully) supported. */
+				break;
+
+			fprintf(stderr, "%m - Failed to open %d(detached-move-mount)", dfd);
+			exit_code = EXIT_FAILURE;
+			break;
+		}
+
+		ret = sys_move_mount(fd_tree, "", dfd, "detached-move-mount", MOVE_MOUNT_F_EMPTY_PATH);
+		if (ret < 0) {
+			if (errno == ENOSPC)
+				fprintf(stderr, "%m - Buggy mount counting");
+			else if (errno == ENOSYS) /* New mount API not (fully) supported. */
+				break;
+			else
+				fprintf(stderr, "%m - Failed to attach mount to %d(detached-move-mount)", dfd);
+			exit_code = EXIT_FAILURE;
+			break;
+		}
+		close(fd_tree);
+
+		ret = umount2(target, MNT_DETACH);
+		if (ret < 0) {
+			fprintf(stderr, "%m - Failed to unmount %s", target);
+			exit_code = EXIT_FAILURE;
+			break;
+		}
+	}
+
+	(void)unlinkat(dfd, "detached-move-mount", AT_REMOVEDIR);
+	close(dfd);
+
+	exit(exit_code);
+}
diff --git a/tests/generic/631 b/tests/generic/631
new file mode 100644
index 00000000..e227c448
--- /dev/null
+++ b/tests/generic/631
@@ -0,0 +1,41 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
+# All Rights Reserved.
+#
+# FS QA Test No. 631
+#
+# Regression test to verify that creating a series of detached mounts,
+# attaching them to the filesystem, and unmounting them does not trigger an
+# integer overflow in ns->mounts causing the kernel to block any new mounts in
+# count_mounts() and returning ENOSPC because it falsely assumes that the
+# maximum number of mounts in the mount namespace has been reached, i.e. it
+# thinks it can't fit the new mounts into the mount namespace anymore.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+	cd /
+	rm -f $tmp.*
+}
+
+. ./common/rc
+
+rm -f $seqres.full
+
+_supported_fs generic
+
+$here/src/detached_mounts_propagation $TEST_DIR >> $seqres.full
+
+echo silence is golden
+status=$?
+exit
diff --git a/tests/generic/631.out b/tests/generic/631.out
new file mode 100644
index 00000000..ce88f447
--- /dev/null
+++ b/tests/generic/631.out
@@ -0,0 +1,2 @@
+QA output created by 631
+silence is golden
diff --git a/tests/generic/group b/tests/generic/group
index 7c7531d1..151f8af2 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -633,3 +633,4 @@
 628 auto quick rw clone
 629 auto quick rw copy_range
 630 auto quick rw dedupe clone
+631 auto quick mount
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v9 3/4] xfs/529: quotas and idmapped mounts
  2021-03-16 10:36 [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests Christian Brauner
  2021-03-16 10:36 ` [PATCH v9 1/4] generic/631: add test for detached mount propagation Christian Brauner
@ 2021-03-16 10:36 ` Christian Brauner
  2021-03-18  6:24   ` Christoph Hellwig
  2021-03-21 14:42   ` Eryu Guan
  2021-03-16 10:36 ` [PATCH v9 4/4] xfs/530: quotas on " Christian Brauner
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 22+ messages in thread
From: Christian Brauner @ 2021-03-16 10:36 UTC (permalink / raw)
  To: Christoph Hellwig, Darrick J . Wong, fstests
  Cc: David Howells, Christian Brauner

Test that xfs quota behave correctly on idmapped mounts.
Mount a scratch device with user and group quota support enabled. Create
directories "unmapped" and "idmapped". Create files in the unampped
mount and verify quota behavior. Create files through the idmapped mount
and verify identical behavior.

Cc: Christoph Hellwig <hch@lst.de>
Cc: Darrick J. Wong <djwong@kernel.org>
Cc: fstests@vger.kernel.org
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
/* v1 */
patch not present

/* v2 */
patch not present

/* v3 */
patch not present

/* v4 */
patch not present

/* v5 */
patch not present

/* v6 */
patch not present

/* v7 */
patch not present

/* v8 */
patch introduced

/* v9 */
- Christian Brauner <christian.brauner@ubuntu.com>:
  - Rebased on current master.
---
 .gitignore                           |   1 +
 src/idmapped-mounts/Makefile         |  14 +-
 src/idmapped-mounts/mount-idmapped.c | 428 +++++++++++++++++
 src/idmapped-mounts/utils.c          |   2 +-
 src/idmapped-mounts/utils.h          |   1 +
 tests/xfs/529                        | 373 +++++++++++++++
 tests/xfs/529.out                    | 657 +++++++++++++++++++++++++++
 tests/xfs/group                      |   1 +
 8 files changed, 1472 insertions(+), 5 deletions(-)
 create mode 100644 src/idmapped-mounts/mount-idmapped.c
 create mode 100644 tests/xfs/529
 create mode 100644 tests/xfs/529.out

diff --git a/.gitignore b/.gitignore
index 3229bb26..4cc9c807 100644
--- a/.gitignore
+++ b/.gitignore
@@ -179,6 +179,7 @@
 /src/aio-dio-regress/aiocp
 /src/aio-dio-regress/aiodio_sparse2
 /src/idmapped-mounts/idmapped-mounts
+/src/idmapped-mounts/mount-idmapped
 /src/log-writes/replay-log
 /src/perf/*.pyc
 
diff --git a/src/idmapped-mounts/Makefile b/src/idmapped-mounts/Makefile
index 6a934146..40bf9164 100644
--- a/src/idmapped-mounts/Makefile
+++ b/src/idmapped-mounts/Makefile
@@ -3,9 +3,10 @@
 TOPDIR = ../..
 include $(TOPDIR)/include/builddefs
 
-TARGETS = idmapped-mounts
+TARGETS = idmapped-mounts mount-idmapped
+CFILES_IDMAPPED_MOUNTS = idmapped-mounts.c utils.c
+CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c
 
-CFILES = idmapped-mounts.c utils.c
 HFILES = missing.h utils.h
 LLDLIBS += -pthread
 LDIRT = $(TARGETS)
@@ -24,12 +25,17 @@ depend: .dep
 
 include $(BUILDRULES)
 
-$(TARGETS): $(CFILES)
+idmapped-mounts:
 	@echo "    [CC]    $@"
-	$(Q)$(LTLINK) $(CFILES) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
+	$(Q)$(LTLINK) $(CFILES_IDMAPPED_MOUNTS) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
+
+mount-idmapped:
+	@echo "    [CC]    $@"
+	$(Q)$(LTLINK) $(CFILES_MOUNT_IDMAPPED) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
 
 install:
 	$(INSTALL) -m 755 -d $(PKG_LIB_DIR)/src/idmapped-mounts
 	$(INSTALL) -m 755 $(TARGETS) $(PKG_LIB_DIR)/src/idmapped-mounts
+	$(INSTALL) -m 755 $(TARGETS) $(PKG_LIB_DIR)/src/mount-idmapped
 
 -include .dep
diff --git a/src/idmapped-mounts/mount-idmapped.c b/src/idmapped-mounts/mount-idmapped.c
new file mode 100644
index 00000000..f127cdc7
--- /dev/null
+++ b/src/idmapped-mounts/mount-idmapped.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "../global.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <limits.h>
+#include <linux/bpf.h>
+#include <linux/sched.h>
+#include <linux/seccomp.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "missing.h"
+#include "utils.h"
+
+/* A few helpful macros. */
+#define STRLITERALLEN(x) (sizeof(""x"") - 1)
+
+#define INTTYPE_TO_STRLEN(type)             \
+	(2 + (sizeof(type) <= 1             \
+		  ? 3                       \
+		  : sizeof(type) <= 2       \
+			? 5                 \
+			: sizeof(type) <= 4 \
+			      ? 10          \
+			      : sizeof(type) <= 8 ? 20 : sizeof(int[-2 * (sizeof(type) > 8)])))
+
+#define syserror(format, ...)                           \
+	({                                              \
+		fprintf(stderr, format, ##__VA_ARGS__); \
+		(-errno);                               \
+	})
+
+#define syserror_set(__ret__, format, ...)                    \
+	({                                                    \
+		typeof(__ret__) __internal_ret__ = (__ret__); \
+		errno = labs(__ret__);                        \
+		fprintf(stderr, format, ##__VA_ARGS__);       \
+		__internal_ret__;                             \
+	})
+
+struct list {
+	void *elem;
+	struct list *next;
+	struct list *prev;
+};
+
+#define list_for_each(__iterator, __list) \
+	for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
+
+static inline void list_init(struct list *list)
+{
+	list->elem = NULL;
+	list->next = list->prev = list;
+}
+
+static inline int list_empty(const struct list *list)
+{
+	return list == list->next;
+}
+
+static inline void __list_add(struct list *new, struct list *prev, struct list *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+static inline void list_add_tail(struct list *head, struct list *list)
+{
+	__list_add(list, head->prev, head);
+}
+
+typedef enum idmap_type_t {
+	ID_TYPE_UID,
+	ID_TYPE_GID
+} idmap_type_t;
+
+struct id_map {
+	idmap_type_t map_type;
+	__u32 nsid;
+	__u32 hostid;
+	__u32 range;
+};
+
+static struct list active_map;
+
+static int add_map_entry(__u32 id_host,
+			 __u32 id_ns,
+			 __u32 range,
+			 idmap_type_t map_type)
+{
+	struct list *new_list = NULL;
+	struct id_map *newmap = NULL;
+
+	newmap = malloc(sizeof(*newmap));
+	if (!newmap)
+		return -ENOMEM;
+
+	new_list = malloc(sizeof(struct list));
+	if (!new_list) {
+		free(newmap);
+		return -ENOMEM;
+	}
+
+	*newmap = (struct id_map){
+		.hostid		= id_host,
+		.nsid		= id_ns,
+		.range		= range,
+		.map_type	= map_type,
+	};
+
+	new_list->elem = newmap;
+	list_add_tail(&active_map, new_list);
+	return 0;
+}
+
+static int parse_map(char *map)
+{
+	char types[2] = {'u', 'g'};
+	int ret;
+	__u32 id_host, id_ns, range;
+	char which;
+
+	if (!map)
+		return -1;
+
+	ret = sscanf(map, "%c:%u:%u:%u", &which, &id_ns, &id_host, &range);
+	if (ret != 4)
+		return -1;
+
+	if (which != 'b' && which != 'u' && which != 'g')
+		return -1;
+
+	for (int i = 0; i < 2; i++) {
+		idmap_type_t map_type;
+
+		if (which != types[i] && which != 'b')
+			continue;
+
+		if (types[i] == 'u')
+			map_type = ID_TYPE_UID;
+		else
+			map_type = ID_TYPE_GID;
+
+		ret = add_map_entry(id_host, id_ns, range, map_type);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int write_id_mapping(idmap_type_t map_type, pid_t pid, const char *buf, size_t buf_size)
+{
+	int fd = -EBADF, setgroups_fd = -EBADF;
+	int fret = -1;
+	int ret;
+	char path[STRLITERALLEN("/proc") + INTTYPE_TO_STRLEN(pid_t) +
+		  STRLITERALLEN("/setgroups") + 1];
+
+	if (geteuid() != 0 && map_type == ID_TYPE_GID) {
+		ret = snprintf(path, sizeof(path), "/proc/%d/setgroups", pid);
+		if (ret < 0 || ret >= sizeof(path))
+			goto out;
+
+		setgroups_fd = open(path, O_WRONLY | O_CLOEXEC);
+		if (setgroups_fd < 0 && errno != ENOENT) {
+			syserror("Failed to open \"%s\"", path);
+			goto out;
+		}
+
+		if (setgroups_fd >= 0) {
+			ret = write_nointr(setgroups_fd, "deny\n", STRLITERALLEN("deny\n"));
+			if (ret != STRLITERALLEN("deny\n")) {
+				syserror("Failed to write \"deny\" to \"/proc/%d/setgroups\"", pid);
+				goto out;
+			}
+		}
+	}
+
+	ret = snprintf(path, sizeof(path), "/proc/%d/%cid_map", pid, map_type == ID_TYPE_UID ? 'u' : 'g');
+	if (ret < 0 || ret >= sizeof(path))
+		goto out;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd < 0) {
+		syserror("Failed to open \"%s\"", path);
+		goto out;
+	}
+
+	ret = write_nointr(fd, buf, buf_size);
+	if (ret != buf_size) {
+		syserror("Failed to write %cid mapping to \"%s\"",
+			 map_type == ID_TYPE_UID ? 'u' : 'g', path);
+		goto out;
+	}
+
+	fret = 0;
+out:
+	if (fd >= 0)
+		close(fd);
+	if (setgroups_fd >= 0)
+		close(setgroups_fd);
+
+	return fret;
+}
+
+static int map_ids_from_idmap(struct list *idmap, pid_t pid)
+{
+	int fill, left;
+	char mapbuf[4096] = {};
+	bool had_entry = false;
+
+	for (idmap_type_t map_type = ID_TYPE_UID, u_or_g = 'u';
+	     map_type <= ID_TYPE_GID; map_type++, u_or_g = 'g') {
+		char *pos = mapbuf;
+		int ret;
+		struct list *iterator;
+
+
+		list_for_each(iterator, idmap) {
+			struct id_map *map = iterator->elem;
+			if (map->map_type != map_type)
+				continue;
+
+			had_entry = true;
+
+			left = 4096 - (pos - mapbuf);
+			fill = snprintf(pos, left, "%u %u %u\n", map->nsid, map->hostid, map->range);
+			/*
+			 * The kernel only takes <= 4k for writes to
+			 * /proc/<pid>/{g,u}id_map
+			 */
+			if (fill <= 0 || fill >= left)
+				return syserror_set(-E2BIG, "Too many %cid mappings defined", u_or_g);
+
+			pos += fill;
+		}
+		if (!had_entry)
+			continue;
+
+		ret = write_id_mapping(map_type, pid, mapbuf, pos - mapbuf);
+		if (ret < 0)
+			return syserror("Failed to write mapping: %s", mapbuf);
+
+		memset(mapbuf, 0, sizeof(mapbuf));
+	}
+
+	return 0;
+}
+
+static int get_userns_fd_from_idmap(struct list *idmap)
+{
+	int ret;
+	pid_t pid;
+	char path_ns[STRLITERALLEN("/proc") + INTTYPE_TO_STRLEN(pid_t) +
+		  STRLITERALLEN("/ns/user") + 1];
+
+	pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER | CLONE_NEWNS);
+	if (pid < 0)
+		return -errno;
+
+	ret = map_ids_from_idmap(idmap, pid);
+	if (ret < 0)
+		return ret;
+
+	ret = snprintf(path_ns, sizeof(path_ns), "/proc/%d/ns/user", pid);
+	if (ret < 0 || (size_t)ret >= sizeof(path_ns))
+		ret = -EIO;
+	else
+		ret = open(path_ns, O_RDONLY | O_CLOEXEC | O_NOCTTY);
+
+	(void)kill(pid, SIGKILL);
+	(void)wait_for_pid(pid);
+	return ret;
+}
+
+static inline bool strnequal(const char *str, const char *eq, size_t len)
+{
+	return strncmp(str, eq, len) == 0;
+}
+
+static void usage(void)
+{
+	const char *text = "\
+mount-idmapped --map-mount=<idmap> <source> <target>\n\
+\n\
+Create an idmapped mount of <source> at <target>\n\
+Options:\n\
+  --map-mount=<idmap>\n\
+	Specify an idmap for the <target> mount in the format\n\
+	<idmap-type>:<id-from>:<id-to>:<id-range>\n\
+	The <idmap-type> can be:\n\
+	\"b\" or \"both\"	-> map both uids and gids\n\
+	\"u\" or \"uid\"	-> map uids\n\
+	\"g\" or \"gid\"	-> map gids\n\
+	For example, specifying:\n\
+	both:1000:1001:1	-> map uid and gid 1000 to uid and gid 1001 in <target> and no other ids\n\
+	uid:20000:100000:1000	-> map uid 20000 to uid 100000, uid 20001 to uid 100001 [...] in <target>\n\
+	Currently up to 340 separate idmappings may be specified.\n\n\
+  --map-mount=/proc/<pid>/ns/user\n\
+	Specify a path to a user namespace whose idmap is to be used.\n\n\
+  --recursive\n\
+	Copy the whole mount tree from <source> and apply the idmap to everyone at <target>.\n\n\
+Examples:\n\
+  - Create an idmapped mount of /source on /target with both ('b') uids and gids mapped:\n\
+	mount-idmapped --map-mount b:0:10000:10000 /source /target\n\n\
+  - Create an idmapped mount of /source on /target with uids ('u') and gids ('g') mapped separately:\n\
+	mount-idmapped --map-mount u:0:10000:10000 g:0:20000:20000 /source /target\n\n\
+";
+	fprintf(stderr, "%s", text);
+	_exit(EXIT_SUCCESS);
+}
+
+#define exit_usage(format, ...)                         \
+	({                                              \
+		fprintf(stderr, format, ##__VA_ARGS__); \
+		usage();                                \
+	})
+
+#define exit_log(format, ...)                           \
+	({                                              \
+		fprintf(stderr, format, ##__VA_ARGS__); \
+		exit(EXIT_FAILURE);                     \
+	})
+
+static const struct option longopts[] = {
+	{"map-mount",	required_argument,	0,	'a'},
+	{"help",	no_argument,		0,	'c'},
+	{"recursive",	no_argument,		0,	'd'},
+	{ NULL,		0,			0,	0  },
+};
+
+int main(int argc, char *argv[])
+{
+	int fd_userns = -EBADF;
+	int index = 0;
+	const char *source = NULL, *target = NULL;
+	bool recursive = false;
+	int fd_tree, new_argc, ret;
+	char *const *new_argv;
+
+	list_init(&active_map);
+	while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
+		switch (ret) {
+		case 'a':
+			if (strnequal(optarg, "/proc", STRLITERALLEN("/proc/"))) {
+				fd_userns = open(optarg, O_RDONLY | O_CLOEXEC);
+				if (fd_userns < 0)
+					exit_log("%m - Failed top open user namespace path %s\n", optarg);
+				break;
+			}
+
+			ret = parse_map(optarg);
+			if (ret < 0)
+				exit_log("Failed to parse idmaps for mount\n");
+			break;
+		case 'd':
+			recursive = true;
+			break;
+		case 'c':
+			/* fallthrough */
+		default:
+			usage();
+		}
+	}
+
+	new_argv = &argv[optind];
+	new_argc = argc - optind;
+	if (new_argc < 2)
+		exit_usage("Missing source or target mountpoint\n\n");
+	source = new_argv[0];
+	target = new_argv[1];
+
+	fd_tree = sys_open_tree(-EBADF, source,
+			        OPEN_TREE_CLONE |
+			        OPEN_TREE_CLOEXEC |
+			        AT_EMPTY_PATH |
+			        (recursive ? AT_RECURSIVE : 0));
+	if (fd_tree < 0) {
+		exit_log("%m - Failed to open %s\n", source);
+		exit(EXIT_FAILURE);
+	}
+
+	if (!list_empty(&active_map)) {
+		struct mount_attr attr = {
+			.attr_set = MOUNT_ATTR_IDMAP,
+		};
+
+		attr.userns_fd = get_userns_fd_from_idmap(&active_map);
+		if (attr.userns_fd < 0)
+			exit_log("%m - Failed to create user namespace\n");
+
+		ret = sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH | AT_RECURSIVE,
+					&attr, sizeof(attr));
+		if (ret < 0)
+			exit_log("%m - Failed to change mount attributes\n");
+		close(attr.userns_fd);
+	}
+
+	ret = sys_move_mount(fd_tree, "", -EBADF, target,
+			     MOVE_MOUNT_F_EMPTY_PATH);
+	if (ret < 0)
+		exit_log("%m - Failed to attach mount to %s\n", target);
+	close(fd_tree);
+
+	exit(EXIT_SUCCESS);
+}
diff --git a/src/idmapped-mounts/utils.c b/src/idmapped-mounts/utils.c
index b27ba445..977443f1 100644
--- a/src/idmapped-mounts/utils.c
+++ b/src/idmapped-mounts/utils.c
@@ -88,7 +88,7 @@ pid_t do_clone(int (*fn)(void *), void *arg, int flags)
 #endif
 }
 
-static int get_userns_fd_cb(void *data)
+int get_userns_fd_cb(void *data)
 {
 	return kill(getpid(), SIGSTOP);
 }
diff --git a/src/idmapped-mounts/utils.h b/src/idmapped-mounts/utils.h
index 93425731..efbf3bc3 100644
--- a/src/idmapped-mounts/utils.h
+++ b/src/idmapped-mounts/utils.h
@@ -20,6 +20,7 @@
 #include "missing.h"
 
 extern pid_t do_clone(int (*fn)(void *), void *arg, int flags);
+extern int get_userns_fd_cb(void *data);
 extern int get_userns_fd(unsigned long nsid, unsigned long hostid,
 			 unsigned long range);
 extern ssize_t read_nointr(int fd, void *buf, size_t count);
diff --git a/tests/xfs/529 b/tests/xfs/529
new file mode 100644
index 00000000..67251818
--- /dev/null
+++ b/tests/xfs/529
@@ -0,0 +1,373 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
+# All Rights Reserved.
+#
+# FS QA Test No. 529
+#
+# Exercise basic xfs_quota functionality (user/group/project quota)
+# Use of "sync" mount option here is an attempt to get deterministic
+# allocator behaviour.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+	cd /
+	rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+. ./common/quota
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+_supported_fs xfs
+_require_idmapped_mounts
+_require_scratch
+_require_xfs_quota
+_require_user fsgqa
+_require_user fsgqa2
+_require_group fsgqa
+_require_group fsgqa2
+
+_scratch_mkfs_xfs >>$seqres.full 2>&1
+
+uqid=`id -u fsgqa`
+gqid=`id -g fsgqa`
+
+uqid2=`id -u fsgqa2`
+gqid2=`id -g fsgqa2`
+
+pqid=10
+cat >$tmp.projects <<EOF
+$pqid:$SCRATCH_MNT
+EOF
+
+cat >$tmp.projid <<EOF
+root:0
+fsgqa:$pqid
+EOF
+
+create_files_unmapped()
+{
+	local bs=$1
+	local inum=$2
+
+	echo "Using type=$type id=$id" >> $seqres.full
+
+	for ((i=0; i<$((inum-1)); i++)); do
+		_file_as_id $SCRATCH_MNT/unmapped/inode$i $id $type 1024 0
+	done
+
+	_file_as_id $SCRATCH_MNT/unmapped/block $id $type $bs 1
+}
+
+create_files_idmapped()
+{
+	local bs=$1
+	local inum=$2
+
+	echo "Using type=$type id=$id2" >> $seqres.full
+
+	for ((i=0; i<$((inum-1)); i++)); do
+		_file_as_id $SCRATCH_MNT/idmapped/inode$i $id2 $type 1024 0
+	done
+
+	_file_as_id $SCRATCH_MNT/idmapped/block $id2 $type $bs 1
+}
+
+clean_files()
+{
+	rm -rf $SCRATCH_MNT/unmapped 2>/dev/null
+	rm -rf $SCRATCH_MNT/idmapped 2>/dev/null
+	rm -rf $tmp.quot 2>/dev/null
+	rm -rf $tmp.quota 2>/dev/null
+}
+
+filter_quot()
+{
+	_filter_quota | grep -v "root \|\#0 " \
+		| sed -e '/#[0-9]*/s/#[0-9]*/#ID/g'
+}
+
+filter_report()
+{
+	_filter_quota | grep -v "^root \|^\#0 " \
+		| sed -e '/^#[0-9]*/s/^#[0-9]*/#ID/g'
+}
+
+filter_quota()
+{
+	_filter_quota | sed -e "/Disk quotas for/s/([0-9]*)/(ID)/g" \
+			    -e "/Disk quotas for/s/#[0-9]*/#ID/g"
+}
+
+filter_state()
+{
+	_filter_quota | sed -e "s/Inode: #[0-9]* (0 blocks, 0 extents)/Inode: #[INO] (0 blocks, 0 extents)/g" \
+			    -e "s/Inode: #[0-9]* ([0-9]* blocks, [0-9]* extents)/Inode: #[INO] (X blocks, Y extents)/g" \
+			    -e "/[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/s/ [0-9][0-9]:[0-9][0-9]:[0-9][0-9]//g" \
+			    -e '/max warnings:/d'
+}
+
+test_quot()
+{
+	local opt="$*"
+
+	echo "checking quot command (type=$type)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "quot -$type $opt -bi" $SCRATCH_MNT | filter_quot
+}
+
+test_report()
+{
+	local opt="$*"
+
+	echo "checking report command (type=$type)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "report -$type $opt -bi" \
+			$SCRATCH_MNT | filter_report
+}
+
+test_quota()
+{
+	local opt="$*"
+
+	echo "checking quota command (type=$type)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "quota -$type $opt -bi $id" \
+			$SCRATCH_MNT | filter_quota
+}
+
+test_limit()
+{
+	local bs=$1
+	local bh=$2
+	local is=$3
+	local ih=$4
+
+	echo "checking limit command (type=$type, bsoft=$bs, bhard=$bh, isoft=$is, ihard=$ih)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "limit -$type bsoft=$bs bhard=$bh fsgqa" \
+			-c "limit -$type isoft=$is ihard=$ih fsgqa" \
+			$SCRATCH_MNT
+
+	# let the timer day transition happen
+	sleep 2
+}
+
+test_timer()
+{
+	echo "checking timer command (type=$type)"
+	# set 3days+1h for time won't become 2days soon
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "timer -$type -bi 73h" \
+			$SCRATCH_MNT | _filter_scratch
+}
+
+test_disable()
+{
+	echo "checking disable command (type=$type)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "disable -$type -v" \
+			$SCRATCH_MNT | filter_state
+}
+
+test_enable()
+{
+	echo "checking enable command (type=$type)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "enable -$type -v" $SCRATCH_MNT | filter_state
+}
+
+test_off()
+{
+	echo "checking off command (type=$type)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "off -$type -v" $SCRATCH_MNT | _filter_scratch
+}
+
+test_remove()
+{
+	echo "checking remove command (type=$type)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "remove -$type -v" \
+			$SCRATCH_MNT | _filter_scratch
+}
+
+test_state()
+{
+	echo "checking state command (type=$type)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "state -$type" $SCRATCH_MNT | filter_state
+}
+
+test_dump()
+{
+	echo "checking dump command (type=$type)"
+	rm -f $tmp.backup 2>>/dev/null
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "dump -$type -f $tmp.backup" \
+			$SCRATCH_MNT | _filter_scratch
+}
+
+test_restore()
+{
+	echo "checking restore command (type=$type)"
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+			-c "restore -$type -f $tmp.backup" \
+			$SCRATCH_MNT | _filter_scratch
+}
+
+_wipe_mounts()
+{
+	umount -l -q "${SCRATCH_MNT}/idmapped" >/dev/null 2>&1
+	_scratch_unmount >/dev/null 2>&1
+}
+
+_wipe_scratch()
+{
+	_wipe_mounts
+	_scratch_mkfs_xfs >>$seqres.full 2>&1
+}
+
+_qmount_idmapped()
+{
+	_wipe_mounts
+	_try_scratch_mount || _fail "qmount failed"
+
+	mkdir -p "${SCRATCH_MNT}/unmapped"
+	mkdir -p "${SCRATCH_MNT}/idmapped"
+
+	$here/src/idmapped-mounts/mount-idmapped --map-mount b:$id:$id2:1 --map-mount b:0:0:1 "$SCRATCH_MNT/unmapped" "$SCRATCH_MNT/idmapped" || _fail "mount-idmapped failed"
+
+	chmod ugo+rwx $SCRATCH_MNT
+	chmod ugo+rwx $SCRATCH_MNT/unmapped
+	chmod ugo+rwx $SCRATCH_MNT/idmapped
+}
+
+test_xfs_quota()
+{
+	# init quota
+	echo "init quota limit and timer, and dump it"
+	if [ "$idmapped" -eq 1 ]; then
+		echo "create_files_idmapped 1024k 15"; create_files_idmapped 1024k 15
+	else
+		echo "create_files_unmapped 1024k 15"; create_files_unmapped 1024k 15
+	fi
+	echo "quota remount"; _qmount_idmapped
+	echo ; test_quot
+	echo ; test_timer
+	echo ; test_limit 512k 2048k 10 20
+	echo ; test_dump
+
+	# report options test
+	echo "report options test"
+	echo ; test_report
+	echo "-N option"; test_report -N
+	echo "-L -U options"; test_report -L $id -U $id
+	echo "-t option"; test_report -t
+	echo "-n option"; test_report -n
+	echo "-h option"; test_report -h
+
+	# quot options test
+	echo "quot options test"
+	echo ; test_quot
+	echo "-f option"; test_quot -f $tmp.quot
+	cat $tmp.quot | filter_quot
+	echo "-n option"; test_quot -n
+
+	# quota options test
+	echo ; test_quota
+	echo "-f option"; test_quota -f $tmp.quota
+	cat $tmp.quota | filter_quota
+	echo "-N option"; test_quota -N
+	echo "-n option"; test_quota -n
+	echo "-h option"; test_quota -h
+
+	# disable/enable test
+	echo "disable quota"
+	echo ; test_disable
+	echo ; test_report -N
+	echo "expect a remove error at here"; test_remove
+	echo ; test_enable
+	echo ; test_report -N
+
+	# off and remove test
+	echo "off and remove test"
+	echo ; test_limit 100m 100m 100 100
+	echo ; test_quota -N
+	echo ; test_off
+	echo ; test_state
+	echo ; test_remove
+	echo ; test_report -N
+	echo "quota remount"; _qmount_idmapped
+	echo ; test_report -N
+
+	# restore test
+	echo "restore quota"
+	echo ; test_restore
+	echo ; test_report -N
+	echo ; test_state
+	echo "cleanup files"; clean_files
+}
+
+echo "----------------------- uquota,sync,unmapped ---------------------------"
+_wipe_scratch
+_qmount_option "uquota,sync"
+type=u
+id=$uqid
+id2=$uqid2
+idmapped=0
+_qmount_idmapped
+test_xfs_quota
+
+echo "----------------------- uquota,sync,idmapped ---------------------------"
+_wipe_scratch
+_qmount_option "uquota,sync"
+type=u
+id=$uqid
+id2=$uqid2
+idmapped=1
+_qmount_idmapped
+test_xfs_quota
+
+echo "----------------------- gquota,sync,unmapped ---------------------------"
+_wipe_scratch
+_qmount_option "gquota,sync"
+type=g
+id=$gqid
+id2=$gqid2
+idmapped=0
+_qmount_idmapped
+test_xfs_quota
+
+echo "----------------------- gquota,sync,idmapped ---------------------------"
+_wipe_scratch
+_qmount_option "gquota,sync"
+type=g
+id=$gqid
+id2=$gqid2
+idmapped=1
+_qmount_idmapped
+test_xfs_quota
+
+umount -l -q "${SCRATCH_MNT}/idmapped" >/dev/null 2>&1
+_scratch_unmount
+
+# success, all done
+status=0
+exit
diff --git a/tests/xfs/529.out b/tests/xfs/529.out
new file mode 100644
index 00000000..7d2cd96d
--- /dev/null
+++ b/tests/xfs/529.out
@@ -0,0 +1,657 @@
+QA output created by 529
+----------------------- uquota,sync,unmapped ---------------------------
+init quota limit and timer, and dump it
+create_files_unmapped 1024k 15
+quota remount
+
+checking quot command (type=u)
+SCRATCH_DEV (SCRATCH_MNT) User:
+ 1024 15 fsgqa 
+
+checking timer command (type=u)
+
+checking limit command (type=u, bsoft=512k, bhard=2048k, isoft=10, ihard=20)
+
+checking dump command (type=u)
+report options test
+
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-N option
+checking report command (type=u)
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-L -U options
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-t option
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-n option
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-h option
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/Grace 
+---------- --------------------------------- --------------------------------- 
+fsgqa 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days]
+
+quot options test
+
+checking quot command (type=u)
+SCRATCH_DEV (SCRATCH_MNT) User:
+ 1024 15 fsgqa 
+-f option
+checking quot command (type=u)
+SCRATCH_DEV (SCRATCH_MNT) User:
+ 1024 15 fsgqa 
+-n option
+checking quot command (type=u)
+SCRATCH_DEV (SCRATCH_MNT) User:
+ 1024 15 #ID 
+
+checking quota command (type=u)
+Disk quotas for User fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-f option
+checking quota command (type=u)
+Disk quotas for User fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-N option
+checking quota command (type=u)
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-n option
+checking quota command (type=u)
+Disk quotas for User #ID (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-h option
+checking quota command (type=u)
+Disk quotas for User fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+disable quota
+
+checking disable command (type=u)
+User quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: OFF
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [3 days]
+Inodes grace time: [3 days]
+Realtime Blocks grace time: [7 days]
+
+checking report command (type=u)
+fsgqa 1024 512 2048 00 [--------] 15 10 20 00 [--------]
+
+expect a remove error at here
+checking remove command (type=u)
+XFS_QUOTARM: Invalid argument
+
+checking enable command (type=u)
+User quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: ON
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [3 days]
+Inodes grace time: [3 days]
+Realtime Blocks grace time: [7 days]
+
+checking report command (type=u)
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+off and remove test
+
+checking limit command (type=u, bsoft=100m, bhard=100m, isoft=100, ihard=100)
+
+checking quota command (type=u)
+SCRATCH_DEV 1024 102400 102400 00 [--------] 15 100 100 00 [--------] SCRATCH_MNT
+
+checking off command (type=u)
+User quota are not enabled on SCRATCH_DEV
+
+checking state command (type=u)
+
+checking remove command (type=u)
+User quota are not enabled on SCRATCH_DEV
+
+checking report command (type=u)
+
+quota remount
+
+checking report command (type=u)
+fsgqa 1024 0 0 00 [--------] 15 0 0 00 [--------]
+
+restore quota
+
+checking restore command (type=u)
+
+checking report command (type=u)
+fsgqa 1024 512 2048 00 [7 days] 15 10 20 00 [7 days]
+
+
+checking state command (type=u)
+User quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: ON
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [7 days]
+Inodes grace time: [7 days]
+Realtime Blocks grace time: [7 days]
+cleanup files
+----------------------- uquota,sync,idmapped ---------------------------
+init quota limit and timer, and dump it
+create_files_idmapped 1024k 15
+quota remount
+
+checking quot command (type=u)
+SCRATCH_DEV (SCRATCH_MNT) User:
+ 1024 15 fsgqa 
+
+checking timer command (type=u)
+
+checking limit command (type=u, bsoft=512k, bhard=2048k, isoft=10, ihard=20)
+
+checking dump command (type=u)
+report options test
+
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-N option
+checking report command (type=u)
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-L -U options
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-t option
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-n option
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-h option
+checking report command (type=u)
+User quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/Grace 
+---------- --------------------------------- --------------------------------- 
+fsgqa 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days]
+
+quot options test
+
+checking quot command (type=u)
+SCRATCH_DEV (SCRATCH_MNT) User:
+ 1024 15 fsgqa 
+-f option
+checking quot command (type=u)
+SCRATCH_DEV (SCRATCH_MNT) User:
+ 1024 15 fsgqa 
+-n option
+checking quot command (type=u)
+SCRATCH_DEV (SCRATCH_MNT) User:
+ 1024 15 #ID 
+
+checking quota command (type=u)
+Disk quotas for User fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-f option
+checking quota command (type=u)
+Disk quotas for User fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-N option
+checking quota command (type=u)
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-n option
+checking quota command (type=u)
+Disk quotas for User #ID (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-h option
+checking quota command (type=u)
+Disk quotas for User fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+disable quota
+
+checking disable command (type=u)
+User quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: OFF
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [3 days]
+Inodes grace time: [3 days]
+Realtime Blocks grace time: [7 days]
+
+checking report command (type=u)
+fsgqa 1024 512 2048 00 [--------] 15 10 20 00 [--------]
+
+expect a remove error at here
+checking remove command (type=u)
+XFS_QUOTARM: Invalid argument
+
+checking enable command (type=u)
+User quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: ON
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [3 days]
+Inodes grace time: [3 days]
+Realtime Blocks grace time: [7 days]
+
+checking report command (type=u)
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+off and remove test
+
+checking limit command (type=u, bsoft=100m, bhard=100m, isoft=100, ihard=100)
+
+checking quota command (type=u)
+SCRATCH_DEV 1024 102400 102400 00 [--------] 15 100 100 00 [--------] SCRATCH_MNT
+
+checking off command (type=u)
+User quota are not enabled on SCRATCH_DEV
+
+checking state command (type=u)
+
+checking remove command (type=u)
+User quota are not enabled on SCRATCH_DEV
+
+checking report command (type=u)
+
+quota remount
+
+checking report command (type=u)
+fsgqa 1024 0 0 00 [--------] 15 0 0 00 [--------]
+
+restore quota
+
+checking restore command (type=u)
+
+checking report command (type=u)
+fsgqa 1024 512 2048 00 [7 days] 15 10 20 00 [7 days]
+
+
+checking state command (type=u)
+User quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: ON
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [7 days]
+Inodes grace time: [7 days]
+Realtime Blocks grace time: [7 days]
+cleanup files
+----------------------- gquota,sync,unmapped ---------------------------
+init quota limit and timer, and dump it
+create_files_unmapped 1024k 15
+quota remount
+
+checking quot command (type=g)
+SCRATCH_DEV (SCRATCH_MNT) Group:
+ 1024 15 fsgqa 
+
+checking timer command (type=g)
+
+checking limit command (type=g, bsoft=512k, bhard=2048k, isoft=10, ihard=20)
+
+checking dump command (type=g)
+report options test
+
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-N option
+checking report command (type=g)
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-L -U options
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-t option
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-n option
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-h option
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/Grace 
+---------- --------------------------------- --------------------------------- 
+fsgqa 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days]
+
+quot options test
+
+checking quot command (type=g)
+SCRATCH_DEV (SCRATCH_MNT) Group:
+ 1024 15 fsgqa 
+-f option
+checking quot command (type=g)
+SCRATCH_DEV (SCRATCH_MNT) Group:
+ 1024 15 fsgqa 
+-n option
+checking quot command (type=g)
+SCRATCH_DEV (SCRATCH_MNT) Group:
+ 1024 15 #ID 
+
+checking quota command (type=g)
+Disk quotas for Group fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-f option
+checking quota command (type=g)
+Disk quotas for Group fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-N option
+checking quota command (type=g)
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-n option
+checking quota command (type=g)
+Disk quotas for Group #ID (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-h option
+checking quota command (type=g)
+Disk quotas for Group fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+disable quota
+
+checking disable command (type=g)
+Group quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: OFF
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [3 days]
+Inodes grace time: [3 days]
+Realtime Blocks grace time: [7 days]
+
+checking report command (type=g)
+fsgqa 1024 512 2048 00 [--------] 15 10 20 00 [--------]
+
+expect a remove error at here
+checking remove command (type=g)
+XFS_QUOTARM: Invalid argument
+
+checking enable command (type=g)
+Group quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: ON
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [3 days]
+Inodes grace time: [3 days]
+Realtime Blocks grace time: [7 days]
+
+checking report command (type=g)
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+off and remove test
+
+checking limit command (type=g, bsoft=100m, bhard=100m, isoft=100, ihard=100)
+
+checking quota command (type=g)
+SCRATCH_DEV 1024 102400 102400 00 [--------] 15 100 100 00 [--------] SCRATCH_MNT
+
+checking off command (type=g)
+Group quota are not enabled on SCRATCH_DEV
+
+checking state command (type=g)
+
+checking remove command (type=g)
+Group quota are not enabled on SCRATCH_DEV
+
+checking report command (type=g)
+
+quota remount
+
+checking report command (type=g)
+fsgqa 1024 0 0 00 [--------] 15 0 0 00 [--------]
+
+restore quota
+
+checking restore command (type=g)
+
+checking report command (type=g)
+fsgqa 1024 512 2048 00 [7 days] 15 10 20 00 [7 days]
+
+
+checking state command (type=g)
+Group quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: ON
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [7 days]
+Inodes grace time: [7 days]
+Realtime Blocks grace time: [7 days]
+cleanup files
+----------------------- gquota,sync,idmapped ---------------------------
+init quota limit and timer, and dump it
+create_files_idmapped 1024k 15
+quota remount
+
+checking quot command (type=g)
+SCRATCH_DEV (SCRATCH_MNT) Group:
+ 1024 15 fsgqa 
+
+checking timer command (type=g)
+
+checking limit command (type=g, bsoft=512k, bhard=2048k, isoft=10, ihard=20)
+
+checking dump command (type=g)
+report options test
+
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-N option
+checking report command (type=g)
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-L -U options
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-t option
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-n option
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
+---------- -------------------------------------------------- -------------------------------------------------- 
+#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+-h option
+checking report command (type=g)
+Group quota on SCRATCH_MNT (SCRATCH_DEV)
+ Blocks Inodes 
+Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/Grace 
+---------- --------------------------------- --------------------------------- 
+fsgqa 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days]
+
+quot options test
+
+checking quot command (type=g)
+SCRATCH_DEV (SCRATCH_MNT) Group:
+ 1024 15 fsgqa 
+-f option
+checking quot command (type=g)
+SCRATCH_DEV (SCRATCH_MNT) Group:
+ 1024 15 fsgqa 
+-n option
+checking quot command (type=g)
+SCRATCH_DEV (SCRATCH_MNT) Group:
+ 1024 15 #ID 
+
+checking quota command (type=g)
+Disk quotas for Group fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-f option
+checking quota command (type=g)
+Disk quotas for Group fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-N option
+checking quota command (type=g)
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-n option
+checking quota command (type=g)
+Disk quotas for Group #ID (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+-h option
+checking quota command (type=g)
+Disk quotas for Group fsgqa (ID)
+Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
+SCRATCH_DEV 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
+disable quota
+
+checking disable command (type=g)
+Group quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: OFF
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [3 days]
+Inodes grace time: [3 days]
+Realtime Blocks grace time: [7 days]
+
+checking report command (type=g)
+fsgqa 1024 512 2048 00 [--------] 15 10 20 00 [--------]
+
+expect a remove error at here
+checking remove command (type=g)
+XFS_QUOTARM: Invalid argument
+
+checking enable command (type=g)
+Group quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: ON
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [3 days]
+Inodes grace time: [3 days]
+Realtime Blocks grace time: [7 days]
+
+checking report command (type=g)
+fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
+
+off and remove test
+
+checking limit command (type=g, bsoft=100m, bhard=100m, isoft=100, ihard=100)
+
+checking quota command (type=g)
+SCRATCH_DEV 1024 102400 102400 00 [--------] 15 100 100 00 [--------] SCRATCH_MNT
+
+checking off command (type=g)
+Group quota are not enabled on SCRATCH_DEV
+
+checking state command (type=g)
+
+checking remove command (type=g)
+Group quota are not enabled on SCRATCH_DEV
+
+checking report command (type=g)
+
+quota remount
+
+checking report command (type=g)
+fsgqa 1024 0 0 00 [--------] 15 0 0 00 [--------]
+
+restore quota
+
+checking restore command (type=g)
+
+checking report command (type=g)
+fsgqa 1024 512 2048 00 [7 days] 15 10 20 00 [7 days]
+
+
+checking state command (type=g)
+Group quota state on SCRATCH_MNT (SCRATCH_DEV)
+ Accounting: ON
+ Enforcement: ON
+ Inode: #[INO] (X blocks, Y extents)
+Blocks grace time: [7 days]
+Inodes grace time: [7 days]
+Realtime Blocks grace time: [7 days]
+cleanup files
diff --git a/tests/xfs/group b/tests/xfs/group
index 288b916d..fb4599ca 100644
--- a/tests/xfs/group
+++ b/tests/xfs/group
@@ -505,3 +505,4 @@
 526 auto quick mkfs
 527 auto quick quota
 528 auto quick rw realtime
+529 auto quick quota
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v9 4/4] xfs/530: quotas on idmapped mounts
  2021-03-16 10:36 [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests Christian Brauner
  2021-03-16 10:36 ` [PATCH v9 1/4] generic/631: add test for detached mount propagation Christian Brauner
  2021-03-16 10:36 ` [PATCH v9 3/4] xfs/529: quotas and idmapped mounts Christian Brauner
@ 2021-03-16 10:36 ` Christian Brauner
  2021-03-18  6:24   ` Christoph Hellwig
  2021-03-21 14:51   ` Eryu Guan
       [not found] ` <20210316103627.2954121-3-christian.brauner@ubuntu.com>
  2021-03-21 14:26 ` [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests Eryu Guan
  4 siblings, 2 replies; 22+ messages in thread
From: Christian Brauner @ 2021-03-16 10:36 UTC (permalink / raw)
  To: Christoph Hellwig, Darrick J . Wong, fstests
  Cc: David Howells, Christian Brauner

This is basically a re-implementation of xfs/050 but each file creation
call is done through an idmapped mount which verifies that the semantics
are identical even when the mount is idmapped.

Cc: Christoph Hellwig <hch@lst.de>
Cc: Darrick J. Wong <djwong@kernel.org>
Cc: fstests@vger.kernel.org
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
/* v1 */
patch not present

/* v2 */
patch not present

/* v3 */
patch not present

/* v4 */
patch not present

/* v5 */
patch not present

/* v6 */
patch not present

/* v7 */
patch not present

/* v8 */
patch introduced

/* v9 */
- Christian Brauner <christian.brauner@ubuntu.com>:
  - Rebased onto current master.
---
 tests/xfs/530     | 274 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/xfs/530.out | 129 ++++++++++++++++++++++
 tests/xfs/group   |   1 +
 3 files changed, 404 insertions(+)
 create mode 100644 tests/xfs/530
 create mode 100644 tests/xfs/530.out

diff --git a/tests/xfs/530 b/tests/xfs/530
new file mode 100644
index 00000000..9e10c40d
--- /dev/null
+++ b/tests/xfs/530
@@ -0,0 +1,274 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
+# All Rights Reserved.
+#
+# FS QA Test No. 530
+#
+# Exercises basic XFS quota functionality
+#       uquota, gquota, uqnoenforce, gqnoenforce
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+. ./common/quota
+
+_cleanup()
+{
+	cd /
+	_scratch_unmount 2>/dev/null
+	rm -f $tmp.*
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# real QA test starts here
+_supported_fs xfs
+
+cp /dev/null $seqres.full
+chmod a+rwx $seqres.full	# arbitrary users will write here
+
+_require_scratch
+_require_xfs_quota
+_require_user fsgqa
+
+_scratch_mkfs >/dev/null 2>&1
+_scratch_mount
+bsize=$(_get_file_block_size $SCRATCH_MNT)
+_scratch_unmount
+
+bsoft=$(( 200 * $bsize ))
+bhard=$(( 1000 * $bsize ))
+isoft=4
+ihard=10
+
+_filter_report()
+{
+	tr -s '[:space:]' | \
+	perl -npe '
+		s/^\#'$id' /[NAME] /g;
+		s/^\#0 \d+ /[ROOT] 0 /g;
+		s/6 days/7 days/g' |
+	perl -npe '
+		$val = 0;
+		if ($ENV{'LARGE_SCRATCH_DEV'}) {
+			$val = $ENV{'NUM_SPACE_FILES'};
+		}
+		s/(^\[ROOT\] \S+ \S+ \S+ \S+ \[--------\] )(\S+)/$1@{[$2 - $val]}/g' |
+	sed -e 's/ 65535 \[--------\]/ 00 \[--------\]/g' |
+	perl -npe '
+		s|^(.*?) (\d+) (\d+) (\d+)|$1 @{[$2 * 1024 /'$bsize']} @{[$3 * 1024 /'$bsize']} @{[$4 * 1024 /'$bsize']}|'
+}
+
+# The actual point at which limit enforcement takes place for the
+# hard block limit is variable depending on filesystem blocksize,
+# and iosize.  What we want to test is that the limit is enforced
+# (ie. blksize less than limit but not unduly less - ~85% is kind)
+# nowadays we actually get much closer to the limit before EDQUOT.
+#
+_filter_and_check_blks()
+{
+	perl -npe '
+		if (/^\#'$id'\s+(\d+)/ && '$enforce') {
+			$maximum = '$bhard';
+			$minimum = '$bhard' * 85/100;
+			$used = $1 * 1024;
+			if (($used < $minimum || $used > $maximum) && '$noextsz') {
+				printf(" URK %d: %d is out of range! [%d,%d]\n",
+					'$id', $used, $minimum, $maximum);
+			}
+			s/^(\#'$id'\s+)(\d+)/\1 =OK=/g;
+		}
+	' | _filter_report
+}
+
+_qsetup()
+{
+	opt=$1
+	enforce=0
+	if [ $opt = "u" -o $opt = "uno" ]; then
+		type=u
+		eval `_choose_uid`
+	elif [ $opt = "g" -o $opt = "gno" ]; then
+		type=g
+		eval `_choose_gid`
+	elif [ $opt = "p" -o $opt = "pno" ]; then
+		type=p
+		eval `_choose_prid`
+	fi
+	[ $opt = "u" -o $opt = "g" -o $opt = "p" ] && enforce=1
+
+	echo "Using type=$type id=$id" >> $seqres.full
+}
+
+_mount_idmapped()
+{
+	if [ "$type" == "u" ]; then
+		# This means root will be able to create files as uid %id in
+		# the underlying filesystem by going through the idmapped mount.
+		$here/src/idmapped-mounts/mount-idmapped --map-mount u:0:$id:1 \
+							 --map-mount u:$id:0:1 \
+							 --map-mount g:0:0:1 \
+							 "$SCRATCH_MNT" "$SCRATCH_MNT" || _fail "mount-idmapped failed"
+	else
+		# This means root will be able to create files as gid %id in
+		# the underlying filesystem by going through the idmapped mount.
+		$here/src/idmapped-mounts/mount-idmapped --map-mount g:0:$id:1 \
+							 --map-mount g:$id:0:1 \
+							 --map-mount u:0:0:1 \
+							 "$SCRATCH_MNT" "$SCRATCH_MNT" || _fail "mount-idmapped failed"
+	fi
+}
+
+_umount_idmapped()
+{
+	# ls -al "$SCRATCH_MNT"
+	umount -l -q "${SCRATCH_MNT}" >/dev/null 2>&1
+	# ls -al "$SCRATCH_MNT"
+}
+
+_exercise()
+{
+	_scratch_mkfs_xfs | _filter_mkfs 2>$tmp.mkfs
+	cat $tmp.mkfs >>$seqres.full
+
+	# keep the blocksize and data size for dd later
+	. $tmp.mkfs
+
+	_qmount
+
+	# Figure out whether we're doing large allocations
+	# (bail out if they're so large they stuff the test up)
+	_test_inode_flag extsz-inherit $SCRATCH_MNT
+	noextsz=$?
+	extsize=`_test_inode_extsz $SCRATCH_MNT`
+	[ $extsize -ge 512000 ] && \
+		_notrun "Extent size hint is too large ($extsize bytes)"
+
+	_qsetup $1
+
+	echo "Using type=$type id=$id" >>$seqres.full
+
+	$XFS_QUOTA_PROG -x -c "warn -$type 65535 -d" $SCRATCH_DEV
+
+	echo
+	echo "*** report no quota settings" | tee -a $seqres.full
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+		-c "repquota -birnN -$type" $SCRATCH_DEV |
+		_filter_report | LC_COLLATE=POSIX sort -ru
+
+	echo
+	echo "*** report initial settings" | tee -a $seqres.full
+	_mount_idmapped
+	_file_as_id $SCRATCH_MNT/initme 0 $type 1024 0
+	_umount_idmapped
+	echo "ls -l $SCRATCH_MNT" >>$seqres.full
+	ls -l $SCRATCH_MNT >>$seqres.full
+	$XFS_QUOTA_PROG -D $tmp.projects -P $temp.projid -x \
+		-c "limit -$type bsoft=${bsoft} bhard=${bhard} $id" \
+		-c "limit -$type isoft=$isoft ihard=$ihard $id" \
+		$SCRATCH_DEV
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+		-c "repquota -birnN -$type" $SCRATCH_DEV |
+		_filter_report | LC_COLLATE=POSIX sort -ru
+
+	echo
+	echo "*** push past the soft inode limit" | tee -a $seqres.full
+	_mount_idmapped
+	_file_as_id $SCRATCH_MNT/softie1 0 $type 1024 0
+	_file_as_id $SCRATCH_MNT/softie2 0 $type 1024 0
+	_file_as_id $SCRATCH_MNT/softie3 0 $type 1024 0
+	_file_as_id $SCRATCH_MNT/softie4 0 $type 1024 0
+	_umount_idmapped
+	_qmount
+	$XFS_QUOTA_PROG -x -c "warn -i -$type 0 $id" $SCRATCH_DEV
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+		-c "repquota -birnN -$type" $SCRATCH_DEV |
+		_filter_report | LC_COLLATE=POSIX sort -ru
+
+	echo
+	echo "*** push past the soft block limit" | tee -a $seqres.full
+	_mount_idmapped
+	_file_as_id $SCRATCH_MNT/softie 0 $type $bsize 300
+	_umount_idmapped
+	_qmount
+	$XFS_QUOTA_PROG -x -c "warn -i -$type 0 $id" \
+		-c "warn -b -$type 0 $id" $SCRATCH_DEV
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+		-c "repquota -birnN -$type" $SCRATCH_DEV |
+		_filter_report | LC_COLLATE=POSIX sort -ru
+
+	echo
+	# Note: for quota accounting (not enforcement), EDQUOT is not expected
+	echo "*** push past the hard inode limit (expect EDQUOT)" | tee -a $seqres.full
+	for i in 1 2 3 4 5 6 7 8 9 10 11 12
+	do
+		_mount_idmapped
+		_file_as_id $SCRATCH_MNT/hard$i 0 $type 1024 0
+		_umount_idmapped
+	done
+	_qmount
+	$XFS_QUOTA_PROG -x  -c "warn -b -$type 0 $id" \
+		-c "warn -i -$type 0 $id" $SCRATCH_DEV
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+		-c "repquota -birnN -$type" $SCRATCH_DEV |
+		_filter_report | LC_COLLATE=POSIX sort -ru
+
+	echo
+	# Note: for quota accounting (not enforcement), EDQUOT is not expected
+	echo "*** push past the hard block limit (expect EDQUOT)" | tee -a $seqres.full
+	_mount_idmapped
+	_file_as_id $SCRATCH_MNT/softie 0 $type $bsize 1200
+	_umount_idmapped
+	echo "ls -l $SCRATCH_MNT" >>$seqres.full
+	ls -l $SCRATCH_MNT >>$seqres.full
+	_qmount
+	$XFS_QUOTA_PROG -x -c "warn -b -$type 0 $id" $SCRATCH_DEV
+	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
+		-c "repquota -birnN -$type" $SCRATCH_DEV |
+		_filter_and_check_blks | LC_COLLATE=POSIX sort -ru
+
+	echo
+	echo "*** unmount"
+	_scratch_unmount
+
+}
+
+cat >$tmp.projects <<EOF
+1:$SCRATCH_MNT
+EOF
+
+cat >$tmp.projid <<EOF
+root:0
+scrach:1
+EOF
+
+projid_file="$tmp.projid"
+
+echo "*** user"
+_qmount_option "uquota"
+_exercise u
+
+echo "*** group"
+_qmount_option "gquota"
+_exercise g
+
+echo "*** uqnoenforce"
+_qmount_option "uqnoenforce"
+_exercise uno
+
+echo "*** gqnoenforce"
+_qmount_option "gqnoenforce"
+_exercise gno
+
+# success, all done
+status=0
+exit
diff --git a/tests/xfs/530.out b/tests/xfs/530.out
new file mode 100644
index 00000000..39266b38
--- /dev/null
+++ b/tests/xfs/530.out
@@ -0,0 +1,129 @@
+QA output created by 530
+*** user
+meta-data=DDEV isize=XXX agcount=N, agsize=XXX blks
+data     = bsize=XXX blocks=XXX, imaxpct=PCT
+         = sunit=XXX swidth=XXX, unwritten=X
+naming   =VERN bsize=XXX
+log      =LDEV bsize=XXX blocks=XXX
+realtime =RDEV extsz=XXX blocks=XXX, rtextents=XXX
+
+*** report no quota settings
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+
+*** report initial settings
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 0 200 1000 00 [--------] 1 4 10 00 [--------] 0 0 0 00 [--------]
+
+*** push past the soft inode limit
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 0 200 1000 00 [--------] 5 4 10 00 [7 days] 0 0 0 00 [--------]
+
+*** push past the soft block limit
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 300 200 1000 00 [7 days] 6 4 10 00 [7 days] 0 0 0 00 [--------]
+
+*** push past the hard inode limit (expect EDQUOT)
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 300 200 1000 00 [7 days] 10 4 10 00 [7 days] 0 0 0 00 [--------]
+
+*** push past the hard block limit (expect EDQUOT)
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] =OK= 200 1000 0 [7 days] 10 4 10 00 [7 days] 0 0 0 00 [--------]
+
+*** unmount
+*** group
+meta-data=DDEV isize=XXX agcount=N, agsize=XXX blks
+data     = bsize=XXX blocks=XXX, imaxpct=PCT
+         = sunit=XXX swidth=XXX, unwritten=X
+naming   =VERN bsize=XXX
+log      =LDEV bsize=XXX blocks=XXX
+realtime =RDEV extsz=XXX blocks=XXX, rtextents=XXX
+
+*** report no quota settings
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+
+*** report initial settings
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 0 200 1000 00 [--------] 1 4 10 00 [--------] 0 0 0 00 [--------]
+
+*** push past the soft inode limit
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 0 200 1000 00 [--------] 5 4 10 00 [7 days] 0 0 0 00 [--------]
+
+*** push past the soft block limit
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 300 200 1000 00 [7 days] 6 4 10 00 [7 days] 0 0 0 00 [--------]
+
+*** push past the hard inode limit (expect EDQUOT)
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 300 200 1000 00 [7 days] 10 4 10 00 [7 days] 0 0 0 00 [--------]
+
+*** push past the hard block limit (expect EDQUOT)
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] =OK= 200 1000 0 [7 days] 10 4 10 00 [7 days] 0 0 0 00 [--------]
+
+*** unmount
+*** uqnoenforce
+meta-data=DDEV isize=XXX agcount=N, agsize=XXX blks
+data     = bsize=XXX blocks=XXX, imaxpct=PCT
+         = sunit=XXX swidth=XXX, unwritten=X
+naming   =VERN bsize=XXX
+log      =LDEV bsize=XXX blocks=XXX
+realtime =RDEV extsz=XXX blocks=XXX, rtextents=XXX
+
+*** report no quota settings
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+
+*** report initial settings
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 0 200 1000 00 [--------] 1 4 10 00 [--------] 0 0 0 00 [--------]
+
+*** push past the soft inode limit
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 0 200 1000 00 [--------] 5 4 10 00 [--------] 0 0 0 00 [--------]
+
+*** push past the soft block limit
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 300 200 1000 00 [--------] 6 4 10 00 [--------] 0 0 0 00 [--------]
+
+*** push past the hard inode limit (expect EDQUOT)
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 300 200 1000 00 [--------] 18 4 10 00 [--none--] 0 0 0 00 [--------]
+
+*** push past the hard block limit (expect EDQUOT)
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 1200 200 1000 00 [--none--] 18 4 10 00 [--none--] 0 0 0 00 [--------]
+
+*** unmount
+*** gqnoenforce
+meta-data=DDEV isize=XXX agcount=N, agsize=XXX blks
+data     = bsize=XXX blocks=XXX, imaxpct=PCT
+         = sunit=XXX swidth=XXX, unwritten=X
+naming   =VERN bsize=XXX
+log      =LDEV bsize=XXX blocks=XXX
+realtime =RDEV extsz=XXX blocks=XXX, rtextents=XXX
+
+*** report no quota settings
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+
+*** report initial settings
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 0 200 1000 00 [--------] 1 4 10 00 [--------] 0 0 0 00 [--------]
+
+*** push past the soft inode limit
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 0 200 1000 00 [--------] 5 4 10 00 [--------] 0 0 0 00 [--------]
+
+*** push past the soft block limit
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 300 200 1000 00 [--------] 6 4 10 00 [--------] 0 0 0 00 [--------]
+
+*** push past the hard inode limit (expect EDQUOT)
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 300 200 1000 00 [--------] 18 4 10 00 [--none--] 0 0 0 00 [--------]
+
+*** push past the hard block limit (expect EDQUOT)
+[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
+[NAME] 1200 200 1000 00 [--none--] 18 4 10 00 [--none--] 0 0 0 00 [--------]
+
+*** unmount
diff --git a/tests/xfs/group b/tests/xfs/group
index fb4599ca..a536d01a 100644
--- a/tests/xfs/group
+++ b/tests/xfs/group
@@ -506,3 +506,4 @@
 527 auto quick quota
 528 auto quick rw realtime
 529 auto quick quota
+530 auto quick quota
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 1/4] generic/631: add test for detached mount propagation
  2021-03-16 10:36 ` [PATCH v9 1/4] generic/631: add test for detached mount propagation Christian Brauner
@ 2021-03-18  6:23   ` Christoph Hellwig
  2021-03-18 15:02     ` Darrick J. Wong
  2021-03-21 14:28   ` Eryu Guan
  1 sibling, 1 reply; 22+ messages in thread
From: Christoph Hellwig @ 2021-03-18  6:23 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

> +/* open_tree() */
> +#ifndef OPEN_TREE_CLONE
> +#define OPEN_TREE_CLONE 1
> +#endif
> +
> +#ifndef OPEN_TREE_CLOEXEC
> +#define OPEN_TREE_CLOEXEC O_CLOEXEC
> +#endif
> +
> +#ifndef __NR_open_tree
> +	#if defined __alpha__
> +		#define __NR_open_tree 538
> +	#elif defined _MIPS_SIM
> +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> +			#define __NR_open_tree 4428
> +		#endif
> +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> +			#define __NR_open_tree 6428
> +		#endif
> +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> +			#define __NR_open_tree 5428
> +		#endif
> +	#elif defined __ia64__
> +		#define __NR_open_tree (428 + 1024)
> +	#else
> +		#define __NR_open_tree 428
> +	#endif
> +#endif
> +
> +/* move_mount() */
> +#ifndef MOVE_MOUNT_F_EMPTY_PATH
> +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
> +#endif
> +
> +#ifndef __NR_move_mount
> +	#if defined __alpha__
> +		#define __NR_move_mount 539
> +	#elif defined _MIPS_SIM
> +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> +			#define __NR_move_mount 4429
> +		#endif
> +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> +			#define __NR_move_mount 6429
> +		#endif
> +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> +			#define __NR_move_mount 5429
> +		#endif
> +	#elif defined __ia64__
> +		#define __NR_move_mount (428 + 1024)
> +	#else
> +		#define __NR_move_mount 429
> +	#endif
> +#endif
> +
> +static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
> +{
> +	return syscall(__NR_open_tree, dfd, filename, flags);
> +}
> +
> +static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
> +				 const char *to_pathname, unsigned int flags)
> +{
> +	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
> +}

Not directly new in your patch, but I wish we'd have a central header
for missing UAPI bits in xfstests instead of spreading it over
various files.

Otherwise looks good:

Reviewed-by: Christoph Hellwig <hch@lst.de>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 2/4] generic/632: add fstests for idmapped mounts
       [not found] ` <20210316103627.2954121-3-christian.brauner@ubuntu.com>
@ 2021-03-18  6:23   ` Christoph Hellwig
  0 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2021-03-18  6:23 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

Looks good,

Reviewed-by: Christoph Hellwig <hch@lst.de>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 3/4] xfs/529: quotas and idmapped mounts
  2021-03-16 10:36 ` [PATCH v9 3/4] xfs/529: quotas and idmapped mounts Christian Brauner
@ 2021-03-18  6:24   ` Christoph Hellwig
  2021-03-21 14:42   ` Eryu Guan
  1 sibling, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2021-03-18  6:24 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

Looks good,

Reviewed-by: Christoph Hellwig <hch@lst.de>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 4/4] xfs/530: quotas on idmapped mounts
  2021-03-16 10:36 ` [PATCH v9 4/4] xfs/530: quotas on " Christian Brauner
@ 2021-03-18  6:24   ` Christoph Hellwig
  2021-03-21 14:51   ` Eryu Guan
  1 sibling, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2021-03-18  6:24 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

Looks good,

Reviewed-by: Christoph Hellwig <hch@lst.de>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 1/4] generic/631: add test for detached mount propagation
  2021-03-18  6:23   ` Christoph Hellwig
@ 2021-03-18 15:02     ` Darrick J. Wong
  2021-03-18 15:14       ` Christian Brauner
  0 siblings, 1 reply; 22+ messages in thread
From: Darrick J. Wong @ 2021-03-18 15:02 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: Christian Brauner, fstests, David Howells

On Thu, Mar 18, 2021 at 07:23:10AM +0100, Christoph Hellwig wrote:
> > +/* open_tree() */
> > +#ifndef OPEN_TREE_CLONE
> > +#define OPEN_TREE_CLONE 1
> > +#endif
> > +
> > +#ifndef OPEN_TREE_CLOEXEC
> > +#define OPEN_TREE_CLOEXEC O_CLOEXEC
> > +#endif
> > +
> > +#ifndef __NR_open_tree
> > +	#if defined __alpha__
> > +		#define __NR_open_tree 538
> > +	#elif defined _MIPS_SIM
> > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > +			#define __NR_open_tree 4428
> > +		#endif
> > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > +			#define __NR_open_tree 6428
> > +		#endif
> > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > +			#define __NR_open_tree 5428
> > +		#endif
> > +	#elif defined __ia64__
> > +		#define __NR_open_tree (428 + 1024)
> > +	#else
> > +		#define __NR_open_tree 428
> > +	#endif
> > +#endif
> > +
> > +/* move_mount() */
> > +#ifndef MOVE_MOUNT_F_EMPTY_PATH
> > +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
> > +#endif
> > +
> > +#ifndef __NR_move_mount
> > +	#if defined __alpha__
> > +		#define __NR_move_mount 539
> > +	#elif defined _MIPS_SIM
> > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > +			#define __NR_move_mount 4429
> > +		#endif
> > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > +			#define __NR_move_mount 6429
> > +		#endif
> > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > +			#define __NR_move_mount 5429
> > +		#endif
> > +	#elif defined __ia64__
> > +		#define __NR_move_mount (428 + 1024)
> > +	#else
> > +		#define __NR_move_mount 429
> > +	#endif
> > +#endif
> > +
> > +static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
> > +{
> > +	return syscall(__NR_open_tree, dfd, filename, flags);
> > +}
> > +
> > +static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
> > +				 const char *to_pathname, unsigned int flags)
> > +{
> > +	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
> > +}
> 
> Not directly new in your patch, but I wish we'd have a central header
> for missing UAPI bits in xfstests instead of spreading it over
> various files.

FWIW if you (or anyone else) /does/ create a src/linux_calls.h or
similar for things like this, I will totally start adding things to it
instead of scattering them everywhere. :)

--D

> Otherwise looks good:
> 
> Reviewed-by: Christoph Hellwig <hch@lst.de>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 1/4] generic/631: add test for detached mount propagation
  2021-03-18 15:02     ` Darrick J. Wong
@ 2021-03-18 15:14       ` Christian Brauner
  2021-03-18 15:57         ` Darrick J. Wong
  0 siblings, 1 reply; 22+ messages in thread
From: Christian Brauner @ 2021-03-18 15:14 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: Christoph Hellwig, fstests, David Howells

On Thu, Mar 18, 2021 at 08:02:30AM -0700, Darrick J. Wong wrote:
> On Thu, Mar 18, 2021 at 07:23:10AM +0100, Christoph Hellwig wrote:
> > > +/* open_tree() */
> > > +#ifndef OPEN_TREE_CLONE
> > > +#define OPEN_TREE_CLONE 1
> > > +#endif
> > > +
> > > +#ifndef OPEN_TREE_CLOEXEC
> > > +#define OPEN_TREE_CLOEXEC O_CLOEXEC
> > > +#endif
> > > +
> > > +#ifndef __NR_open_tree
> > > +	#if defined __alpha__
> > > +		#define __NR_open_tree 538
> > > +	#elif defined _MIPS_SIM
> > > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > > +			#define __NR_open_tree 4428
> > > +		#endif
> > > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > > +			#define __NR_open_tree 6428
> > > +		#endif
> > > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > > +			#define __NR_open_tree 5428
> > > +		#endif
> > > +	#elif defined __ia64__
> > > +		#define __NR_open_tree (428 + 1024)
> > > +	#else
> > > +		#define __NR_open_tree 428
> > > +	#endif
> > > +#endif
> > > +
> > > +/* move_mount() */
> > > +#ifndef MOVE_MOUNT_F_EMPTY_PATH
> > > +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
> > > +#endif
> > > +
> > > +#ifndef __NR_move_mount
> > > +	#if defined __alpha__
> > > +		#define __NR_move_mount 539
> > > +	#elif defined _MIPS_SIM
> > > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > > +			#define __NR_move_mount 4429
> > > +		#endif
> > > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > > +			#define __NR_move_mount 6429
> > > +		#endif
> > > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > > +			#define __NR_move_mount 5429
> > > +		#endif
> > > +	#elif defined __ia64__
> > > +		#define __NR_move_mount (428 + 1024)
> > > +	#else
> > > +		#define __NR_move_mount 429
> > > +	#endif
> > > +#endif
> > > +
> > > +static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
> > > +{
> > > +	return syscall(__NR_open_tree, dfd, filename, flags);
> > > +}
> > > +
> > > +static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
> > > +				 const char *to_pathname, unsigned int flags)
> > > +{
> > > +	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
> > > +}
> > 
> > Not directly new in your patch, but I wish we'd have a central header
> > for missing UAPI bits in xfstests instead of spreading it over
> > various files.
> 
> FWIW if you (or anyone else) /does/ create a src/linux_calls.h or
> similar for things like this, I will totally start adding things to it
> instead of scattering them everywhere. :)

I do usually have in my C projects two .h files (without corresponding
.c files):

1. syscall_numbers.h
   https://github.com/lxc/lxc/blob/master/src/lxc/syscall_numbers.h

   Where syscall_numbers defines the missing syscall numbers. For new
   syscalls in the style exhibited in this patch and for legacy syscalls
   it's a bit more nasty (i.e. syscalls before we unified numbering [apart
   from alpha]):
   
#ifndef __NR_memfd_create
	#if defined __i386__
		#define __NR_memfd_create 356
	#elif defined __x86_64__
		#define __NR_memfd_create 319
	#elif defined __arm__
		#define __NR_memfd_create 385
	#elif defined __aarch64__
		#define __NR_memfd_create 279
	#elif defined __s390__
		#define __NR_memfd_create 350
	#elif defined __powerpc__
		#define __NR_memfd_create 360
	#elif defined __riscv
		#define __NR_memfd_create 279
	#elif defined __sparc__
		#define __NR_memfd_create 348
	#elif defined __blackfin__
		#define __NR_memfd_create 390
	#elif defined __ia64__
		#define __NR_memfd_create 1340
	#elif defined _MIPS_SIM
		#if _MIPS_SIM == _MIPS_SIM_ABI32
			#define __NR_memfd_create 4354
		#endif
		#if _MIPS_SIM == _MIPS_SIM_NABI32
			#define __NR_memfd_create 6318
		#endif
		#if _MIPS_SIM == _MIPS_SIM_ABI64
			#define __NR_memfd_create 5314
		#endif
	#else
		#define -1
		#warning "__NR_memfd_create not defined for your architecture"
	#endif
#endif

2. syscall_wrappers.h
   https://github.com/lxc/lxc/blob/master/src/lxc/syscall_wrappers.h

   Where syscall_wrappers.h defines minimal wrappers for missing
   syscalls and their corresponding types.

#ifndef HAVE_SYS_SIGNALFD_H
struct signalfd_siginfo {
	uint32_t ssi_signo;
	int32_t ssi_errno;
	int32_t ssi_code;
	uint32_t ssi_pid;
	uint32_t ssi_uid;
	int32_t ssi_fd;
	uint32_t ssi_tid;
	uint32_t ssi_band;
	uint32_t ssi_overrun;
	uint32_t ssi_trapno;
	int32_t ssi_status;
	int32_t ssi_int;
	uint64_t ssi_ptr;
	uint64_t ssi_utime;
	uint64_t ssi_stime;
	uint64_t ssi_addr;
	uint8_t __pad[48];
};

static inline int signalfd(int fd, const sigset_t *mask, int flags)
{
	int retval;

	retval = syscall(__NR_signalfd4, fd, mask, _NSIG / 8, flags);
#ifdef __NR_signalfd
	if (errno == ENOSYS && flags == 0)
		retval = syscall(__NR_signalfd, fd, mask, _NSIG / 8);
#endif

	return retval;
}
#endif

Something like this might also work for xfstests.

Christian

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 1/4] generic/631: add test for detached mount propagation
  2021-03-18 15:14       ` Christian Brauner
@ 2021-03-18 15:57         ` Darrick J. Wong
  2021-03-18 16:31           ` Darrick J. Wong
  0 siblings, 1 reply; 22+ messages in thread
From: Darrick J. Wong @ 2021-03-18 15:57 UTC (permalink / raw)
  To: Christian Brauner; +Cc: Christoph Hellwig, fstests, David Howells

On Thu, Mar 18, 2021 at 04:14:44PM +0100, Christian Brauner wrote:
> On Thu, Mar 18, 2021 at 08:02:30AM -0700, Darrick J. Wong wrote:
> > On Thu, Mar 18, 2021 at 07:23:10AM +0100, Christoph Hellwig wrote:
> > > > +/* open_tree() */
> > > > +#ifndef OPEN_TREE_CLONE
> > > > +#define OPEN_TREE_CLONE 1
> > > > +#endif
> > > > +
> > > > +#ifndef OPEN_TREE_CLOEXEC
> > > > +#define OPEN_TREE_CLOEXEC O_CLOEXEC
> > > > +#endif
> > > > +
> > > > +#ifndef __NR_open_tree
> > > > +	#if defined __alpha__
> > > > +		#define __NR_open_tree 538
> > > > +	#elif defined _MIPS_SIM
> > > > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > > > +			#define __NR_open_tree 4428
> > > > +		#endif
> > > > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > > > +			#define __NR_open_tree 6428
> > > > +		#endif
> > > > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > > > +			#define __NR_open_tree 5428
> > > > +		#endif
> > > > +	#elif defined __ia64__
> > > > +		#define __NR_open_tree (428 + 1024)
> > > > +	#else
> > > > +		#define __NR_open_tree 428
> > > > +	#endif
> > > > +#endif
> > > > +
> > > > +/* move_mount() */
> > > > +#ifndef MOVE_MOUNT_F_EMPTY_PATH
> > > > +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
> > > > +#endif
> > > > +
> > > > +#ifndef __NR_move_mount
> > > > +	#if defined __alpha__
> > > > +		#define __NR_move_mount 539
> > > > +	#elif defined _MIPS_SIM
> > > > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > > > +			#define __NR_move_mount 4429
> > > > +		#endif
> > > > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > > > +			#define __NR_move_mount 6429
> > > > +		#endif
> > > > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > > > +			#define __NR_move_mount 5429
> > > > +		#endif
> > > > +	#elif defined __ia64__
> > > > +		#define __NR_move_mount (428 + 1024)
> > > > +	#else
> > > > +		#define __NR_move_mount 429
> > > > +	#endif
> > > > +#endif
> > > > +
> > > > +static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
> > > > +{
> > > > +	return syscall(__NR_open_tree, dfd, filename, flags);
> > > > +}
> > > > +
> > > > +static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
> > > > +				 const char *to_pathname, unsigned int flags)
> > > > +{
> > > > +	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
> > > > +}
> > > 
> > > Not directly new in your patch, but I wish we'd have a central header
> > > for missing UAPI bits in xfstests instead of spreading it over
> > > various files.
> > 
> > FWIW if you (or anyone else) /does/ create a src/linux_calls.h or
> > similar for things like this, I will totally start adding things to it
> > instead of scattering them everywhere. :)
> 
> I do usually have in my C projects two .h files (without corresponding
> .c files):
> 
> 1. syscall_numbers.h
>    https://github.com/lxc/lxc/blob/master/src/lxc/syscall_numbers.h
> 
>    Where syscall_numbers defines the missing syscall numbers. For new
>    syscalls in the style exhibited in this patch and for legacy syscalls
>    it's a bit more nasty (i.e. syscalls before we unified numbering [apart
>    from alpha]):
>    
> #ifndef __NR_memfd_create
> 	#if defined __i386__
> 		#define __NR_memfd_create 356
> 	#elif defined __x86_64__
> 		#define __NR_memfd_create 319
> 	#elif defined __arm__
> 		#define __NR_memfd_create 385
> 	#elif defined __aarch64__
> 		#define __NR_memfd_create 279
> 	#elif defined __s390__
> 		#define __NR_memfd_create 350
> 	#elif defined __powerpc__
> 		#define __NR_memfd_create 360
> 	#elif defined __riscv
> 		#define __NR_memfd_create 279
> 	#elif defined __sparc__
> 		#define __NR_memfd_create 348
> 	#elif defined __blackfin__
> 		#define __NR_memfd_create 390
> 	#elif defined __ia64__
> 		#define __NR_memfd_create 1340
> 	#elif defined _MIPS_SIM
> 		#if _MIPS_SIM == _MIPS_SIM_ABI32
> 			#define __NR_memfd_create 4354
> 		#endif
> 		#if _MIPS_SIM == _MIPS_SIM_NABI32
> 			#define __NR_memfd_create 6318
> 		#endif
> 		#if _MIPS_SIM == _MIPS_SIM_ABI64
> 			#define __NR_memfd_create 5314
> 		#endif
> 	#else
> 		#define -1
> 		#warning "__NR_memfd_create not defined for your architecture"
> 	#endif
> #endif
> 
> 2. syscall_wrappers.h
>    https://github.com/lxc/lxc/blob/master/src/lxc/syscall_wrappers.h
> 
>    Where syscall_wrappers.h defines minimal wrappers for missing
>    syscalls and their corresponding types.
> 
> #ifndef HAVE_SYS_SIGNALFD_H
> struct signalfd_siginfo {
> 	uint32_t ssi_signo;
> 	int32_t ssi_errno;
> 	int32_t ssi_code;
> 	uint32_t ssi_pid;
> 	uint32_t ssi_uid;
> 	int32_t ssi_fd;
> 	uint32_t ssi_tid;
> 	uint32_t ssi_band;
> 	uint32_t ssi_overrun;
> 	uint32_t ssi_trapno;
> 	int32_t ssi_status;
> 	int32_t ssi_int;
> 	uint64_t ssi_ptr;
> 	uint64_t ssi_utime;
> 	uint64_t ssi_stime;
> 	uint64_t ssi_addr;
> 	uint8_t __pad[48];
> };
> 
> static inline int signalfd(int fd, const sigset_t *mask, int flags)
> {
> 	int retval;
> 
> 	retval = syscall(__NR_signalfd4, fd, mask, _NSIG / 8, flags);
> #ifdef __NR_signalfd
> 	if (errno == ENOSYS && flags == 0)
> 		retval = syscall(__NR_signalfd, fd, mask, _NSIG / 8);
> #endif
> 
> 	return retval;
> }
> #endif
> 
> Something like this might also work for xfstests.

Yeah, not 5 minutes later someone emailed me a bug report that fstests
doesn't build on Ubuntu 16.04 due to its kernel headers not knowing
about fsxattr, so I guess I'll have a patch out adding this exact header
file later today.

--D

> 
> Christian

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 1/4] generic/631: add test for detached mount propagation
  2021-03-18 15:57         ` Darrick J. Wong
@ 2021-03-18 16:31           ` Darrick J. Wong
  0 siblings, 0 replies; 22+ messages in thread
From: Darrick J. Wong @ 2021-03-18 16:31 UTC (permalink / raw)
  To: Christian Brauner; +Cc: Christoph Hellwig, fstests, David Howells

On Thu, Mar 18, 2021 at 08:57:19AM -0700, Darrick J. Wong wrote:
> On Thu, Mar 18, 2021 at 04:14:44PM +0100, Christian Brauner wrote:
> > On Thu, Mar 18, 2021 at 08:02:30AM -0700, Darrick J. Wong wrote:
> > > On Thu, Mar 18, 2021 at 07:23:10AM +0100, Christoph Hellwig wrote:
> > > > > +/* open_tree() */
> > > > > +#ifndef OPEN_TREE_CLONE
> > > > > +#define OPEN_TREE_CLONE 1
> > > > > +#endif
> > > > > +
> > > > > +#ifndef OPEN_TREE_CLOEXEC
> > > > > +#define OPEN_TREE_CLOEXEC O_CLOEXEC
> > > > > +#endif
> > > > > +
> > > > > +#ifndef __NR_open_tree
> > > > > +	#if defined __alpha__
> > > > > +		#define __NR_open_tree 538
> > > > > +	#elif defined _MIPS_SIM
> > > > > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > > > > +			#define __NR_open_tree 4428
> > > > > +		#endif
> > > > > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > > > > +			#define __NR_open_tree 6428
> > > > > +		#endif
> > > > > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > > > > +			#define __NR_open_tree 5428
> > > > > +		#endif
> > > > > +	#elif defined __ia64__
> > > > > +		#define __NR_open_tree (428 + 1024)
> > > > > +	#else
> > > > > +		#define __NR_open_tree 428
> > > > > +	#endif
> > > > > +#endif
> > > > > +
> > > > > +/* move_mount() */
> > > > > +#ifndef MOVE_MOUNT_F_EMPTY_PATH
> > > > > +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
> > > > > +#endif
> > > > > +
> > > > > +#ifndef __NR_move_mount
> > > > > +	#if defined __alpha__
> > > > > +		#define __NR_move_mount 539
> > > > > +	#elif defined _MIPS_SIM
> > > > > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > > > > +			#define __NR_move_mount 4429
> > > > > +		#endif
> > > > > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > > > > +			#define __NR_move_mount 6429
> > > > > +		#endif
> > > > > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > > > > +			#define __NR_move_mount 5429
> > > > > +		#endif
> > > > > +	#elif defined __ia64__
> > > > > +		#define __NR_move_mount (428 + 1024)
> > > > > +	#else
> > > > > +		#define __NR_move_mount 429
> > > > > +	#endif
> > > > > +#endif
> > > > > +
> > > > > +static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
> > > > > +{
> > > > > +	return syscall(__NR_open_tree, dfd, filename, flags);
> > > > > +}
> > > > > +
> > > > > +static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
> > > > > +				 const char *to_pathname, unsigned int flags)
> > > > > +{
> > > > > +	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
> > > > > +}
> > > > 
> > > > Not directly new in your patch, but I wish we'd have a central header
> > > > for missing UAPI bits in xfstests instead of spreading it over
> > > > various files.
> > > 
> > > FWIW if you (or anyone else) /does/ create a src/linux_calls.h or
> > > similar for things like this, I will totally start adding things to it
> > > instead of scattering them everywhere. :)
> > 
> > I do usually have in my C projects two .h files (without corresponding
> > .c files):
> > 
> > 1. syscall_numbers.h
> >    https://github.com/lxc/lxc/blob/master/src/lxc/syscall_numbers.h
> > 
> >    Where syscall_numbers defines the missing syscall numbers. For new
> >    syscalls in the style exhibited in this patch and for legacy syscalls
> >    it's a bit more nasty (i.e. syscalls before we unified numbering [apart
> >    from alpha]):
> >    
> > #ifndef __NR_memfd_create
> > 	#if defined __i386__
> > 		#define __NR_memfd_create 356
> > 	#elif defined __x86_64__
> > 		#define __NR_memfd_create 319
> > 	#elif defined __arm__
> > 		#define __NR_memfd_create 385
> > 	#elif defined __aarch64__
> > 		#define __NR_memfd_create 279
> > 	#elif defined __s390__
> > 		#define __NR_memfd_create 350
> > 	#elif defined __powerpc__
> > 		#define __NR_memfd_create 360
> > 	#elif defined __riscv
> > 		#define __NR_memfd_create 279
> > 	#elif defined __sparc__
> > 		#define __NR_memfd_create 348
> > 	#elif defined __blackfin__
> > 		#define __NR_memfd_create 390
> > 	#elif defined __ia64__
> > 		#define __NR_memfd_create 1340
> > 	#elif defined _MIPS_SIM
> > 		#if _MIPS_SIM == _MIPS_SIM_ABI32
> > 			#define __NR_memfd_create 4354
> > 		#endif
> > 		#if _MIPS_SIM == _MIPS_SIM_NABI32
> > 			#define __NR_memfd_create 6318
> > 		#endif
> > 		#if _MIPS_SIM == _MIPS_SIM_ABI64
> > 			#define __NR_memfd_create 5314
> > 		#endif
> > 	#else
> > 		#define -1
> > 		#warning "__NR_memfd_create not defined for your architecture"
> > 	#endif
> > #endif
> > 
> > 2. syscall_wrappers.h
> >    https://github.com/lxc/lxc/blob/master/src/lxc/syscall_wrappers.h
> > 
> >    Where syscall_wrappers.h defines minimal wrappers for missing
> >    syscalls and their corresponding types.
> > 
> > #ifndef HAVE_SYS_SIGNALFD_H
> > struct signalfd_siginfo {
> > 	uint32_t ssi_signo;
> > 	int32_t ssi_errno;
> > 	int32_t ssi_code;
> > 	uint32_t ssi_pid;
> > 	uint32_t ssi_uid;
> > 	int32_t ssi_fd;
> > 	uint32_t ssi_tid;
> > 	uint32_t ssi_band;
> > 	uint32_t ssi_overrun;
> > 	uint32_t ssi_trapno;
> > 	int32_t ssi_status;
> > 	int32_t ssi_int;
> > 	uint64_t ssi_ptr;
> > 	uint64_t ssi_utime;
> > 	uint64_t ssi_stime;
> > 	uint64_t ssi_addr;
> > 	uint8_t __pad[48];
> > };
> > 
> > static inline int signalfd(int fd, const sigset_t *mask, int flags)
> > {
> > 	int retval;
> > 
> > 	retval = syscall(__NR_signalfd4, fd, mask, _NSIG / 8, flags);
> > #ifdef __NR_signalfd
> > 	if (errno == ENOSYS && flags == 0)
> > 		retval = syscall(__NR_signalfd, fd, mask, _NSIG / 8);
> > #endif
> > 
> > 	return retval;
> > }
> > #endif
> > 
> > Something like this might also work for xfstests.
> 
> Yeah, not 5 minutes later someone emailed me a bug report that fstests
> doesn't build on Ubuntu 16.04 due to its kernel headers not knowing
> about fsxattr, so I guess I'll have a patch out adding this exact header
> file later today.

Or, as my own notes reminded me, put all that stuff in a header file,
and include it from src/global.h if the system libraries didn't pull it
in.

--D

> --D
> 
> > 
> > Christian

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests
  2021-03-16 10:36 [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests Christian Brauner
                   ` (3 preceding siblings ...)
       [not found] ` <20210316103627.2954121-3-christian.brauner@ubuntu.com>
@ 2021-03-21 14:26 ` Eryu Guan
  2021-03-21 15:32   ` Christian Brauner
  4 siblings, 1 reply; 22+ messages in thread
From: Eryu Guan @ 2021-03-21 14:26 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Tue, Mar 16, 2021 at 11:36:23AM +0100, Christian Brauner wrote:
> Hey everyone,
> 
> /* v9 */
> Rebased onto current master.
> Hopefully we can merge this soon so we can have regular testing asap and
> the rebasing is getting a bit tedious too. ;)

Sorry for the late review.. You don't have to rebase on every release if
updating the test seq number is the only thing changed. Seq number
conflicts all the time, usually I can fix it on commit.

Patch 2 is missing from the list, maybe it's too big to post, could you
please provide a git repo so I can pull from there?

Some minor comments go to specific patch.

Thanks,
Eryu

> 
> ubuntu@f1-vm:~/src/git/xfstests$ sudo ./check generic/631
> FSTYP         -- xfs (non-debug)
> PLATFORM      -- Linux/x86_64 f1-vm 5.12.0-rc3-inode-helpers #343 SMP Mon Mar 15 12:57:02 UTC 2021
> MKFS_OPTIONS  -- -f -bsize=4096 /dev/loop1
> MOUNT_OPTIONS -- /dev/loop1 /mnt/scratch
> 
> generic/631 9s ...  11s
> Ran: generic/631
> Passed all 1 tests
> 
> ubuntu@f1-vm:~/src/git/xfstests$ sudo ./check generic/632
> FSTYP         -- xfs (non-debug)
> PLATFORM      -- Linux/x86_64 f1-vm 5.12.0-rc3-inode-helpers #343 SMP Mon Mar 15 12:57:02 UTC 2021
> MKFS_OPTIONS  -- -f -bsize=4096 /dev/loop1
> MOUNT_OPTIONS -- /dev/loop1 /mnt/scratch
> 
> generic/632      15s
> Ran: generic/632
> Passed all 1 tests
> 
> ubuntu@f1-vm:~/src/git/xfstests$ sudo ./check xfs/529
> FSTYP         -- xfs (non-debug)
> PLATFORM      -- Linux/x86_64 f1-vm 5.12.0-rc3-inode-helpers #343 SMP Mon Mar 15 12:57:02 UTC 2021
> MKFS_OPTIONS  -- -f -bsize=4096 /dev/loop1
> MOUNT_OPTIONS -- /dev/loop1 /mnt/scratch
> 
> xfs/529 25s ...  43s
> Ran: xfs/529
> Passed all 1 tests
> 
> ubuntu@f1-vm:~/src/git/xfstests$ sudo ./check xfs/530
> FSTYP         -- xfs (non-debug)
> PLATFORM      -- Linux/x86_64 f1-vm 5.12.0-rc3-inode-helpers #343 SMP Mon Mar 15 12:57:02 UTC 2021
> MKFS_OPTIONS  -- -f -bsize=4096 /dev/loop1
> MOUNT_OPTIONS -- /dev/loop1 /mnt/scratch
> 
> xfs/530  24s
> Ran: xfs/530
> Passed all 1 tests
> 
> /* v8 */
> This adds the testsuite for idmapped mounts. It has been expanded quite
> a bit too since the last version:
> - Add a testcase to verify that detached mounts in the new mount api
>   work correctly.
> - Add a full testsuite for idmapped mounts.
> - Add two quota tests verifying xfs behavior.
> 
> Sorry that it took so long to resend this testsuite but I had to chase
> and fix a bug in the new mount API (completely independent of idmapped
> mounts) before I could add quota tests because they would otherwise run
> danger to render the host mount namespace unuseable due to that bug
> (cf.  https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d3110f256d126b44d34c1f662310cd295877c447)
> All tests here pass for me on a kernel with idmapped mounts. The xfs
> specific quota tests should go on top of the fixes that Darrick is
> currently carrying for 5.12 and which he sent out a little while ago.
> 
> Thanks!
> Christian
> 
> Christian Brauner (4):
>   generic/631: add test for detached mount propagation
>   generic/632: add fstests for idmapped mounts
>   xfs/529: quotas and idmapped mounts
>   xfs/530: quotas on idmapped mounts
> 
>  .gitignore                            |    3 +
>  common/rc                             |   25 +
>  configure.ac                          |    2 +
>  include/builddefs.in                  |    1 +
>  m4/Makefile                           |    1 +
>  m4/package_libcap.m4                  |    4 +
>  src/Makefile                          |    8 +-
>  src/detached_mounts_propagation.c     |  189 +
>  src/feature.c                         |   40 +-
>  src/idmapped-mounts/Makefile          |   41 +
>  src/idmapped-mounts/idmapped-mounts.c | 8627 +++++++++++++++++++++++++
>  src/idmapped-mounts/missing.h         |  151 +
>  src/idmapped-mounts/mount-idmapped.c  |  428 ++
>  src/idmapped-mounts/utils.c           |  134 +
>  src/idmapped-mounts/utils.h           |   30 +
>  tests/generic/631                     |   41 +
>  tests/generic/631.out                 |    2 +
>  tests/generic/632                     |   42 +
>  tests/generic/632.out                 |    2 +
>  tests/generic/group                   |    2 +
>  tests/xfs/529                         |  373 ++
>  tests/xfs/529.out                     |  657 ++
>  tests/xfs/530                         |  274 +
>  tests/xfs/530.out                     |  129 +
>  tests/xfs/group                       |    2 +
>  25 files changed, 11203 insertions(+), 5 deletions(-)
>  create mode 100644 m4/package_libcap.m4
>  create mode 100644 src/detached_mounts_propagation.c
>  create mode 100644 src/idmapped-mounts/Makefile
>  create mode 100644 src/idmapped-mounts/idmapped-mounts.c
>  create mode 100644 src/idmapped-mounts/missing.h
>  create mode 100644 src/idmapped-mounts/mount-idmapped.c
>  create mode 100644 src/idmapped-mounts/utils.c
>  create mode 100644 src/idmapped-mounts/utils.h
>  create mode 100644 tests/generic/631
>  create mode 100644 tests/generic/631.out
>  create mode 100644 tests/generic/632
>  create mode 100644 tests/generic/632.out
>  create mode 100644 tests/xfs/529
>  create mode 100644 tests/xfs/529.out
>  create mode 100644 tests/xfs/530
>  create mode 100644 tests/xfs/530.out
> 
> 
> base-commit: f6ddaf130d5b0817278afe441fdde52f464f321b
> -- 
> 2.27.0

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 1/4] generic/631: add test for detached mount propagation
  2021-03-16 10:36 ` [PATCH v9 1/4] generic/631: add test for detached mount propagation Christian Brauner
  2021-03-18  6:23   ` Christoph Hellwig
@ 2021-03-21 14:28   ` Eryu Guan
  2021-03-22 10:00     ` Christian Brauner
  1 sibling, 1 reply; 22+ messages in thread
From: Eryu Guan @ 2021-03-21 14:28 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Tue, Mar 16, 2021 at 11:36:24AM +0100, Christian Brauner wrote:
> Regression test to verify that creating a series of detached mounts,
> attaching them to the filesystem, and unmounting them does not trigger an
> integer overflow in ns->mounts causing the kernel to block any new mounts in
> count_mounts() and returning ENOSPC because it falsely assumes that the
> maximum number of mounts in the mount namespace has been reached, i.e. it
> thinks it can't fit the new mounts into the mount namespace anymore.
> 
> The test is written in a way that it will leave the host's mount
> namespace intact so we are sure to never make the host's mount namespace
> unuseable!
> 
> Link: https://git.kernel.org/torvalds/c/ee2e3f50629f17b0752b55b2566c15ce8dafb557
> Cc: Christoph Hellwig <hch@lst.de>
> Cc: David Howells <dhowells@redhat.com>
> Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
> ---
> /* v1 */
> patch not present
> 
> /* v2 */
> patch not present
> 
> /* v3 */
> patch not present
> 
> /* v4 */
> patch not present
> 
> /* v5 */
> patch not present
> 
> /* v6 */
> patch not present
> 
> /* v7 */
> patch not present
> 
> /* v8 */
> patch introduced
> 
> /* v9 */
> - Christian Brauner <christian.brauner@ubuntu.com>:
>   - Rebased on current master.
> ---
>  .gitignore                        |   1 +
>  src/Makefile                      |   3 +-
>  src/detached_mounts_propagation.c | 252 ++++++++++++++++++++++++++++++
>  tests/generic/631                 |  41 +++++
>  tests/generic/631.out             |   2 +
>  tests/generic/group               |   1 +
>  6 files changed, 299 insertions(+), 1 deletion(-)
>  create mode 100644 src/detached_mounts_propagation.c
>  create mode 100644 tests/generic/631
>  create mode 100644 tests/generic/631.out
> 
> diff --git a/.gitignore b/.gitignore
> index f3bc0a4f..72bd40a0 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -62,6 +62,7 @@
>  /src/cloner
>  /src/dbtest
>  /src/deduperace
> +/src/detached_mounts_propagation
>  /src/devzero
>  /src/dio-interleaved
>  /src/dio-invalidate-cache
> diff --git a/src/Makefile b/src/Makefile
> index 3d729a34..99019e6e 100644
> --- a/src/Makefile
> +++ b/src/Makefile
> @@ -29,7 +29,8 @@ LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
>  	attr-list-by-handle-cursor-test listxattr dio-interleaved t_dir_type \
>  	dio-invalidate-cache stat_test t_encrypted_d_revalidate \
>  	attr_replace_test swapon mkswap t_attr_corruption t_open_tmpfiles \
> -	fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail
> +	fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail \
> +	detached_mounts_propagation
>  
>  SUBDIRS = log-writes perf
>  
> diff --git a/src/detached_mounts_propagation.c b/src/detached_mounts_propagation.c
> new file mode 100644
> index 00000000..4a588e46
> --- /dev/null
> +++ b/src/detached_mounts_propagation.c
> @@ -0,0 +1,252 @@
> +/* SPDX-License-Identifier: LGPL-2.1+ */
> +/*
> + * Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
> + * All Rights Reserved.
> + *
> + * Regression test to verify that creating a series of detached mounts,
> + * attaching them to the filesystem, and unmounting them does not trigger an
> + * integer overflow in ns->mounts causing the kernel to block any new mounts in
> + * count_mounts() and returning ENOSPC because it falsely assumes that the
> + * maximum number of mounts in the mount namespace has been reached, i.e. it
> + * thinks it can't fit the new mounts into the mount namespace anymore.
> + */
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <getopt.h>
> +#include <limits.h>
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/mount.h>
> +#include <sys/stat.h>
> +#include <sys/syscall.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +
> +/* open_tree() */
> +#ifndef OPEN_TREE_CLONE
> +#define OPEN_TREE_CLONE 1
> +#endif
> +
> +#ifndef OPEN_TREE_CLOEXEC
> +#define OPEN_TREE_CLOEXEC O_CLOEXEC
> +#endif
> +
> +#ifndef __NR_open_tree
> +	#if defined __alpha__
> +		#define __NR_open_tree 538
> +	#elif defined _MIPS_SIM
> +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> +			#define __NR_open_tree 4428
> +		#endif
> +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> +			#define __NR_open_tree 6428
> +		#endif
> +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> +			#define __NR_open_tree 5428
> +		#endif
> +	#elif defined __ia64__
> +		#define __NR_open_tree (428 + 1024)
> +	#else
> +		#define __NR_open_tree 428
> +	#endif
> +#endif
> +
> +/* move_mount() */
> +#ifndef MOVE_MOUNT_F_EMPTY_PATH
> +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
> +#endif
> +
> +#ifndef __NR_move_mount
> +	#if defined __alpha__
> +		#define __NR_move_mount 539
> +	#elif defined _MIPS_SIM
> +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> +			#define __NR_move_mount 4429
> +		#endif
> +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> +			#define __NR_move_mount 6429
> +		#endif
> +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> +			#define __NR_move_mount 5429
> +		#endif
> +	#elif defined __ia64__
> +		#define __NR_move_mount (428 + 1024)
> +	#else
> +		#define __NR_move_mount 429
> +	#endif
> +#endif
> +
> +static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
> +{
> +	return syscall(__NR_open_tree, dfd, filename, flags);
> +}
> +
> +static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
> +				 const char *to_pathname, unsigned int flags)
> +{
> +	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
> +}
> +
> +static bool is_shared_mountpoint(const char *path)
> +{
> +	bool shared = false;
> +	FILE *f = NULL;
> +	char *line = NULL;
> +	int i;
> +	size_t len = 0;
> +
> +	f = fopen("/proc/self/mountinfo", "re");
> +	if (!f)
> +		return 0;
> +
> +	while (getline(&line, &len, f) > 0) {
> +		char *slider1, *slider2;
> +
> +		for (slider1 = line, i = 0; slider1 && i < 4; i++)
> +			slider1 = strchr(slider1 + 1, ' ');
> +
> +		if (!slider1)
> +			continue;
> +
> +		slider2 = strchr(slider1 + 1, ' ');
> +		if (!slider2)
> +			continue;
> +
> +		*slider2 = '\0';
> +		if (strcmp(slider1 + 1, path) == 0) {
> +			/* This is the path. Is it shared? */
> +			slider1 = strchr(slider2 + 1, ' ');
> +			if (slider1 && strstr(slider1, "shared:")) {
> +				shared = true;
> +				break;
> +			}
> +		}
> +	}
> +	fclose(f);
> +	free(line);
> +
> +	return shared;
> +}
> +
> +static void usage(void)
> +{
> +	const char *text = "mount-new [--recursive] <base-dir>\n";
> +	fprintf(stderr, "%s", text);
> +	_exit(EXIT_SUCCESS);
> +}
> +
> +#define exit_usage(format, ...)                              \
> +	({                                                   \
> +		fprintf(stderr, format "\n", ##__VA_ARGS__); \
> +		usage();                                     \
> +	})
> +
> +#define exit_log(format, ...)                                \
> +	({                                                   \
> +		fprintf(stderr, format "\n", ##__VA_ARGS__); \
> +		exit(EXIT_FAILURE);                          \
> +	})
> +
> +static const struct option longopts[] = {
> +	{"help",	no_argument,		0,	'a'},
> +	{ NULL,		no_argument,		0,	 0 },
> +};
> +
> +int main(int argc, char *argv[])
> +{
> +	int exit_code = EXIT_SUCCESS, index = 0;
> +	int dfd, fd_tree, new_argc, ret;
> +	char *base_dir;
> +	char *const *new_argv;
> +	char target[PATH_MAX];
> +
> +	while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
> +		switch (ret) {
> +		case 'a':
> +			/* fallthrough */
> +		default:
> +			usage();
> +		}
> +	}
> +
> +	new_argv = &argv[optind];
> +	new_argc = argc - optind;
> +	if (new_argc < 1)
> +		exit_usage("Missing base directory\n");
> +	base_dir = new_argv[0];
> +
> +	if (*base_dir != '/')
> +		exit_log("Please specify an absolute path");
> +
> +	/* Ensure that target is a shared mountpoint. */
> +	if (!is_shared_mountpoint(base_dir))
> +		exit_log("Please ensure that \"%s\" is a shared mountpoint", base_dir);
> +
> +	ret = unshare(CLONE_NEWNS);
> +	if (ret < 0)
> +		exit_log("%m - Failed to create new mount namespace");
> +
> +	ret = mount(NULL, base_dir, NULL, MS_REC | MS_SHARED, NULL);
> +	if (ret < 0)
> +		exit_log("%m - Failed to make base_dir shared mountpoint");
> +
> +	dfd = open(base_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
> +	if (dfd < 0)
> +		exit_log("%m - Failed to open base directory \"%s\"", base_dir);
> +
> +	ret = mkdirat(dfd, "detached-move-mount", 0755);
> +	if (ret < 0)
> +		exit_log("%m - Failed to create required temporary directories");
> +
> +	ret = snprintf(target, sizeof(target), "%s/detached-move-mount", base_dir);
> +	if (ret < 0 || (size_t)ret >= sizeof(target))
> +		exit_log("%m - Failed to assemble target path");
> +
> +	/*
> +	 * Having a mount table with 10000 mounts is already quite excessive
> +	 * and shoult account even for weird test systems.
> +	 */
> +	for (size_t i = 0; i < 10000; i++) {
> +		fd_tree = sys_open_tree(dfd, "detached-move-mount",
> +					OPEN_TREE_CLONE |
> +					OPEN_TREE_CLOEXEC |
> +					AT_EMPTY_PATH);
> +		if (fd_tree < 0) {
> +			if (errno == ENOSYS) /* New mount API not (fully) supported. */
> +				break;
> +
> +			fprintf(stderr, "%m - Failed to open %d(detached-move-mount)", dfd);
> +			exit_code = EXIT_FAILURE;
> +			break;
> +		}
> +
> +		ret = sys_move_mount(fd_tree, "", dfd, "detached-move-mount", MOVE_MOUNT_F_EMPTY_PATH);
> +		if (ret < 0) {
> +			if (errno == ENOSPC)
> +				fprintf(stderr, "%m - Buggy mount counting");
> +			else if (errno == ENOSYS) /* New mount API not (fully) supported. */
> +				break;
> +			else
> +				fprintf(stderr, "%m - Failed to attach mount to %d(detached-move-mount)", dfd);
> +			exit_code = EXIT_FAILURE;
> +			break;
> +		}
> +		close(fd_tree);
> +
> +		ret = umount2(target, MNT_DETACH);
> +		if (ret < 0) {
> +			fprintf(stderr, "%m - Failed to unmount %s", target);
> +			exit_code = EXIT_FAILURE;
> +			break;
> +		}
> +	}
> +
> +	(void)unlinkat(dfd, "detached-move-mount", AT_REMOVEDIR);
> +	close(dfd);
> +
> +	exit(exit_code);
> +}
> diff --git a/tests/generic/631 b/tests/generic/631
> new file mode 100644
> index 00000000..e227c448
> --- /dev/null
> +++ b/tests/generic/631
> @@ -0,0 +1,41 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0+
> +#
> +# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
> +# All Rights Reserved.
> +#
> +# FS QA Test No. 631
> +#
> +# Regression test to verify that creating a series of detached mounts,
> +# attaching them to the filesystem, and unmounting them does not trigger an
> +# integer overflow in ns->mounts causing the kernel to block any new mounts in
> +# count_mounts() and returning ENOSPC because it falsely assumes that the
> +# maximum number of mounts in the mount namespace has been reached, i.e. it
> +# thinks it can't fit the new mounts into the mount namespace anymore.
> +#
> +seq=`basename $0`
> +seqres=$RESULT_DIR/$seq
> +echo "QA output created by $seq"
> +
> +here=`pwd`
> +tmp=/tmp/$$
> +status=1	# failure is the default!
> +trap "_cleanup; exit \$status" 0 1 2 3 15
> +
> +_cleanup()
> +{
> +	cd /
> +	rm -f $tmp.*
> +}
> +
> +. ./common/rc
> +
> +rm -f $seqres.full
> +
> +_supported_fs generic

_require_test

As test runs directly on $TEST_DIR.

Also needs

_require_test_program "detached_mounts_propagation"

> +
> +$here/src/detached_mounts_propagation $TEST_DIR >> $seqres.full
> +
> +echo silence is golden
> +status=$?

This should go after detached_mounts_propagation command?

Thanks,
Eryu

> +exit
> diff --git a/tests/generic/631.out b/tests/generic/631.out
> new file mode 100644
> index 00000000..ce88f447
> --- /dev/null
> +++ b/tests/generic/631.out
> @@ -0,0 +1,2 @@
> +QA output created by 631
> +silence is golden
> diff --git a/tests/generic/group b/tests/generic/group
> index 7c7531d1..151f8af2 100644
> --- a/tests/generic/group
> +++ b/tests/generic/group
> @@ -633,3 +633,4 @@
>  628 auto quick rw clone
>  629 auto quick rw copy_range
>  630 auto quick rw dedupe clone
> +631 auto quick mount
> -- 
> 2.27.0

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 3/4] xfs/529: quotas and idmapped mounts
  2021-03-16 10:36 ` [PATCH v9 3/4] xfs/529: quotas and idmapped mounts Christian Brauner
  2021-03-18  6:24   ` Christoph Hellwig
@ 2021-03-21 14:42   ` Eryu Guan
  2021-03-22 10:11     ` Christian Brauner
  1 sibling, 1 reply; 22+ messages in thread
From: Eryu Guan @ 2021-03-21 14:42 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Tue, Mar 16, 2021 at 11:36:26AM +0100, Christian Brauner wrote:
> Test that xfs quota behave correctly on idmapped mounts.
> Mount a scratch device with user and group quota support enabled. Create
> directories "unmapped" and "idmapped". Create files in the unampped
> mount and verify quota behavior. Create files through the idmapped mount
> and verify identical behavior.
> 
> Cc: Christoph Hellwig <hch@lst.de>
> Cc: Darrick J. Wong <djwong@kernel.org>
> Cc: fstests@vger.kernel.org
> Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
> ---
> /* v1 */
> patch not present
> 
> /* v2 */
> patch not present
> 
> /* v3 */
> patch not present
> 
> /* v4 */
> patch not present
> 
> /* v5 */
> patch not present
> 
> /* v6 */
> patch not present
> 
> /* v7 */
> patch not present
> 
> /* v8 */
> patch introduced
> 
> /* v9 */
> - Christian Brauner <christian.brauner@ubuntu.com>:
>   - Rebased on current master.
> ---
>  .gitignore                           |   1 +
>  src/idmapped-mounts/Makefile         |  14 +-
>  src/idmapped-mounts/mount-idmapped.c | 428 +++++++++++++++++
>  src/idmapped-mounts/utils.c          |   2 +-
>  src/idmapped-mounts/utils.h          |   1 +
>  tests/xfs/529                        | 373 +++++++++++++++
>  tests/xfs/529.out                    | 657 +++++++++++++++++++++++++++
>  tests/xfs/group                      |   1 +
>  8 files changed, 1472 insertions(+), 5 deletions(-)
>  create mode 100644 src/idmapped-mounts/mount-idmapped.c
>  create mode 100644 tests/xfs/529
>  create mode 100644 tests/xfs/529.out
> 
> diff --git a/.gitignore b/.gitignore
> index 3229bb26..4cc9c807 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -179,6 +179,7 @@
>  /src/aio-dio-regress/aiocp
>  /src/aio-dio-regress/aiodio_sparse2
>  /src/idmapped-mounts/idmapped-mounts
> +/src/idmapped-mounts/mount-idmapped
>  /src/log-writes/replay-log
>  /src/perf/*.pyc
>  
> diff --git a/src/idmapped-mounts/Makefile b/src/idmapped-mounts/Makefile
> index 6a934146..40bf9164 100644
> --- a/src/idmapped-mounts/Makefile
> +++ b/src/idmapped-mounts/Makefile
> @@ -3,9 +3,10 @@
>  TOPDIR = ../..
>  include $(TOPDIR)/include/builddefs
>  
> -TARGETS = idmapped-mounts
> +TARGETS = idmapped-mounts mount-idmapped
> +CFILES_IDMAPPED_MOUNTS = idmapped-mounts.c utils.c
> +CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c
>  
> -CFILES = idmapped-mounts.c utils.c
>  HFILES = missing.h utils.h
>  LLDLIBS += -pthread
>  LDIRT = $(TARGETS)
> @@ -24,12 +25,17 @@ depend: .dep
>  
>  include $(BUILDRULES)
>  
> -$(TARGETS): $(CFILES)
> +idmapped-mounts:
>  	@echo "    [CC]    $@"
> -	$(Q)$(LTLINK) $(CFILES) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
> +	$(Q)$(LTLINK) $(CFILES_IDMAPPED_MOUNTS) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
> +
> +mount-idmapped:
> +	@echo "    [CC]    $@"
> +	$(Q)$(LTLINK) $(CFILES_MOUNT_IDMAPPED) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
>  
>  install:
>  	$(INSTALL) -m 755 -d $(PKG_LIB_DIR)/src/idmapped-mounts
>  	$(INSTALL) -m 755 $(TARGETS) $(PKG_LIB_DIR)/src/idmapped-mounts
> +	$(INSTALL) -m 755 $(TARGETS) $(PKG_LIB_DIR)/src/mount-idmapped
>  
>  -include .dep
> diff --git a/src/idmapped-mounts/mount-idmapped.c b/src/idmapped-mounts/mount-idmapped.c
> new file mode 100644
> index 00000000..f127cdc7
> --- /dev/null
> +++ b/src/idmapped-mounts/mount-idmapped.c
> @@ -0,0 +1,428 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#ifndef _GNU_SOURCE
> +#define _GNU_SOURCE
> +#endif
> +
> +#include "../global.h"
> +
> +#include <dirent.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <getopt.h>
> +#include <libgen.h>
> +#include <limits.h>
> +#include <linux/bpf.h>
> +#include <linux/sched.h>
> +#include <linux/seccomp.h>
> +#include <sched.h>
> +#include <signal.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/mman.h>
> +#include <sys/stat.h>
> +#include <sys/syscall.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +#include "missing.h"
> +#include "utils.h"
> +
> +/* A few helpful macros. */
> +#define STRLITERALLEN(x) (sizeof(""x"") - 1)
> +
> +#define INTTYPE_TO_STRLEN(type)             \
> +	(2 + (sizeof(type) <= 1             \
> +		  ? 3                       \
> +		  : sizeof(type) <= 2       \
> +			? 5                 \
> +			: sizeof(type) <= 4 \
> +			      ? 10          \
> +			      : sizeof(type) <= 8 ? 20 : sizeof(int[-2 * (sizeof(type) > 8)])))
> +
> +#define syserror(format, ...)                           \
> +	({                                              \
> +		fprintf(stderr, format, ##__VA_ARGS__); \
> +		(-errno);                               \
> +	})
> +
> +#define syserror_set(__ret__, format, ...)                    \
> +	({                                                    \
> +		typeof(__ret__) __internal_ret__ = (__ret__); \
> +		errno = labs(__ret__);                        \
> +		fprintf(stderr, format, ##__VA_ARGS__);       \
> +		__internal_ret__;                             \
> +	})
> +
> +struct list {
> +	void *elem;
> +	struct list *next;
> +	struct list *prev;
> +};
> +
> +#define list_for_each(__iterator, __list) \
> +	for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
> +
> +static inline void list_init(struct list *list)
> +{
> +	list->elem = NULL;
> +	list->next = list->prev = list;
> +}
> +
> +static inline int list_empty(const struct list *list)
> +{
> +	return list == list->next;
> +}
> +
> +static inline void __list_add(struct list *new, struct list *prev, struct list *next)
> +{
> +	next->prev = new;
> +	new->next = next;
> +	new->prev = prev;
> +	prev->next = new;
> +}
> +
> +static inline void list_add_tail(struct list *head, struct list *list)
> +{
> +	__list_add(list, head->prev, head);
> +}
> +
> +typedef enum idmap_type_t {
> +	ID_TYPE_UID,
> +	ID_TYPE_GID
> +} idmap_type_t;
> +
> +struct id_map {
> +	idmap_type_t map_type;
> +	__u32 nsid;
> +	__u32 hostid;
> +	__u32 range;
> +};
> +
> +static struct list active_map;
> +
> +static int add_map_entry(__u32 id_host,
> +			 __u32 id_ns,
> +			 __u32 range,
> +			 idmap_type_t map_type)
> +{
> +	struct list *new_list = NULL;
> +	struct id_map *newmap = NULL;
> +
> +	newmap = malloc(sizeof(*newmap));
> +	if (!newmap)
> +		return -ENOMEM;
> +
> +	new_list = malloc(sizeof(struct list));
> +	if (!new_list) {
> +		free(newmap);
> +		return -ENOMEM;
> +	}
> +
> +	*newmap = (struct id_map){
> +		.hostid		= id_host,
> +		.nsid		= id_ns,
> +		.range		= range,
> +		.map_type	= map_type,
> +	};
> +
> +	new_list->elem = newmap;
> +	list_add_tail(&active_map, new_list);
> +	return 0;
> +}
> +
> +static int parse_map(char *map)
> +{
> +	char types[2] = {'u', 'g'};
> +	int ret;
> +	__u32 id_host, id_ns, range;
> +	char which;
> +
> +	if (!map)
> +		return -1;
> +
> +	ret = sscanf(map, "%c:%u:%u:%u", &which, &id_ns, &id_host, &range);
> +	if (ret != 4)
> +		return -1;
> +
> +	if (which != 'b' && which != 'u' && which != 'g')
> +		return -1;
> +
> +	for (int i = 0; i < 2; i++) {
> +		idmap_type_t map_type;
> +
> +		if (which != types[i] && which != 'b')
> +			continue;
> +
> +		if (types[i] == 'u')
> +			map_type = ID_TYPE_UID;
> +		else
> +			map_type = ID_TYPE_GID;
> +
> +		ret = add_map_entry(id_host, id_ns, range, map_type);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int write_id_mapping(idmap_type_t map_type, pid_t pid, const char *buf, size_t buf_size)
> +{
> +	int fd = -EBADF, setgroups_fd = -EBADF;
> +	int fret = -1;
> +	int ret;
> +	char path[STRLITERALLEN("/proc") + INTTYPE_TO_STRLEN(pid_t) +
> +		  STRLITERALLEN("/setgroups") + 1];
> +
> +	if (geteuid() != 0 && map_type == ID_TYPE_GID) {
> +		ret = snprintf(path, sizeof(path), "/proc/%d/setgroups", pid);
> +		if (ret < 0 || ret >= sizeof(path))
> +			goto out;
> +
> +		setgroups_fd = open(path, O_WRONLY | O_CLOEXEC);
> +		if (setgroups_fd < 0 && errno != ENOENT) {
> +			syserror("Failed to open \"%s\"", path);
> +			goto out;
> +		}
> +
> +		if (setgroups_fd >= 0) {
> +			ret = write_nointr(setgroups_fd, "deny\n", STRLITERALLEN("deny\n"));
> +			if (ret != STRLITERALLEN("deny\n")) {
> +				syserror("Failed to write \"deny\" to \"/proc/%d/setgroups\"", pid);
> +				goto out;
> +			}
> +		}
> +	}
> +
> +	ret = snprintf(path, sizeof(path), "/proc/%d/%cid_map", pid, map_type == ID_TYPE_UID ? 'u' : 'g');
> +	if (ret < 0 || ret >= sizeof(path))
> +		goto out;
> +
> +	fd = open(path, O_WRONLY | O_CLOEXEC);
> +	if (fd < 0) {
> +		syserror("Failed to open \"%s\"", path);
> +		goto out;
> +	}
> +
> +	ret = write_nointr(fd, buf, buf_size);
> +	if (ret != buf_size) {
> +		syserror("Failed to write %cid mapping to \"%s\"",
> +			 map_type == ID_TYPE_UID ? 'u' : 'g', path);
> +		goto out;
> +	}
> +
> +	fret = 0;
> +out:
> +	if (fd >= 0)
> +		close(fd);
> +	if (setgroups_fd >= 0)
> +		close(setgroups_fd);
> +
> +	return fret;
> +}
> +
> +static int map_ids_from_idmap(struct list *idmap, pid_t pid)
> +{
> +	int fill, left;
> +	char mapbuf[4096] = {};
> +	bool had_entry = false;
> +
> +	for (idmap_type_t map_type = ID_TYPE_UID, u_or_g = 'u';
> +	     map_type <= ID_TYPE_GID; map_type++, u_or_g = 'g') {
> +		char *pos = mapbuf;
> +		int ret;
> +		struct list *iterator;
> +
> +
> +		list_for_each(iterator, idmap) {
> +			struct id_map *map = iterator->elem;
> +			if (map->map_type != map_type)
> +				continue;
> +
> +			had_entry = true;
> +
> +			left = 4096 - (pos - mapbuf);
> +			fill = snprintf(pos, left, "%u %u %u\n", map->nsid, map->hostid, map->range);
> +			/*
> +			 * The kernel only takes <= 4k for writes to
> +			 * /proc/<pid>/{g,u}id_map
> +			 */
> +			if (fill <= 0 || fill >= left)
> +				return syserror_set(-E2BIG, "Too many %cid mappings defined", u_or_g);
> +
> +			pos += fill;
> +		}
> +		if (!had_entry)
> +			continue;
> +
> +		ret = write_id_mapping(map_type, pid, mapbuf, pos - mapbuf);
> +		if (ret < 0)
> +			return syserror("Failed to write mapping: %s", mapbuf);
> +
> +		memset(mapbuf, 0, sizeof(mapbuf));
> +	}
> +
> +	return 0;
> +}
> +
> +static int get_userns_fd_from_idmap(struct list *idmap)
> +{
> +	int ret;
> +	pid_t pid;
> +	char path_ns[STRLITERALLEN("/proc") + INTTYPE_TO_STRLEN(pid_t) +
> +		  STRLITERALLEN("/ns/user") + 1];
> +
> +	pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER | CLONE_NEWNS);
> +	if (pid < 0)
> +		return -errno;
> +
> +	ret = map_ids_from_idmap(idmap, pid);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = snprintf(path_ns, sizeof(path_ns), "/proc/%d/ns/user", pid);
> +	if (ret < 0 || (size_t)ret >= sizeof(path_ns))
> +		ret = -EIO;
> +	else
> +		ret = open(path_ns, O_RDONLY | O_CLOEXEC | O_NOCTTY);
> +
> +	(void)kill(pid, SIGKILL);
> +	(void)wait_for_pid(pid);
> +	return ret;
> +}
> +
> +static inline bool strnequal(const char *str, const char *eq, size_t len)
> +{
> +	return strncmp(str, eq, len) == 0;
> +}
> +
> +static void usage(void)
> +{
> +	const char *text = "\
> +mount-idmapped --map-mount=<idmap> <source> <target>\n\
> +\n\
> +Create an idmapped mount of <source> at <target>\n\
> +Options:\n\
> +  --map-mount=<idmap>\n\
> +	Specify an idmap for the <target> mount in the format\n\
> +	<idmap-type>:<id-from>:<id-to>:<id-range>\n\
> +	The <idmap-type> can be:\n\
> +	\"b\" or \"both\"	-> map both uids and gids\n\
> +	\"u\" or \"uid\"	-> map uids\n\
> +	\"g\" or \"gid\"	-> map gids\n\
> +	For example, specifying:\n\
> +	both:1000:1001:1	-> map uid and gid 1000 to uid and gid 1001 in <target> and no other ids\n\
> +	uid:20000:100000:1000	-> map uid 20000 to uid 100000, uid 20001 to uid 100001 [...] in <target>\n\
> +	Currently up to 340 separate idmappings may be specified.\n\n\
> +  --map-mount=/proc/<pid>/ns/user\n\
> +	Specify a path to a user namespace whose idmap is to be used.\n\n\
> +  --recursive\n\
> +	Copy the whole mount tree from <source> and apply the idmap to everyone at <target>.\n\n\
> +Examples:\n\
> +  - Create an idmapped mount of /source on /target with both ('b') uids and gids mapped:\n\
> +	mount-idmapped --map-mount b:0:10000:10000 /source /target\n\n\
> +  - Create an idmapped mount of /source on /target with uids ('u') and gids ('g') mapped separately:\n\
> +	mount-idmapped --map-mount u:0:10000:10000 g:0:20000:20000 /source /target\n\n\
> +";
> +	fprintf(stderr, "%s", text);
> +	_exit(EXIT_SUCCESS);
> +}
> +
> +#define exit_usage(format, ...)                         \
> +	({                                              \
> +		fprintf(stderr, format, ##__VA_ARGS__); \
> +		usage();                                \
> +	})
> +
> +#define exit_log(format, ...)                           \
> +	({                                              \
> +		fprintf(stderr, format, ##__VA_ARGS__); \
> +		exit(EXIT_FAILURE);                     \
> +	})
> +
> +static const struct option longopts[] = {
> +	{"map-mount",	required_argument,	0,	'a'},
> +	{"help",	no_argument,		0,	'c'},
> +	{"recursive",	no_argument,		0,	'd'},
> +	{ NULL,		0,			0,	0  },
> +};
> +
> +int main(int argc, char *argv[])
> +{
> +	int fd_userns = -EBADF;
> +	int index = 0;
> +	const char *source = NULL, *target = NULL;
> +	bool recursive = false;
> +	int fd_tree, new_argc, ret;
> +	char *const *new_argv;
> +
> +	list_init(&active_map);
> +	while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
> +		switch (ret) {
> +		case 'a':
> +			if (strnequal(optarg, "/proc", STRLITERALLEN("/proc/"))) {
> +				fd_userns = open(optarg, O_RDONLY | O_CLOEXEC);
> +				if (fd_userns < 0)
> +					exit_log("%m - Failed top open user namespace path %s\n", optarg);
> +				break;
> +			}
> +
> +			ret = parse_map(optarg);
> +			if (ret < 0)
> +				exit_log("Failed to parse idmaps for mount\n");
> +			break;
> +		case 'd':
> +			recursive = true;
> +			break;
> +		case 'c':
> +			/* fallthrough */
> +		default:
> +			usage();
> +		}
> +	}
> +
> +	new_argv = &argv[optind];
> +	new_argc = argc - optind;
> +	if (new_argc < 2)
> +		exit_usage("Missing source or target mountpoint\n\n");
> +	source = new_argv[0];
> +	target = new_argv[1];
> +
> +	fd_tree = sys_open_tree(-EBADF, source,
> +			        OPEN_TREE_CLONE |
> +			        OPEN_TREE_CLOEXEC |
> +			        AT_EMPTY_PATH |
> +			        (recursive ? AT_RECURSIVE : 0));
> +	if (fd_tree < 0) {
> +		exit_log("%m - Failed to open %s\n", source);
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	if (!list_empty(&active_map)) {
> +		struct mount_attr attr = {
> +			.attr_set = MOUNT_ATTR_IDMAP,
> +		};
> +
> +		attr.userns_fd = get_userns_fd_from_idmap(&active_map);
> +		if (attr.userns_fd < 0)
> +			exit_log("%m - Failed to create user namespace\n");
> +
> +		ret = sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH | AT_RECURSIVE,
> +					&attr, sizeof(attr));
> +		if (ret < 0)
> +			exit_log("%m - Failed to change mount attributes\n");
> +		close(attr.userns_fd);
> +	}
> +
> +	ret = sys_move_mount(fd_tree, "", -EBADF, target,
> +			     MOVE_MOUNT_F_EMPTY_PATH);
> +	if (ret < 0)
> +		exit_log("%m - Failed to attach mount to %s\n", target);
> +	close(fd_tree);
> +
> +	exit(EXIT_SUCCESS);
> +}
> diff --git a/src/idmapped-mounts/utils.c b/src/idmapped-mounts/utils.c
> index b27ba445..977443f1 100644
> --- a/src/idmapped-mounts/utils.c
> +++ b/src/idmapped-mounts/utils.c
> @@ -88,7 +88,7 @@ pid_t do_clone(int (*fn)(void *), void *arg, int flags)
>  #endif
>  }
>  
> -static int get_userns_fd_cb(void *data)
> +int get_userns_fd_cb(void *data)
>  {
>  	return kill(getpid(), SIGSTOP);
>  }
> diff --git a/src/idmapped-mounts/utils.h b/src/idmapped-mounts/utils.h
> index 93425731..efbf3bc3 100644
> --- a/src/idmapped-mounts/utils.h
> +++ b/src/idmapped-mounts/utils.h
> @@ -20,6 +20,7 @@
>  #include "missing.h"
>  
>  extern pid_t do_clone(int (*fn)(void *), void *arg, int flags);
> +extern int get_userns_fd_cb(void *data);
>  extern int get_userns_fd(unsigned long nsid, unsigned long hostid,
>  			 unsigned long range);
>  extern ssize_t read_nointr(int fd, void *buf, size_t count);
> diff --git a/tests/xfs/529 b/tests/xfs/529
> new file mode 100644
> index 00000000..67251818
> --- /dev/null
> +++ b/tests/xfs/529
> @@ -0,0 +1,373 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0+
> +#
> +# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
> +# All Rights Reserved.
> +#
> +# FS QA Test No. 529
> +#
> +# Exercise basic xfs_quota functionality (user/group/project quota)
> +# Use of "sync" mount option here is an attempt to get deterministic
> +# allocator behaviour.
> +#
> +seq=`basename $0`
> +seqres=$RESULT_DIR/$seq
> +echo "QA output created by $seq"
> +
> +here=`pwd`
> +tmp=/tmp/$$
> +status=1	# failure is the default!
> +trap "_cleanup; exit \$status" 0 1 2 3 15
> +
> +_cleanup()
> +{
> +	cd /
> +	rm -f $tmp.*

Seems we should do wipe_mounts in _cleanup as well, in case test exits
in the middle of the test with idmapped mount still mounted.

> +}
> +
> +# get standard environment, filters and checks
> +. ./common/rc
> +. ./common/filter
> +. ./common/quota
> +
> +# remove previous $seqres.full before test
> +rm -f $seqres.full
> +
> +# real QA test starts here
> +_supported_fs xfs
> +_require_idmapped_mounts
> +_require_scratch
> +_require_xfs_quota
> +_require_user fsgqa
> +_require_user fsgqa2
> +_require_group fsgqa
> +_require_group fsgqa2
> +
> +_scratch_mkfs_xfs >>$seqres.full 2>&1
> +
> +uqid=`id -u fsgqa`
> +gqid=`id -g fsgqa`
> +
> +uqid2=`id -u fsgqa2`
> +gqid2=`id -g fsgqa2`
> +
> +pqid=10
> +cat >$tmp.projects <<EOF
> +$pqid:$SCRATCH_MNT
> +EOF
> +
> +cat >$tmp.projid <<EOF
> +root:0
> +fsgqa:$pqid
> +EOF
> +
> +create_files_unmapped()
> +{
> +	local bs=$1
> +	local inum=$2
> +
> +	echo "Using type=$type id=$id" >> $seqres.full
> +
> +	for ((i=0; i<$((inum-1)); i++)); do
> +		_file_as_id $SCRATCH_MNT/unmapped/inode$i $id $type 1024 0
> +	done
> +
> +	_file_as_id $SCRATCH_MNT/unmapped/block $id $type $bs 1
> +}
> +
> +create_files_idmapped()
> +{
> +	local bs=$1
> +	local inum=$2
> +
> +	echo "Using type=$type id=$id2" >> $seqres.full
> +
> +	for ((i=0; i<$((inum-1)); i++)); do
> +		_file_as_id $SCRATCH_MNT/idmapped/inode$i $id2 $type 1024 0
> +	done
> +
> +	_file_as_id $SCRATCH_MNT/idmapped/block $id2 $type $bs 1
> +}
> +
> +clean_files()
> +{
> +	rm -rf $SCRATCH_MNT/unmapped 2>/dev/null
> +	rm -rf $SCRATCH_MNT/idmapped 2>/dev/null
> +	rm -rf $tmp.quot 2>/dev/null
> +	rm -rf $tmp.quota 2>/dev/null
> +}
> +
> +filter_quot()
> +{
> +	_filter_quota | grep -v "root \|\#0 " \
> +		| sed -e '/#[0-9]*/s/#[0-9]*/#ID/g'
> +}
> +
> +filter_report()
> +{
> +	_filter_quota | grep -v "^root \|^\#0 " \
> +		| sed -e '/^#[0-9]*/s/^#[0-9]*/#ID/g'
> +}
> +
> +filter_quota()
> +{
> +	_filter_quota | sed -e "/Disk quotas for/s/([0-9]*)/(ID)/g" \
> +			    -e "/Disk quotas for/s/#[0-9]*/#ID/g"
> +}
> +
> +filter_state()
> +{
> +	_filter_quota | sed -e "s/Inode: #[0-9]* (0 blocks, 0 extents)/Inode: #[INO] (0 blocks, 0 extents)/g" \
> +			    -e "s/Inode: #[0-9]* ([0-9]* blocks, [0-9]* extents)/Inode: #[INO] (X blocks, Y extents)/g" \
> +			    -e "/[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/s/ [0-9][0-9]:[0-9][0-9]:[0-9][0-9]//g" \
> +			    -e '/max warnings:/d'
> +}
> +
> +test_quot()
> +{
> +	local opt="$*"
> +
> +	echo "checking quot command (type=$type)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "quot -$type $opt -bi" $SCRATCH_MNT | filter_quot
> +}
> +
> +test_report()
> +{
> +	local opt="$*"
> +
> +	echo "checking report command (type=$type)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "report -$type $opt -bi" \
> +			$SCRATCH_MNT | filter_report
> +}
> +
> +test_quota()
> +{
> +	local opt="$*"
> +
> +	echo "checking quota command (type=$type)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "quota -$type $opt -bi $id" \
> +			$SCRATCH_MNT | filter_quota
> +}
> +
> +test_limit()
> +{
> +	local bs=$1
> +	local bh=$2
> +	local is=$3
> +	local ih=$4
> +
> +	echo "checking limit command (type=$type, bsoft=$bs, bhard=$bh, isoft=$is, ihard=$ih)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "limit -$type bsoft=$bs bhard=$bh fsgqa" \
> +			-c "limit -$type isoft=$is ihard=$ih fsgqa" \
> +			$SCRATCH_MNT
> +
> +	# let the timer day transition happen
> +	sleep 2
> +}
> +
> +test_timer()
> +{
> +	echo "checking timer command (type=$type)"
> +	# set 3days+1h for time won't become 2days soon
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "timer -$type -bi 73h" \
> +			$SCRATCH_MNT | _filter_scratch
> +}
> +
> +test_disable()
> +{
> +	echo "checking disable command (type=$type)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "disable -$type -v" \
> +			$SCRATCH_MNT | filter_state
> +}
> +
> +test_enable()
> +{
> +	echo "checking enable command (type=$type)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "enable -$type -v" $SCRATCH_MNT | filter_state
> +}
> +
> +test_off()
> +{
> +	echo "checking off command (type=$type)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "off -$type -v" $SCRATCH_MNT | _filter_scratch
> +}
> +
> +test_remove()
> +{
> +	echo "checking remove command (type=$type)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "remove -$type -v" \
> +			$SCRATCH_MNT | _filter_scratch
> +}
> +
> +test_state()
> +{
> +	echo "checking state command (type=$type)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "state -$type" $SCRATCH_MNT | filter_state
> +}
> +
> +test_dump()
> +{
> +	echo "checking dump command (type=$type)"
> +	rm -f $tmp.backup 2>>/dev/null
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "dump -$type -f $tmp.backup" \
> +			$SCRATCH_MNT | _filter_scratch
> +}
> +
> +test_restore()
> +{
> +	echo "checking restore command (type=$type)"
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +			-c "restore -$type -f $tmp.backup" \
> +			$SCRATCH_MNT | _filter_scratch
> +}
> +
> +_wipe_mounts()

Local functions don't need the leading "_" in function name.

> +{
> +	umount -l -q "${SCRATCH_MNT}/idmapped" >/dev/null 2>&1

$UMOUNT_PROG, and you already dump stderr and stdout to /dev/null, -q
seems not necessary.

> +	_scratch_unmount >/dev/null 2>&1
> +}
> +
> +_wipe_scratch()
> +{
> +	_wipe_mounts
> +	_scratch_mkfs_xfs >>$seqres.full 2>&1
> +}
> +
> +_qmount_idmapped()
> +{
> +	_wipe_mounts
> +	_try_scratch_mount || _fail "qmount failed"
> +
> +	mkdir -p "${SCRATCH_MNT}/unmapped"
> +	mkdir -p "${SCRATCH_MNT}/idmapped"
> +
> +	$here/src/idmapped-mounts/mount-idmapped --map-mount b:$id:$id2:1 --map-mount b:0:0:1 "$SCRATCH_MNT/unmapped" "$SCRATCH_MNT/idmapped" || _fail "mount-idmapped failed"
> +
> +	chmod ugo+rwx $SCRATCH_MNT
> +	chmod ugo+rwx $SCRATCH_MNT/unmapped
> +	chmod ugo+rwx $SCRATCH_MNT/idmapped
> +}
> +
> +test_xfs_quota()
> +{
> +	# init quota
> +	echo "init quota limit and timer, and dump it"
> +	if [ "$idmapped" -eq 1 ]; then
> +		echo "create_files_idmapped 1024k 15"; create_files_idmapped 1024k 15
> +	else
> +		echo "create_files_unmapped 1024k 15"; create_files_unmapped 1024k 15
> +	fi
> +	echo "quota remount"; _qmount_idmapped
> +	echo ; test_quot
> +	echo ; test_timer
> +	echo ; test_limit 512k 2048k 10 20
> +	echo ; test_dump
> +
> +	# report options test
> +	echo "report options test"
> +	echo ; test_report
> +	echo "-N option"; test_report -N
> +	echo "-L -U options"; test_report -L $id -U $id
> +	echo "-t option"; test_report -t
> +	echo "-n option"; test_report -n
> +	echo "-h option"; test_report -h
> +
> +	# quot options test
> +	echo "quot options test"
> +	echo ; test_quot
> +	echo "-f option"; test_quot -f $tmp.quot
> +	cat $tmp.quot | filter_quot
> +	echo "-n option"; test_quot -n
> +
> +	# quota options test
> +	echo ; test_quota
> +	echo "-f option"; test_quota -f $tmp.quota
> +	cat $tmp.quota | filter_quota
> +	echo "-N option"; test_quota -N
> +	echo "-n option"; test_quota -n
> +	echo "-h option"; test_quota -h
> +
> +	# disable/enable test
> +	echo "disable quota"
> +	echo ; test_disable
> +	echo ; test_report -N
> +	echo "expect a remove error at here"; test_remove
> +	echo ; test_enable
> +	echo ; test_report -N
> +
> +	# off and remove test
> +	echo "off and remove test"
> +	echo ; test_limit 100m 100m 100 100
> +	echo ; test_quota -N
> +	echo ; test_off
> +	echo ; test_state
> +	echo ; test_remove
> +	echo ; test_report -N
> +	echo "quota remount"; _qmount_idmapped
> +	echo ; test_report -N
> +
> +	# restore test
> +	echo "restore quota"
> +	echo ; test_restore
> +	echo ; test_report -N
> +	echo ; test_state
> +	echo "cleanup files"; clean_files
> +}
> +
> +echo "----------------------- uquota,sync,unmapped ---------------------------"
> +_wipe_scratch
> +_qmount_option "uquota,sync"
> +type=u
> +id=$uqid
> +id2=$uqid2
> +idmapped=0
> +_qmount_idmapped
> +test_xfs_quota
> +
> +echo "----------------------- uquota,sync,idmapped ---------------------------"
> +_wipe_scratch
> +_qmount_option "uquota,sync"
> +type=u
> +id=$uqid
> +id2=$uqid2
> +idmapped=1
> +_qmount_idmapped
> +test_xfs_quota
> +
> +echo "----------------------- gquota,sync,unmapped ---------------------------"
> +_wipe_scratch
> +_qmount_option "gquota,sync"
> +type=g
> +id=$gqid
> +id2=$gqid2
> +idmapped=0
> +_qmount_idmapped
> +test_xfs_quota
> +
> +echo "----------------------- gquota,sync,idmapped ---------------------------"
> +_wipe_scratch
> +_qmount_option "gquota,sync"
> +type=g
> +id=$gqid
> +id2=$gqid2
> +idmapped=1
> +_qmount_idmapped
> +test_xfs_quota
> +
> +umount -l -q "${SCRATCH_MNT}/idmapped" >/dev/null 2>&1
> +_scratch_unmount

Could use wipe_mounts here?

Thanks,
Eryu

> +
> +# success, all done
> +status=0
> +exit
> diff --git a/tests/xfs/529.out b/tests/xfs/529.out
> new file mode 100644
> index 00000000..7d2cd96d
> --- /dev/null
> +++ b/tests/xfs/529.out
> @@ -0,0 +1,657 @@
> +QA output created by 529
> +----------------------- uquota,sync,unmapped ---------------------------
> +init quota limit and timer, and dump it
> +create_files_unmapped 1024k 15
> +quota remount
> +
> +checking quot command (type=u)
> +SCRATCH_DEV (SCRATCH_MNT) User:
> + 1024 15 fsgqa 
> +
> +checking timer command (type=u)
> +
> +checking limit command (type=u, bsoft=512k, bhard=2048k, isoft=10, ihard=20)
> +
> +checking dump command (type=u)
> +report options test
> +
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-N option
> +checking report command (type=u)
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-L -U options
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-t option
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-n option
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-h option
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/Grace 
> +---------- --------------------------------- --------------------------------- 
> +fsgqa 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days]
> +
> +quot options test
> +
> +checking quot command (type=u)
> +SCRATCH_DEV (SCRATCH_MNT) User:
> + 1024 15 fsgqa 
> +-f option
> +checking quot command (type=u)
> +SCRATCH_DEV (SCRATCH_MNT) User:
> + 1024 15 fsgqa 
> +-n option
> +checking quot command (type=u)
> +SCRATCH_DEV (SCRATCH_MNT) User:
> + 1024 15 #ID 
> +
> +checking quota command (type=u)
> +Disk quotas for User fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-f option
> +checking quota command (type=u)
> +Disk quotas for User fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-N option
> +checking quota command (type=u)
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-n option
> +checking quota command (type=u)
> +Disk quotas for User #ID (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-h option
> +checking quota command (type=u)
> +Disk quotas for User fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +disable quota
> +
> +checking disable command (type=u)
> +User quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: OFF
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [3 days]
> +Inodes grace time: [3 days]
> +Realtime Blocks grace time: [7 days]
> +
> +checking report command (type=u)
> +fsgqa 1024 512 2048 00 [--------] 15 10 20 00 [--------]
> +
> +expect a remove error at here
> +checking remove command (type=u)
> +XFS_QUOTARM: Invalid argument
> +
> +checking enable command (type=u)
> +User quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: ON
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [3 days]
> +Inodes grace time: [3 days]
> +Realtime Blocks grace time: [7 days]
> +
> +checking report command (type=u)
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +off and remove test
> +
> +checking limit command (type=u, bsoft=100m, bhard=100m, isoft=100, ihard=100)
> +
> +checking quota command (type=u)
> +SCRATCH_DEV 1024 102400 102400 00 [--------] 15 100 100 00 [--------] SCRATCH_MNT
> +
> +checking off command (type=u)
> +User quota are not enabled on SCRATCH_DEV
> +
> +checking state command (type=u)
> +
> +checking remove command (type=u)
> +User quota are not enabled on SCRATCH_DEV
> +
> +checking report command (type=u)
> +
> +quota remount
> +
> +checking report command (type=u)
> +fsgqa 1024 0 0 00 [--------] 15 0 0 00 [--------]
> +
> +restore quota
> +
> +checking restore command (type=u)
> +
> +checking report command (type=u)
> +fsgqa 1024 512 2048 00 [7 days] 15 10 20 00 [7 days]
> +
> +
> +checking state command (type=u)
> +User quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: ON
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [7 days]
> +Inodes grace time: [7 days]
> +Realtime Blocks grace time: [7 days]
> +cleanup files
> +----------------------- uquota,sync,idmapped ---------------------------
> +init quota limit and timer, and dump it
> +create_files_idmapped 1024k 15
> +quota remount
> +
> +checking quot command (type=u)
> +SCRATCH_DEV (SCRATCH_MNT) User:
> + 1024 15 fsgqa 
> +
> +checking timer command (type=u)
> +
> +checking limit command (type=u, bsoft=512k, bhard=2048k, isoft=10, ihard=20)
> +
> +checking dump command (type=u)
> +report options test
> +
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-N option
> +checking report command (type=u)
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-L -U options
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-t option
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-n option
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-h option
> +checking report command (type=u)
> +User quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +User ID Used Soft Hard Warn/Grace Used Soft Hard Warn/Grace 
> +---------- --------------------------------- --------------------------------- 
> +fsgqa 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days]
> +
> +quot options test
> +
> +checking quot command (type=u)
> +SCRATCH_DEV (SCRATCH_MNT) User:
> + 1024 15 fsgqa 
> +-f option
> +checking quot command (type=u)
> +SCRATCH_DEV (SCRATCH_MNT) User:
> + 1024 15 fsgqa 
> +-n option
> +checking quot command (type=u)
> +SCRATCH_DEV (SCRATCH_MNT) User:
> + 1024 15 #ID 
> +
> +checking quota command (type=u)
> +Disk quotas for User fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-f option
> +checking quota command (type=u)
> +Disk quotas for User fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-N option
> +checking quota command (type=u)
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-n option
> +checking quota command (type=u)
> +Disk quotas for User #ID (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-h option
> +checking quota command (type=u)
> +Disk quotas for User fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +disable quota
> +
> +checking disable command (type=u)
> +User quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: OFF
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [3 days]
> +Inodes grace time: [3 days]
> +Realtime Blocks grace time: [7 days]
> +
> +checking report command (type=u)
> +fsgqa 1024 512 2048 00 [--------] 15 10 20 00 [--------]
> +
> +expect a remove error at here
> +checking remove command (type=u)
> +XFS_QUOTARM: Invalid argument
> +
> +checking enable command (type=u)
> +User quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: ON
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [3 days]
> +Inodes grace time: [3 days]
> +Realtime Blocks grace time: [7 days]
> +
> +checking report command (type=u)
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +off and remove test
> +
> +checking limit command (type=u, bsoft=100m, bhard=100m, isoft=100, ihard=100)
> +
> +checking quota command (type=u)
> +SCRATCH_DEV 1024 102400 102400 00 [--------] 15 100 100 00 [--------] SCRATCH_MNT
> +
> +checking off command (type=u)
> +User quota are not enabled on SCRATCH_DEV
> +
> +checking state command (type=u)
> +
> +checking remove command (type=u)
> +User quota are not enabled on SCRATCH_DEV
> +
> +checking report command (type=u)
> +
> +quota remount
> +
> +checking report command (type=u)
> +fsgqa 1024 0 0 00 [--------] 15 0 0 00 [--------]
> +
> +restore quota
> +
> +checking restore command (type=u)
> +
> +checking report command (type=u)
> +fsgqa 1024 512 2048 00 [7 days] 15 10 20 00 [7 days]
> +
> +
> +checking state command (type=u)
> +User quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: ON
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [7 days]
> +Inodes grace time: [7 days]
> +Realtime Blocks grace time: [7 days]
> +cleanup files
> +----------------------- gquota,sync,unmapped ---------------------------
> +init quota limit and timer, and dump it
> +create_files_unmapped 1024k 15
> +quota remount
> +
> +checking quot command (type=g)
> +SCRATCH_DEV (SCRATCH_MNT) Group:
> + 1024 15 fsgqa 
> +
> +checking timer command (type=g)
> +
> +checking limit command (type=g, bsoft=512k, bhard=2048k, isoft=10, ihard=20)
> +
> +checking dump command (type=g)
> +report options test
> +
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-N option
> +checking report command (type=g)
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-L -U options
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-t option
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-n option
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-h option
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/Grace 
> +---------- --------------------------------- --------------------------------- 
> +fsgqa 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days]
> +
> +quot options test
> +
> +checking quot command (type=g)
> +SCRATCH_DEV (SCRATCH_MNT) Group:
> + 1024 15 fsgqa 
> +-f option
> +checking quot command (type=g)
> +SCRATCH_DEV (SCRATCH_MNT) Group:
> + 1024 15 fsgqa 
> +-n option
> +checking quot command (type=g)
> +SCRATCH_DEV (SCRATCH_MNT) Group:
> + 1024 15 #ID 
> +
> +checking quota command (type=g)
> +Disk quotas for Group fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-f option
> +checking quota command (type=g)
> +Disk quotas for Group fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-N option
> +checking quota command (type=g)
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-n option
> +checking quota command (type=g)
> +Disk quotas for Group #ID (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-h option
> +checking quota command (type=g)
> +Disk quotas for Group fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +disable quota
> +
> +checking disable command (type=g)
> +Group quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: OFF
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [3 days]
> +Inodes grace time: [3 days]
> +Realtime Blocks grace time: [7 days]
> +
> +checking report command (type=g)
> +fsgqa 1024 512 2048 00 [--------] 15 10 20 00 [--------]
> +
> +expect a remove error at here
> +checking remove command (type=g)
> +XFS_QUOTARM: Invalid argument
> +
> +checking enable command (type=g)
> +Group quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: ON
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [3 days]
> +Inodes grace time: [3 days]
> +Realtime Blocks grace time: [7 days]
> +
> +checking report command (type=g)
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +off and remove test
> +
> +checking limit command (type=g, bsoft=100m, bhard=100m, isoft=100, ihard=100)
> +
> +checking quota command (type=g)
> +SCRATCH_DEV 1024 102400 102400 00 [--------] 15 100 100 00 [--------] SCRATCH_MNT
> +
> +checking off command (type=g)
> +Group quota are not enabled on SCRATCH_DEV
> +
> +checking state command (type=g)
> +
> +checking remove command (type=g)
> +Group quota are not enabled on SCRATCH_DEV
> +
> +checking report command (type=g)
> +
> +quota remount
> +
> +checking report command (type=g)
> +fsgqa 1024 0 0 00 [--------] 15 0 0 00 [--------]
> +
> +restore quota
> +
> +checking restore command (type=g)
> +
> +checking report command (type=g)
> +fsgqa 1024 512 2048 00 [7 days] 15 10 20 00 [7 days]
> +
> +
> +checking state command (type=g)
> +Group quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: ON
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [7 days]
> +Inodes grace time: [7 days]
> +Realtime Blocks grace time: [7 days]
> +cleanup files
> +----------------------- gquota,sync,idmapped ---------------------------
> +init quota limit and timer, and dump it
> +create_files_idmapped 1024k 15
> +quota remount
> +
> +checking quot command (type=g)
> +SCRATCH_DEV (SCRATCH_MNT) Group:
> + 1024 15 fsgqa 
> +
> +checking timer command (type=g)
> +
> +checking limit command (type=g, bsoft=512k, bhard=2048k, isoft=10, ihard=20)
> +
> +checking dump command (type=g)
> +report options test
> +
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-N option
> +checking report command (type=g)
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-L -U options
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-t option
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-n option
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/ Grace 
> +---------- -------------------------------------------------- -------------------------------------------------- 
> +#ID 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +-h option
> +checking report command (type=g)
> +Group quota on SCRATCH_MNT (SCRATCH_DEV)
> + Blocks Inodes 
> +Group ID Used Soft Hard Warn/Grace Used Soft Hard Warn/Grace 
> +---------- --------------------------------- --------------------------------- 
> +fsgqa 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days]
> +
> +quot options test
> +
> +checking quot command (type=g)
> +SCRATCH_DEV (SCRATCH_MNT) Group:
> + 1024 15 fsgqa 
> +-f option
> +checking quot command (type=g)
> +SCRATCH_DEV (SCRATCH_MNT) Group:
> + 1024 15 fsgqa 
> +-n option
> +checking quot command (type=g)
> +SCRATCH_DEV (SCRATCH_MNT) Group:
> + 1024 15 #ID 
> +
> +checking quota command (type=g)
> +Disk quotas for Group fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-f option
> +checking quota command (type=g)
> +Disk quotas for Group fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-N option
> +checking quota command (type=g)
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-n option
> +checking quota command (type=g)
> +Disk quotas for Group #ID (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1024 512 2048 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +-h option
> +checking quota command (type=g)
> +Disk quotas for Group fsgqa (ID)
> +Filesystem Blocks Quota Limit Warn/Time Files Quota Limit Warn/Time Mounted on
> +SCRATCH_DEV 1M 512K 2M 00 [3 days] 15 10 20 00 [3 days] SCRATCH_MNT
> +disable quota
> +
> +checking disable command (type=g)
> +Group quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: OFF
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [3 days]
> +Inodes grace time: [3 days]
> +Realtime Blocks grace time: [7 days]
> +
> +checking report command (type=g)
> +fsgqa 1024 512 2048 00 [--------] 15 10 20 00 [--------]
> +
> +expect a remove error at here
> +checking remove command (type=g)
> +XFS_QUOTARM: Invalid argument
> +
> +checking enable command (type=g)
> +Group quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: ON
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [3 days]
> +Inodes grace time: [3 days]
> +Realtime Blocks grace time: [7 days]
> +
> +checking report command (type=g)
> +fsgqa 1024 512 2048 00 [3 days] 15 10 20 00 [3 days]
> +
> +off and remove test
> +
> +checking limit command (type=g, bsoft=100m, bhard=100m, isoft=100, ihard=100)
> +
> +checking quota command (type=g)
> +SCRATCH_DEV 1024 102400 102400 00 [--------] 15 100 100 00 [--------] SCRATCH_MNT
> +
> +checking off command (type=g)
> +Group quota are not enabled on SCRATCH_DEV
> +
> +checking state command (type=g)
> +
> +checking remove command (type=g)
> +Group quota are not enabled on SCRATCH_DEV
> +
> +checking report command (type=g)
> +
> +quota remount
> +
> +checking report command (type=g)
> +fsgqa 1024 0 0 00 [--------] 15 0 0 00 [--------]
> +
> +restore quota
> +
> +checking restore command (type=g)
> +
> +checking report command (type=g)
> +fsgqa 1024 512 2048 00 [7 days] 15 10 20 00 [7 days]
> +
> +
> +checking state command (type=g)
> +Group quota state on SCRATCH_MNT (SCRATCH_DEV)
> + Accounting: ON
> + Enforcement: ON
> + Inode: #[INO] (X blocks, Y extents)
> +Blocks grace time: [7 days]
> +Inodes grace time: [7 days]
> +Realtime Blocks grace time: [7 days]
> +cleanup files
> diff --git a/tests/xfs/group b/tests/xfs/group
> index 288b916d..fb4599ca 100644
> --- a/tests/xfs/group
> +++ b/tests/xfs/group
> @@ -505,3 +505,4 @@
>  526 auto quick mkfs
>  527 auto quick quota
>  528 auto quick rw realtime
> +529 auto quick quota
> -- 
> 2.27.0

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 4/4] xfs/530: quotas on idmapped mounts
  2021-03-16 10:36 ` [PATCH v9 4/4] xfs/530: quotas on " Christian Brauner
  2021-03-18  6:24   ` Christoph Hellwig
@ 2021-03-21 14:51   ` Eryu Guan
  2021-03-22 12:25     ` Christian Brauner
  1 sibling, 1 reply; 22+ messages in thread
From: Eryu Guan @ 2021-03-21 14:51 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Tue, Mar 16, 2021 at 11:36:27AM +0100, Christian Brauner wrote:
> This is basically a re-implementation of xfs/050 but each file creation
> call is done through an idmapped mount which verifies that the semantics
> are identical even when the mount is idmapped.
> 
> Cc: Christoph Hellwig <hch@lst.de>
> Cc: Darrick J. Wong <djwong@kernel.org>
> Cc: fstests@vger.kernel.org
> Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
> ---
> /* v1 */
> patch not present
> 
> /* v2 */
> patch not present
> 
> /* v3 */
> patch not present
> 
> /* v4 */
> patch not present
> 
> /* v5 */
> patch not present
> 
> /* v6 */
> patch not present
> 
> /* v7 */
> patch not present
> 
> /* v8 */
> patch introduced
> 
> /* v9 */
> - Christian Brauner <christian.brauner@ubuntu.com>:
>   - Rebased onto current master.
> ---
>  tests/xfs/530     | 274 ++++++++++++++++++++++++++++++++++++++++++++++
>  tests/xfs/530.out | 129 ++++++++++++++++++++++
>  tests/xfs/group   |   1 +
>  3 files changed, 404 insertions(+)
>  create mode 100644 tests/xfs/530
>  create mode 100644 tests/xfs/530.out
> 
> diff --git a/tests/xfs/530 b/tests/xfs/530
> new file mode 100644
> index 00000000..9e10c40d
> --- /dev/null
> +++ b/tests/xfs/530
> @@ -0,0 +1,274 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0+
> +#
> +# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
> +# All Rights Reserved.
> +#
> +# FS QA Test No. 530
> +#
> +# Exercises basic XFS quota functionality
> +#       uquota, gquota, uqnoenforce, gqnoenforce
> +#
> +seq=`basename $0`
> +seqres=$RESULT_DIR/$seq
> +echo "QA output created by $seq"
> +
> +here=`pwd`
> +tmp=/tmp/$$
> +status=1	# failure is the default!
> +
> +# get standard environment, filters and checks
> +. ./common/rc
> +. ./common/filter
> +. ./common/quota
> +
> +_cleanup()
> +{
> +	cd /
> +	_scratch_unmount 2>/dev/null
> +	rm -f $tmp.*
> +}
> +trap "_cleanup; exit \$status" 0 1 2 3 15
> +
> +# real QA test starts here
> +_supported_fs xfs
> +
> +cp /dev/null $seqres.full
> +chmod a+rwx $seqres.full	# arbitrary users will write here
> +
> +_require_scratch
> +_require_xfs_quota
> +_require_user fsgqa
> +
> +_scratch_mkfs >/dev/null 2>&1
> +_scratch_mount
> +bsize=$(_get_file_block_size $SCRATCH_MNT)
> +_scratch_unmount
> +
> +bsoft=$(( 200 * $bsize ))
> +bhard=$(( 1000 * $bsize ))
> +isoft=4
> +ihard=10
> +
> +_filter_report()

Seems this is mostly copied from _filter_quota_report(), just remove the
$id and $bsize check in the beginning, is it possible to re-use
_filter_quota_report?

> +{
> +	tr -s '[:space:]' | \
> +	perl -npe '
> +		s/^\#'$id' /[NAME] /g;
> +		s/^\#0 \d+ /[ROOT] 0 /g;
> +		s/6 days/7 days/g' |
> +	perl -npe '
> +		$val = 0;
> +		if ($ENV{'LARGE_SCRATCH_DEV'}) {
> +			$val = $ENV{'NUM_SPACE_FILES'};
> +		}
> +		s/(^\[ROOT\] \S+ \S+ \S+ \S+ \[--------\] )(\S+)/$1@{[$2 - $val]}/g' |
> +	sed -e 's/ 65535 \[--------\]/ 00 \[--------\]/g' |
> +	perl -npe '
> +		s|^(.*?) (\d+) (\d+) (\d+)|$1 @{[$2 * 1024 /'$bsize']} @{[$3 * 1024 /'$bsize']} @{[$4 * 1024 /'$bsize']}|'
> +}
> +
> +# The actual point at which limit enforcement takes place for the
> +# hard block limit is variable depending on filesystem blocksize,
> +# and iosize.  What we want to test is that the limit is enforced
> +# (ie. blksize less than limit but not unduly less - ~85% is kind)
> +# nowadays we actually get much closer to the limit before EDQUOT.
> +#
> +_filter_and_check_blks()
> +{
> +	perl -npe '
> +		if (/^\#'$id'\s+(\d+)/ && '$enforce') {
> +			$maximum = '$bhard';
> +			$minimum = '$bhard' * 85/100;
> +			$used = $1 * 1024;
> +			if (($used < $minimum || $used > $maximum) && '$noextsz') {
> +				printf(" URK %d: %d is out of range! [%d,%d]\n",
> +					'$id', $used, $minimum, $maximum);
> +			}
> +			s/^(\#'$id'\s+)(\d+)/\1 =OK=/g;
> +		}
> +	' | _filter_report
> +}
> +
> +_qsetup()
> +{
> +	opt=$1
> +	enforce=0
> +	if [ $opt = "u" -o $opt = "uno" ]; then
> +		type=u
> +		eval `_choose_uid`
> +	elif [ $opt = "g" -o $opt = "gno" ]; then
> +		type=g
> +		eval `_choose_gid`
> +	elif [ $opt = "p" -o $opt = "pno" ]; then
> +		type=p
> +		eval `_choose_prid`
> +	fi
> +	[ $opt = "u" -o $opt = "g" -o $opt = "p" ] && enforce=1
> +
> +	echo "Using type=$type id=$id" >> $seqres.full
> +}

And above two functions are copied from xfs/050 and xfs/299, could you
please re-factor them into a common helper so they could be shared by
tests?

> +
> +_mount_idmapped()

Seems this could be made into a generic _scratch_mount_idmapped()
helper.

> +{
> +	if [ "$type" == "u" ]; then
> +		# This means root will be able to create files as uid %id in
> +		# the underlying filesystem by going through the idmapped mount.
> +		$here/src/idmapped-mounts/mount-idmapped --map-mount u:0:$id:1 \
> +							 --map-mount u:$id:0:1 \
> +							 --map-mount g:0:0:1 \
> +							 "$SCRATCH_MNT" "$SCRATCH_MNT" || _fail "mount-idmapped failed"
> +	else
> +		# This means root will be able to create files as gid %id in
> +		# the underlying filesystem by going through the idmapped mount.
> +		$here/src/idmapped-mounts/mount-idmapped --map-mount g:0:$id:1 \
> +							 --map-mount g:$id:0:1 \
> +							 --map-mount u:0:0:1 \
> +							 "$SCRATCH_MNT" "$SCRATCH_MNT" || _fail "mount-idmapped failed"
> +	fi
> +}
> +
> +_umount_idmapped()

And turn this into _scratch_umount_idmapped()

> +{
> +	# ls -al "$SCRATCH_MNT"

Could be removed?

> +	umount -l -q "${SCRATCH_MNT}" >/dev/null 2>&1

s/umount/$UMOUNT_PROG/. And it'd be good to add some comments to desribe
why lazy umount is needed.

> +	# ls -al "$SCRATCH_MNT"

Could be removed?

> +}
> +
> +_exercise()

Remove the leading "_" for local functions.

Thanks,
Eryu

> +{
> +	_scratch_mkfs_xfs | _filter_mkfs 2>$tmp.mkfs
> +	cat $tmp.mkfs >>$seqres.full
> +
> +	# keep the blocksize and data size for dd later
> +	. $tmp.mkfs
> +
> +	_qmount
> +
> +	# Figure out whether we're doing large allocations
> +	# (bail out if they're so large they stuff the test up)
> +	_test_inode_flag extsz-inherit $SCRATCH_MNT
> +	noextsz=$?
> +	extsize=`_test_inode_extsz $SCRATCH_MNT`
> +	[ $extsize -ge 512000 ] && \
> +		_notrun "Extent size hint is too large ($extsize bytes)"
> +
> +	_qsetup $1
> +
> +	echo "Using type=$type id=$id" >>$seqres.full
> +
> +	$XFS_QUOTA_PROG -x -c "warn -$type 65535 -d" $SCRATCH_DEV
> +
> +	echo
> +	echo "*** report no quota settings" | tee -a $seqres.full
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +		-c "repquota -birnN -$type" $SCRATCH_DEV |
> +		_filter_report | LC_COLLATE=POSIX sort -ru
> +
> +	echo
> +	echo "*** report initial settings" | tee -a $seqres.full
> +	_mount_idmapped
> +	_file_as_id $SCRATCH_MNT/initme 0 $type 1024 0
> +	_umount_idmapped
> +	echo "ls -l $SCRATCH_MNT" >>$seqres.full
> +	ls -l $SCRATCH_MNT >>$seqres.full
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $temp.projid -x \
> +		-c "limit -$type bsoft=${bsoft} bhard=${bhard} $id" \
> +		-c "limit -$type isoft=$isoft ihard=$ihard $id" \
> +		$SCRATCH_DEV
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +		-c "repquota -birnN -$type" $SCRATCH_DEV |
> +		_filter_report | LC_COLLATE=POSIX sort -ru
> +
> +	echo
> +	echo "*** push past the soft inode limit" | tee -a $seqres.full
> +	_mount_idmapped
> +	_file_as_id $SCRATCH_MNT/softie1 0 $type 1024 0
> +	_file_as_id $SCRATCH_MNT/softie2 0 $type 1024 0
> +	_file_as_id $SCRATCH_MNT/softie3 0 $type 1024 0
> +	_file_as_id $SCRATCH_MNT/softie4 0 $type 1024 0
> +	_umount_idmapped
> +	_qmount
> +	$XFS_QUOTA_PROG -x -c "warn -i -$type 0 $id" $SCRATCH_DEV
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +		-c "repquota -birnN -$type" $SCRATCH_DEV |
> +		_filter_report | LC_COLLATE=POSIX sort -ru
> +
> +	echo
> +	echo "*** push past the soft block limit" | tee -a $seqres.full
> +	_mount_idmapped
> +	_file_as_id $SCRATCH_MNT/softie 0 $type $bsize 300
> +	_umount_idmapped
> +	_qmount
> +	$XFS_QUOTA_PROG -x -c "warn -i -$type 0 $id" \
> +		-c "warn -b -$type 0 $id" $SCRATCH_DEV
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +		-c "repquota -birnN -$type" $SCRATCH_DEV |
> +		_filter_report | LC_COLLATE=POSIX sort -ru
> +
> +	echo
> +	# Note: for quota accounting (not enforcement), EDQUOT is not expected
> +	echo "*** push past the hard inode limit (expect EDQUOT)" | tee -a $seqres.full
> +	for i in 1 2 3 4 5 6 7 8 9 10 11 12
> +	do
> +		_mount_idmapped
> +		_file_as_id $SCRATCH_MNT/hard$i 0 $type 1024 0
> +		_umount_idmapped
> +	done
> +	_qmount
> +	$XFS_QUOTA_PROG -x  -c "warn -b -$type 0 $id" \
> +		-c "warn -i -$type 0 $id" $SCRATCH_DEV
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +		-c "repquota -birnN -$type" $SCRATCH_DEV |
> +		_filter_report | LC_COLLATE=POSIX sort -ru
> +
> +	echo
> +	# Note: for quota accounting (not enforcement), EDQUOT is not expected
> +	echo "*** push past the hard block limit (expect EDQUOT)" | tee -a $seqres.full
> +	_mount_idmapped
> +	_file_as_id $SCRATCH_MNT/softie 0 $type $bsize 1200
> +	_umount_idmapped
> +	echo "ls -l $SCRATCH_MNT" >>$seqres.full
> +	ls -l $SCRATCH_MNT >>$seqres.full
> +	_qmount
> +	$XFS_QUOTA_PROG -x -c "warn -b -$type 0 $id" $SCRATCH_DEV
> +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> +		-c "repquota -birnN -$type" $SCRATCH_DEV |
> +		_filter_and_check_blks | LC_COLLATE=POSIX sort -ru
> +
> +	echo
> +	echo "*** unmount"
> +	_scratch_unmount
> +
> +}
> +
> +cat >$tmp.projects <<EOF
> +1:$SCRATCH_MNT
> +EOF
> +
> +cat >$tmp.projid <<EOF
> +root:0
> +scrach:1
> +EOF
> +
> +projid_file="$tmp.projid"
> +
> +echo "*** user"
> +_qmount_option "uquota"
> +_exercise u
> +
> +echo "*** group"
> +_qmount_option "gquota"
> +_exercise g
> +
> +echo "*** uqnoenforce"
> +_qmount_option "uqnoenforce"
> +_exercise uno
> +
> +echo "*** gqnoenforce"
> +_qmount_option "gqnoenforce"
> +_exercise gno
> +
> +# success, all done
> +status=0
> +exit
> diff --git a/tests/xfs/530.out b/tests/xfs/530.out
> new file mode 100644
> index 00000000..39266b38
> --- /dev/null
> +++ b/tests/xfs/530.out
> @@ -0,0 +1,129 @@
> +QA output created by 530
> +*** user
> +meta-data=DDEV isize=XXX agcount=N, agsize=XXX blks
> +data     = bsize=XXX blocks=XXX, imaxpct=PCT
> +         = sunit=XXX swidth=XXX, unwritten=X
> +naming   =VERN bsize=XXX
> +log      =LDEV bsize=XXX blocks=XXX
> +realtime =RDEV extsz=XXX blocks=XXX, rtextents=XXX
> +
> +*** report no quota settings
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +
> +*** report initial settings
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 0 200 1000 00 [--------] 1 4 10 00 [--------] 0 0 0 00 [--------]
> +
> +*** push past the soft inode limit
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 0 200 1000 00 [--------] 5 4 10 00 [7 days] 0 0 0 00 [--------]
> +
> +*** push past the soft block limit
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 300 200 1000 00 [7 days] 6 4 10 00 [7 days] 0 0 0 00 [--------]
> +
> +*** push past the hard inode limit (expect EDQUOT)
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 300 200 1000 00 [7 days] 10 4 10 00 [7 days] 0 0 0 00 [--------]
> +
> +*** push past the hard block limit (expect EDQUOT)
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] =OK= 200 1000 0 [7 days] 10 4 10 00 [7 days] 0 0 0 00 [--------]
> +
> +*** unmount
> +*** group
> +meta-data=DDEV isize=XXX agcount=N, agsize=XXX blks
> +data     = bsize=XXX blocks=XXX, imaxpct=PCT
> +         = sunit=XXX swidth=XXX, unwritten=X
> +naming   =VERN bsize=XXX
> +log      =LDEV bsize=XXX blocks=XXX
> +realtime =RDEV extsz=XXX blocks=XXX, rtextents=XXX
> +
> +*** report no quota settings
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +
> +*** report initial settings
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 0 200 1000 00 [--------] 1 4 10 00 [--------] 0 0 0 00 [--------]
> +
> +*** push past the soft inode limit
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 0 200 1000 00 [--------] 5 4 10 00 [7 days] 0 0 0 00 [--------]
> +
> +*** push past the soft block limit
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 300 200 1000 00 [7 days] 6 4 10 00 [7 days] 0 0 0 00 [--------]
> +
> +*** push past the hard inode limit (expect EDQUOT)
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 300 200 1000 00 [7 days] 10 4 10 00 [7 days] 0 0 0 00 [--------]
> +
> +*** push past the hard block limit (expect EDQUOT)
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] =OK= 200 1000 0 [7 days] 10 4 10 00 [7 days] 0 0 0 00 [--------]
> +
> +*** unmount
> +*** uqnoenforce
> +meta-data=DDEV isize=XXX agcount=N, agsize=XXX blks
> +data     = bsize=XXX blocks=XXX, imaxpct=PCT
> +         = sunit=XXX swidth=XXX, unwritten=X
> +naming   =VERN bsize=XXX
> +log      =LDEV bsize=XXX blocks=XXX
> +realtime =RDEV extsz=XXX blocks=XXX, rtextents=XXX
> +
> +*** report no quota settings
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +
> +*** report initial settings
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 0 200 1000 00 [--------] 1 4 10 00 [--------] 0 0 0 00 [--------]
> +
> +*** push past the soft inode limit
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 0 200 1000 00 [--------] 5 4 10 00 [--------] 0 0 0 00 [--------]
> +
> +*** push past the soft block limit
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 300 200 1000 00 [--------] 6 4 10 00 [--------] 0 0 0 00 [--------]
> +
> +*** push past the hard inode limit (expect EDQUOT)
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 300 200 1000 00 [--------] 18 4 10 00 [--none--] 0 0 0 00 [--------]
> +
> +*** push past the hard block limit (expect EDQUOT)
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 1200 200 1000 00 [--none--] 18 4 10 00 [--none--] 0 0 0 00 [--------]
> +
> +*** unmount
> +*** gqnoenforce
> +meta-data=DDEV isize=XXX agcount=N, agsize=XXX blks
> +data     = bsize=XXX blocks=XXX, imaxpct=PCT
> +         = sunit=XXX swidth=XXX, unwritten=X
> +naming   =VERN bsize=XXX
> +log      =LDEV bsize=XXX blocks=XXX
> +realtime =RDEV extsz=XXX blocks=XXX, rtextents=XXX
> +
> +*** report no quota settings
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +
> +*** report initial settings
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 0 200 1000 00 [--------] 1 4 10 00 [--------] 0 0 0 00 [--------]
> +
> +*** push past the soft inode limit
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 0 200 1000 00 [--------] 5 4 10 00 [--------] 0 0 0 00 [--------]
> +
> +*** push past the soft block limit
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 300 200 1000 00 [--------] 6 4 10 00 [--------] 0 0 0 00 [--------]
> +
> +*** push past the hard inode limit (expect EDQUOT)
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 300 200 1000 00 [--------] 18 4 10 00 [--none--] 0 0 0 00 [--------]
> +
> +*** push past the hard block limit (expect EDQUOT)
> +[ROOT] 0 0 0 00 [--------] 3 0 0 00 [--------] 0 0 0 00 [--------]
> +[NAME] 1200 200 1000 00 [--none--] 18 4 10 00 [--none--] 0 0 0 00 [--------]
> +
> +*** unmount
> diff --git a/tests/xfs/group b/tests/xfs/group
> index fb4599ca..a536d01a 100644
> --- a/tests/xfs/group
> +++ b/tests/xfs/group
> @@ -506,3 +506,4 @@
>  527 auto quick quota
>  528 auto quick rw realtime
>  529 auto quick quota
> +530 auto quick quota
> -- 
> 2.27.0

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests
  2021-03-21 14:26 ` [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests Eryu Guan
@ 2021-03-21 15:32   ` Christian Brauner
  2021-03-22  2:37     ` Eryu Guan
  0 siblings, 1 reply; 22+ messages in thread
From: Christian Brauner @ 2021-03-21 15:32 UTC (permalink / raw)
  To: Eryu Guan; +Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Sun, Mar 21, 2021 at 10:26:35PM +0800, Eryu Guan wrote:
> On Tue, Mar 16, 2021 at 11:36:23AM +0100, Christian Brauner wrote:
> > Hey everyone,
> > 
> > /* v9 */
> > Rebased onto current master.
> > Hopefully we can merge this soon so we can have regular testing asap and
> > the rebasing is getting a bit tedious too. ;)
> 
> Sorry for the late review.. You don't have to rebase on every release if
> updating the test seq number is the only thing changed. Seq number
> conflicts all the time, usually I can fix it on commit.
> 
> Patch 2 is missing from the list, maybe it's too big to post, could you

_Sigh_, this seems like another vger issue. The individual Ccs/Tos
should've all gotten the patch but it's missing from the list. This is a
pattern I've seen quite a few times recently with vger. There's nothing
to do apart from complain about it to the vger maintainer. :)

> please provide a git repo so I can pull from there?

For sure. It's available in three locations:
https://git.kernel.org/pub/scm/linux/kernel/git/brauner/xfstests-dev.git/log/?h=idmapped_mounts_v2
https://gitlab.com/brauner/xfstests/-/tree/idmapped_mounts_v2
https://github.com/brauner/xfstests/tree/idmapped_mounts_v2

(Are you fixing the things you pointed out in the individual patches
yourself or should I resend a new version?)

Thanks!
Christian

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests
  2021-03-21 15:32   ` Christian Brauner
@ 2021-03-22  2:37     ` Eryu Guan
  2021-03-22  9:49       ` Christian Brauner
  0 siblings, 1 reply; 22+ messages in thread
From: Eryu Guan @ 2021-03-22  2:37 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Eryu Guan, Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Sun, Mar 21, 2021 at 04:32:00PM +0100, Christian Brauner wrote:
> On Sun, Mar 21, 2021 at 10:26:35PM +0800, Eryu Guan wrote:
> > On Tue, Mar 16, 2021 at 11:36:23AM +0100, Christian Brauner wrote:
> > > Hey everyone,
> > > 
> > > /* v9 */
> > > Rebased onto current master.
> > > Hopefully we can merge this soon so we can have regular testing asap and
> > > the rebasing is getting a bit tedious too. ;)
> > 
> > Sorry for the late review.. You don't have to rebase on every release if
> > updating the test seq number is the only thing changed. Seq number
> > conflicts all the time, usually I can fix it on commit.
> > 
> > Patch 2 is missing from the list, maybe it's too big to post, could you
> 
> _Sigh_, this seems like another vger issue. The individual Ccs/Tos
> should've all gotten the patch but it's missing from the list. This is a
> pattern I've seen quite a few times recently with vger. There's nothing
> to do apart from complain about it to the vger maintainer. :)
> 
> > please provide a git repo so I can pull from there?
> 
> For sure. It's available in three locations:
> https://git.kernel.org/pub/scm/linux/kernel/git/brauner/xfstests-dev.git/log/?h=idmapped_mounts_v2
> https://gitlab.com/brauner/xfstests/-/tree/idmapped_mounts_v2
> https://github.com/brauner/xfstests/tree/idmapped_mounts_v2

Ok, thanks!

> 
> (Are you fixing the things you pointed out in the individual patches
> yourself or should I resend a new version?)

New version please, I can fix trival things like typo, renumber the
test, code style, etc.

Thanks,
Eryu

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests
  2021-03-22  2:37     ` Eryu Guan
@ 2021-03-22  9:49       ` Christian Brauner
  0 siblings, 0 replies; 22+ messages in thread
From: Christian Brauner @ 2021-03-22  9:49 UTC (permalink / raw)
  To: Eryu Guan
  Cc: Eryu Guan, Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Mon, Mar 22, 2021 at 10:37:04AM +0800, Eryu Guan wrote:
> On Sun, Mar 21, 2021 at 04:32:00PM +0100, Christian Brauner wrote:
> > On Sun, Mar 21, 2021 at 10:26:35PM +0800, Eryu Guan wrote:
> > > On Tue, Mar 16, 2021 at 11:36:23AM +0100, Christian Brauner wrote:
> > > > Hey everyone,
> > > > 
> > > > /* v9 */
> > > > Rebased onto current master.
> > > > Hopefully we can merge this soon so we can have regular testing asap and
> > > > the rebasing is getting a bit tedious too. ;)
> > > 
> > > Sorry for the late review.. You don't have to rebase on every release if
> > > updating the test seq number is the only thing changed. Seq number
> > > conflicts all the time, usually I can fix it on commit.
> > > 
> > > Patch 2 is missing from the list, maybe it's too big to post, could you
> > 
> > _Sigh_, this seems like another vger issue. The individual Ccs/Tos
> > should've all gotten the patch but it's missing from the list. This is a
> > pattern I've seen quite a few times recently with vger. There's nothing
> > to do apart from complain about it to the vger maintainer. :)
> > 
> > > please provide a git repo so I can pull from there?
> > 
> > For sure. It's available in three locations:
> > https://git.kernel.org/pub/scm/linux/kernel/git/brauner/xfstests-dev.git/log/?h=idmapped_mounts_v2
> > https://gitlab.com/brauner/xfstests/-/tree/idmapped_mounts_v2
> > https://github.com/brauner/xfstests/tree/idmapped_mounts_v2
> 
> Ok, thanks!
> 
> > 
> > (Are you fixing the things you pointed out in the individual patches
> > yourself or should I resend a new version?)
> 
> New version please, I can fix trival things like typo, renumber the

I'll give you a new version hopefully today (CET).

> test, code style, etc.

Thanks!

Christian

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 1/4] generic/631: add test for detached mount propagation
  2021-03-21 14:28   ` Eryu Guan
@ 2021-03-22 10:00     ` Christian Brauner
  0 siblings, 0 replies; 22+ messages in thread
From: Christian Brauner @ 2021-03-22 10:00 UTC (permalink / raw)
  To: Eryu Guan; +Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Sun, Mar 21, 2021 at 10:28:52PM +0800, Eryu Guan wrote:
> On Tue, Mar 16, 2021 at 11:36:24AM +0100, Christian Brauner wrote:
> > Regression test to verify that creating a series of detached mounts,
> > attaching them to the filesystem, and unmounting them does not trigger an
> > integer overflow in ns->mounts causing the kernel to block any new mounts in
> > count_mounts() and returning ENOSPC because it falsely assumes that the
> > maximum number of mounts in the mount namespace has been reached, i.e. it
> > thinks it can't fit the new mounts into the mount namespace anymore.
> > 
> > The test is written in a way that it will leave the host's mount
> > namespace intact so we are sure to never make the host's mount namespace
> > unuseable!
> > 
> > Link: https://git.kernel.org/torvalds/c/ee2e3f50629f17b0752b55b2566c15ce8dafb557
> > Cc: Christoph Hellwig <hch@lst.de>
> > Cc: David Howells <dhowells@redhat.com>
> > Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
> > ---
> > /* v1 */
> > patch not present
> > 
> > /* v2 */
> > patch not present
> > 
> > /* v3 */
> > patch not present
> > 
> > /* v4 */
> > patch not present
> > 
> > /* v5 */
> > patch not present
> > 
> > /* v6 */
> > patch not present
> > 
> > /* v7 */
> > patch not present
> > 
> > /* v8 */
> > patch introduced
> > 
> > /* v9 */
> > - Christian Brauner <christian.brauner@ubuntu.com>:
> >   - Rebased on current master.
> > ---
> >  .gitignore                        |   1 +
> >  src/Makefile                      |   3 +-
> >  src/detached_mounts_propagation.c | 252 ++++++++++++++++++++++++++++++
> >  tests/generic/631                 |  41 +++++
> >  tests/generic/631.out             |   2 +
> >  tests/generic/group               |   1 +
> >  6 files changed, 299 insertions(+), 1 deletion(-)
> >  create mode 100644 src/detached_mounts_propagation.c
> >  create mode 100644 tests/generic/631
> >  create mode 100644 tests/generic/631.out
> > 
> > diff --git a/.gitignore b/.gitignore
> > index f3bc0a4f..72bd40a0 100644
> > --- a/.gitignore
> > +++ b/.gitignore
> > @@ -62,6 +62,7 @@
> >  /src/cloner
> >  /src/dbtest
> >  /src/deduperace
> > +/src/detached_mounts_propagation
> >  /src/devzero
> >  /src/dio-interleaved
> >  /src/dio-invalidate-cache
> > diff --git a/src/Makefile b/src/Makefile
> > index 3d729a34..99019e6e 100644
> > --- a/src/Makefile
> > +++ b/src/Makefile
> > @@ -29,7 +29,8 @@ LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
> >  	attr-list-by-handle-cursor-test listxattr dio-interleaved t_dir_type \
> >  	dio-invalidate-cache stat_test t_encrypted_d_revalidate \
> >  	attr_replace_test swapon mkswap t_attr_corruption t_open_tmpfiles \
> > -	fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail
> > +	fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail \
> > +	detached_mounts_propagation
> >  
> >  SUBDIRS = log-writes perf
> >  
> > diff --git a/src/detached_mounts_propagation.c b/src/detached_mounts_propagation.c
> > new file mode 100644
> > index 00000000..4a588e46
> > --- /dev/null
> > +++ b/src/detached_mounts_propagation.c
> > @@ -0,0 +1,252 @@
> > +/* SPDX-License-Identifier: LGPL-2.1+ */
> > +/*
> > + * Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
> > + * All Rights Reserved.
> > + *
> > + * Regression test to verify that creating a series of detached mounts,
> > + * attaching them to the filesystem, and unmounting them does not trigger an
> > + * integer overflow in ns->mounts causing the kernel to block any new mounts in
> > + * count_mounts() and returning ENOSPC because it falsely assumes that the
> > + * maximum number of mounts in the mount namespace has been reached, i.e. it
> > + * thinks it can't fit the new mounts into the mount namespace anymore.
> > + */
> > +
> > +#include <errno.h>
> > +#include <fcntl.h>
> > +#include <getopt.h>
> > +#include <limits.h>
> > +#include <sched.h>
> > +#include <stdbool.h>
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <sys/mount.h>
> > +#include <sys/stat.h>
> > +#include <sys/syscall.h>
> > +#include <sys/types.h>
> > +#include <unistd.h>
> > +
> > +/* open_tree() */
> > +#ifndef OPEN_TREE_CLONE
> > +#define OPEN_TREE_CLONE 1
> > +#endif
> > +
> > +#ifndef OPEN_TREE_CLOEXEC
> > +#define OPEN_TREE_CLOEXEC O_CLOEXEC
> > +#endif
> > +
> > +#ifndef __NR_open_tree
> > +	#if defined __alpha__
> > +		#define __NR_open_tree 538
> > +	#elif defined _MIPS_SIM
> > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > +			#define __NR_open_tree 4428
> > +		#endif
> > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > +			#define __NR_open_tree 6428
> > +		#endif
> > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > +			#define __NR_open_tree 5428
> > +		#endif
> > +	#elif defined __ia64__
> > +		#define __NR_open_tree (428 + 1024)
> > +	#else
> > +		#define __NR_open_tree 428
> > +	#endif
> > +#endif
> > +
> > +/* move_mount() */
> > +#ifndef MOVE_MOUNT_F_EMPTY_PATH
> > +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
> > +#endif
> > +
> > +#ifndef __NR_move_mount
> > +	#if defined __alpha__
> > +		#define __NR_move_mount 539
> > +	#elif defined _MIPS_SIM
> > +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
> > +			#define __NR_move_mount 4429
> > +		#endif
> > +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
> > +			#define __NR_move_mount 6429
> > +		#endif
> > +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
> > +			#define __NR_move_mount 5429
> > +		#endif
> > +	#elif defined __ia64__
> > +		#define __NR_move_mount (428 + 1024)
> > +	#else
> > +		#define __NR_move_mount 429
> > +	#endif
> > +#endif
> > +
> > +static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
> > +{
> > +	return syscall(__NR_open_tree, dfd, filename, flags);
> > +}
> > +
> > +static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
> > +				 const char *to_pathname, unsigned int flags)
> > +{
> > +	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
> > +}
> > +
> > +static bool is_shared_mountpoint(const char *path)
> > +{
> > +	bool shared = false;
> > +	FILE *f = NULL;
> > +	char *line = NULL;
> > +	int i;
> > +	size_t len = 0;
> > +
> > +	f = fopen("/proc/self/mountinfo", "re");
> > +	if (!f)
> > +		return 0;
> > +
> > +	while (getline(&line, &len, f) > 0) {
> > +		char *slider1, *slider2;
> > +
> > +		for (slider1 = line, i = 0; slider1 && i < 4; i++)
> > +			slider1 = strchr(slider1 + 1, ' ');
> > +
> > +		if (!slider1)
> > +			continue;
> > +
> > +		slider2 = strchr(slider1 + 1, ' ');
> > +		if (!slider2)
> > +			continue;
> > +
> > +		*slider2 = '\0';
> > +		if (strcmp(slider1 + 1, path) == 0) {
> > +			/* This is the path. Is it shared? */
> > +			slider1 = strchr(slider2 + 1, ' ');
> > +			if (slider1 && strstr(slider1, "shared:")) {
> > +				shared = true;
> > +				break;
> > +			}
> > +		}
> > +	}
> > +	fclose(f);
> > +	free(line);
> > +
> > +	return shared;
> > +}
> > +
> > +static void usage(void)
> > +{
> > +	const char *text = "mount-new [--recursive] <base-dir>\n";
> > +	fprintf(stderr, "%s", text);
> > +	_exit(EXIT_SUCCESS);
> > +}
> > +
> > +#define exit_usage(format, ...)                              \
> > +	({                                                   \
> > +		fprintf(stderr, format "\n", ##__VA_ARGS__); \
> > +		usage();                                     \
> > +	})
> > +
> > +#define exit_log(format, ...)                                \
> > +	({                                                   \
> > +		fprintf(stderr, format "\n", ##__VA_ARGS__); \
> > +		exit(EXIT_FAILURE);                          \
> > +	})
> > +
> > +static const struct option longopts[] = {
> > +	{"help",	no_argument,		0,	'a'},
> > +	{ NULL,		no_argument,		0,	 0 },
> > +};
> > +
> > +int main(int argc, char *argv[])
> > +{
> > +	int exit_code = EXIT_SUCCESS, index = 0;
> > +	int dfd, fd_tree, new_argc, ret;
> > +	char *base_dir;
> > +	char *const *new_argv;
> > +	char target[PATH_MAX];
> > +
> > +	while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
> > +		switch (ret) {
> > +		case 'a':
> > +			/* fallthrough */
> > +		default:
> > +			usage();
> > +		}
> > +	}
> > +
> > +	new_argv = &argv[optind];
> > +	new_argc = argc - optind;
> > +	if (new_argc < 1)
> > +		exit_usage("Missing base directory\n");
> > +	base_dir = new_argv[0];
> > +
> > +	if (*base_dir != '/')
> > +		exit_log("Please specify an absolute path");
> > +
> > +	/* Ensure that target is a shared mountpoint. */
> > +	if (!is_shared_mountpoint(base_dir))
> > +		exit_log("Please ensure that \"%s\" is a shared mountpoint", base_dir);
> > +
> > +	ret = unshare(CLONE_NEWNS);
> > +	if (ret < 0)
> > +		exit_log("%m - Failed to create new mount namespace");
> > +
> > +	ret = mount(NULL, base_dir, NULL, MS_REC | MS_SHARED, NULL);
> > +	if (ret < 0)
> > +		exit_log("%m - Failed to make base_dir shared mountpoint");
> > +
> > +	dfd = open(base_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
> > +	if (dfd < 0)
> > +		exit_log("%m - Failed to open base directory \"%s\"", base_dir);
> > +
> > +	ret = mkdirat(dfd, "detached-move-mount", 0755);
> > +	if (ret < 0)
> > +		exit_log("%m - Failed to create required temporary directories");
> > +
> > +	ret = snprintf(target, sizeof(target), "%s/detached-move-mount", base_dir);
> > +	if (ret < 0 || (size_t)ret >= sizeof(target))
> > +		exit_log("%m - Failed to assemble target path");
> > +
> > +	/*
> > +	 * Having a mount table with 10000 mounts is already quite excessive
> > +	 * and shoult account even for weird test systems.
> > +	 */
> > +	for (size_t i = 0; i < 10000; i++) {
> > +		fd_tree = sys_open_tree(dfd, "detached-move-mount",
> > +					OPEN_TREE_CLONE |
> > +					OPEN_TREE_CLOEXEC |
> > +					AT_EMPTY_PATH);
> > +		if (fd_tree < 0) {
> > +			if (errno == ENOSYS) /* New mount API not (fully) supported. */
> > +				break;
> > +
> > +			fprintf(stderr, "%m - Failed to open %d(detached-move-mount)", dfd);
> > +			exit_code = EXIT_FAILURE;
> > +			break;
> > +		}
> > +
> > +		ret = sys_move_mount(fd_tree, "", dfd, "detached-move-mount", MOVE_MOUNT_F_EMPTY_PATH);
> > +		if (ret < 0) {
> > +			if (errno == ENOSPC)
> > +				fprintf(stderr, "%m - Buggy mount counting");
> > +			else if (errno == ENOSYS) /* New mount API not (fully) supported. */
> > +				break;
> > +			else
> > +				fprintf(stderr, "%m - Failed to attach mount to %d(detached-move-mount)", dfd);
> > +			exit_code = EXIT_FAILURE;
> > +			break;
> > +		}
> > +		close(fd_tree);
> > +
> > +		ret = umount2(target, MNT_DETACH);
> > +		if (ret < 0) {
> > +			fprintf(stderr, "%m - Failed to unmount %s", target);
> > +			exit_code = EXIT_FAILURE;
> > +			break;
> > +		}
> > +	}
> > +
> > +	(void)unlinkat(dfd, "detached-move-mount", AT_REMOVEDIR);
> > +	close(dfd);
> > +
> > +	exit(exit_code);
> > +}
> > diff --git a/tests/generic/631 b/tests/generic/631
> > new file mode 100644
> > index 00000000..e227c448
> > --- /dev/null
> > +++ b/tests/generic/631
> > @@ -0,0 +1,41 @@
> > +#! /bin/bash
> > +# SPDX-License-Identifier: GPL-2.0+
> > +#
> > +# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
> > +# All Rights Reserved.
> > +#
> > +# FS QA Test No. 631
> > +#
> > +# Regression test to verify that creating a series of detached mounts,
> > +# attaching them to the filesystem, and unmounting them does not trigger an
> > +# integer overflow in ns->mounts causing the kernel to block any new mounts in
> > +# count_mounts() and returning ENOSPC because it falsely assumes that the
> > +# maximum number of mounts in the mount namespace has been reached, i.e. it
> > +# thinks it can't fit the new mounts into the mount namespace anymore.
> > +#
> > +seq=`basename $0`
> > +seqres=$RESULT_DIR/$seq
> > +echo "QA output created by $seq"
> > +
> > +here=`pwd`
> > +tmp=/tmp/$$
> > +status=1	# failure is the default!
> > +trap "_cleanup; exit \$status" 0 1 2 3 15
> > +
> > +_cleanup()
> > +{
> > +	cd /
> > +	rm -f $tmp.*
> > +}
> > +
> > +. ./common/rc
> > +
> > +rm -f $seqres.full
> > +
> > +_supported_fs generic
> 
> _require_test

added

> 
> As test runs directly on $TEST_DIR.
> 
> Also needs
> 
> _require_test_program "detached_mounts_propagation"

added

> 
> > +
> > +$here/src/detached_mounts_propagation $TEST_DIR >> $seqres.full
> > +
> > +echo silence is golden
> > +status=$?
> 
> This should go after detached_mounts_propagation command?

Fixed.
(Yeah, though it doesn't really matter as the error would lead to the
mount namespace being unuseable and you'd see this pretty glaringly not
just in terms of behavior but also in terms of the output in 631.out.)

Christian

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 3/4] xfs/529: quotas and idmapped mounts
  2021-03-21 14:42   ` Eryu Guan
@ 2021-03-22 10:11     ` Christian Brauner
  0 siblings, 0 replies; 22+ messages in thread
From: Christian Brauner @ 2021-03-22 10:11 UTC (permalink / raw)
  To: Eryu Guan; +Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Sun, Mar 21, 2021 at 10:42:06PM +0800, Eryu Guan wrote:
> On Tue, Mar 16, 2021 at 11:36:26AM +0100, Christian Brauner wrote:
> > Test that xfs quota behave correctly on idmapped mounts.
> > Mount a scratch device with user and group quota support enabled. Create
> > directories "unmapped" and "idmapped". Create files in the unampped
> > mount and verify quota behavior. Create files through the idmapped mount
> > and verify identical behavior.
> > 
> > Cc: Christoph Hellwig <hch@lst.de>
> > Cc: Darrick J. Wong <djwong@kernel.org>
> > Cc: fstests@vger.kernel.org
> > Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
> > ---
> > /* v1 */
> > patch not present
> > 
> > /* v2 */
> > patch not present
> > 
> > /* v3 */
> > patch not present
> > 
> > /* v4 */
> > patch not present
> > 
> > /* v5 */
> > patch not present
> > 
> > /* v6 */
> > patch not present
> > 
> > /* v7 */
> > patch not present
> > 
> > /* v8 */
> > patch introduced
> > 
> > /* v9 */
> > - Christian Brauner <christian.brauner@ubuntu.com>:
> >   - Rebased on current master.
> > ---
> >  .gitignore                           |   1 +
> >  src/idmapped-mounts/Makefile         |  14 +-
> >  src/idmapped-mounts/mount-idmapped.c | 428 +++++++++++++++++
> >  src/idmapped-mounts/utils.c          |   2 +-
> >  src/idmapped-mounts/utils.h          |   1 +
> >  tests/xfs/529                        | 373 +++++++++++++++
> >  tests/xfs/529.out                    | 657 +++++++++++++++++++++++++++
> >  tests/xfs/group                      |   1 +
> >  8 files changed, 1472 insertions(+), 5 deletions(-)
> >  create mode 100644 src/idmapped-mounts/mount-idmapped.c
> >  create mode 100644 tests/xfs/529
> >  create mode 100644 tests/xfs/529.out
> > 
> > diff --git a/.gitignore b/.gitignore
> > index 3229bb26..4cc9c807 100644
> > --- a/.gitignore
> > +++ b/.gitignore
> > @@ -179,6 +179,7 @@
> >  /src/aio-dio-regress/aiocp
> >  /src/aio-dio-regress/aiodio_sparse2
> >  /src/idmapped-mounts/idmapped-mounts
> > +/src/idmapped-mounts/mount-idmapped
> >  /src/log-writes/replay-log
> >  /src/perf/*.pyc
> >  
> > diff --git a/src/idmapped-mounts/Makefile b/src/idmapped-mounts/Makefile
> > index 6a934146..40bf9164 100644
> > --- a/src/idmapped-mounts/Makefile
> > +++ b/src/idmapped-mounts/Makefile
> > @@ -3,9 +3,10 @@
> >  TOPDIR = ../..
> >  include $(TOPDIR)/include/builddefs
> >  
> > -TARGETS = idmapped-mounts
> > +TARGETS = idmapped-mounts mount-idmapped
> > +CFILES_IDMAPPED_MOUNTS = idmapped-mounts.c utils.c
> > +CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c
> >  
> > -CFILES = idmapped-mounts.c utils.c
> >  HFILES = missing.h utils.h
> >  LLDLIBS += -pthread
> >  LDIRT = $(TARGETS)
> > @@ -24,12 +25,17 @@ depend: .dep
> >  
> >  include $(BUILDRULES)
> >  
> > -$(TARGETS): $(CFILES)
> > +idmapped-mounts:
> >  	@echo "    [CC]    $@"
> > -	$(Q)$(LTLINK) $(CFILES) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
> > +	$(Q)$(LTLINK) $(CFILES_IDMAPPED_MOUNTS) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
> > +
> > +mount-idmapped:
> > +	@echo "    [CC]    $@"
> > +	$(Q)$(LTLINK) $(CFILES_MOUNT_IDMAPPED) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
> >  
> >  install:
> >  	$(INSTALL) -m 755 -d $(PKG_LIB_DIR)/src/idmapped-mounts
> >  	$(INSTALL) -m 755 $(TARGETS) $(PKG_LIB_DIR)/src/idmapped-mounts
> > +	$(INSTALL) -m 755 $(TARGETS) $(PKG_LIB_DIR)/src/mount-idmapped
> >  
> >  -include .dep
> > diff --git a/src/idmapped-mounts/mount-idmapped.c b/src/idmapped-mounts/mount-idmapped.c
> > new file mode 100644
> > index 00000000..f127cdc7
> > --- /dev/null
> > +++ b/src/idmapped-mounts/mount-idmapped.c
> > @@ -0,0 +1,428 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +#ifndef _GNU_SOURCE
> > +#define _GNU_SOURCE
> > +#endif
> > +
> > +#include "../global.h"
> > +
> > +#include <dirent.h>
> > +#include <errno.h>
> > +#include <fcntl.h>
> > +#include <getopt.h>
> > +#include <libgen.h>
> > +#include <limits.h>
> > +#include <linux/bpf.h>
> > +#include <linux/sched.h>
> > +#include <linux/seccomp.h>
> > +#include <sched.h>
> > +#include <signal.h>
> > +#include <stdbool.h>
> > +#include <stdint.h>
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <sys/mman.h>
> > +#include <sys/stat.h>
> > +#include <sys/syscall.h>
> > +#include <sys/types.h>
> > +#include <sys/wait.h>
> > +#include <unistd.h>
> > +
> > +#include "missing.h"
> > +#include "utils.h"
> > +
> > +/* A few helpful macros. */
> > +#define STRLITERALLEN(x) (sizeof(""x"") - 1)
> > +
> > +#define INTTYPE_TO_STRLEN(type)             \
> > +	(2 + (sizeof(type) <= 1             \
> > +		  ? 3                       \
> > +		  : sizeof(type) <= 2       \
> > +			? 5                 \
> > +			: sizeof(type) <= 4 \
> > +			      ? 10          \
> > +			      : sizeof(type) <= 8 ? 20 : sizeof(int[-2 * (sizeof(type) > 8)])))
> > +
> > +#define syserror(format, ...)                           \
> > +	({                                              \
> > +		fprintf(stderr, format, ##__VA_ARGS__); \
> > +		(-errno);                               \
> > +	})
> > +
> > +#define syserror_set(__ret__, format, ...)                    \
> > +	({                                                    \
> > +		typeof(__ret__) __internal_ret__ = (__ret__); \
> > +		errno = labs(__ret__);                        \
> > +		fprintf(stderr, format, ##__VA_ARGS__);       \
> > +		__internal_ret__;                             \
> > +	})
> > +
> > +struct list {
> > +	void *elem;
> > +	struct list *next;
> > +	struct list *prev;
> > +};
> > +
> > +#define list_for_each(__iterator, __list) \
> > +	for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
> > +
> > +static inline void list_init(struct list *list)
> > +{
> > +	list->elem = NULL;
> > +	list->next = list->prev = list;
> > +}
> > +
> > +static inline int list_empty(const struct list *list)
> > +{
> > +	return list == list->next;
> > +}
> > +
> > +static inline void __list_add(struct list *new, struct list *prev, struct list *next)
> > +{
> > +	next->prev = new;
> > +	new->next = next;
> > +	new->prev = prev;
> > +	prev->next = new;
> > +}
> > +
> > +static inline void list_add_tail(struct list *head, struct list *list)
> > +{
> > +	__list_add(list, head->prev, head);
> > +}
> > +
> > +typedef enum idmap_type_t {
> > +	ID_TYPE_UID,
> > +	ID_TYPE_GID
> > +} idmap_type_t;
> > +
> > +struct id_map {
> > +	idmap_type_t map_type;
> > +	__u32 nsid;
> > +	__u32 hostid;
> > +	__u32 range;
> > +};
> > +
> > +static struct list active_map;
> > +
> > +static int add_map_entry(__u32 id_host,
> > +			 __u32 id_ns,
> > +			 __u32 range,
> > +			 idmap_type_t map_type)
> > +{
> > +	struct list *new_list = NULL;
> > +	struct id_map *newmap = NULL;
> > +
> > +	newmap = malloc(sizeof(*newmap));
> > +	if (!newmap)
> > +		return -ENOMEM;
> > +
> > +	new_list = malloc(sizeof(struct list));
> > +	if (!new_list) {
> > +		free(newmap);
> > +		return -ENOMEM;
> > +	}
> > +
> > +	*newmap = (struct id_map){
> > +		.hostid		= id_host,
> > +		.nsid		= id_ns,
> > +		.range		= range,
> > +		.map_type	= map_type,
> > +	};
> > +
> > +	new_list->elem = newmap;
> > +	list_add_tail(&active_map, new_list);
> > +	return 0;
> > +}
> > +
> > +static int parse_map(char *map)
> > +{
> > +	char types[2] = {'u', 'g'};
> > +	int ret;
> > +	__u32 id_host, id_ns, range;
> > +	char which;
> > +
> > +	if (!map)
> > +		return -1;
> > +
> > +	ret = sscanf(map, "%c:%u:%u:%u", &which, &id_ns, &id_host, &range);
> > +	if (ret != 4)
> > +		return -1;
> > +
> > +	if (which != 'b' && which != 'u' && which != 'g')
> > +		return -1;
> > +
> > +	for (int i = 0; i < 2; i++) {
> > +		idmap_type_t map_type;
> > +
> > +		if (which != types[i] && which != 'b')
> > +			continue;
> > +
> > +		if (types[i] == 'u')
> > +			map_type = ID_TYPE_UID;
> > +		else
> > +			map_type = ID_TYPE_GID;
> > +
> > +		ret = add_map_entry(id_host, id_ns, range, map_type);
> > +		if (ret < 0)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int write_id_mapping(idmap_type_t map_type, pid_t pid, const char *buf, size_t buf_size)
> > +{
> > +	int fd = -EBADF, setgroups_fd = -EBADF;
> > +	int fret = -1;
> > +	int ret;
> > +	char path[STRLITERALLEN("/proc") + INTTYPE_TO_STRLEN(pid_t) +
> > +		  STRLITERALLEN("/setgroups") + 1];
> > +
> > +	if (geteuid() != 0 && map_type == ID_TYPE_GID) {
> > +		ret = snprintf(path, sizeof(path), "/proc/%d/setgroups", pid);
> > +		if (ret < 0 || ret >= sizeof(path))
> > +			goto out;
> > +
> > +		setgroups_fd = open(path, O_WRONLY | O_CLOEXEC);
> > +		if (setgroups_fd < 0 && errno != ENOENT) {
> > +			syserror("Failed to open \"%s\"", path);
> > +			goto out;
> > +		}
> > +
> > +		if (setgroups_fd >= 0) {
> > +			ret = write_nointr(setgroups_fd, "deny\n", STRLITERALLEN("deny\n"));
> > +			if (ret != STRLITERALLEN("deny\n")) {
> > +				syserror("Failed to write \"deny\" to \"/proc/%d/setgroups\"", pid);
> > +				goto out;
> > +			}
> > +		}
> > +	}
> > +
> > +	ret = snprintf(path, sizeof(path), "/proc/%d/%cid_map", pid, map_type == ID_TYPE_UID ? 'u' : 'g');
> > +	if (ret < 0 || ret >= sizeof(path))
> > +		goto out;
> > +
> > +	fd = open(path, O_WRONLY | O_CLOEXEC);
> > +	if (fd < 0) {
> > +		syserror("Failed to open \"%s\"", path);
> > +		goto out;
> > +	}
> > +
> > +	ret = write_nointr(fd, buf, buf_size);
> > +	if (ret != buf_size) {
> > +		syserror("Failed to write %cid mapping to \"%s\"",
> > +			 map_type == ID_TYPE_UID ? 'u' : 'g', path);
> > +		goto out;
> > +	}
> > +
> > +	fret = 0;
> > +out:
> > +	if (fd >= 0)
> > +		close(fd);
> > +	if (setgroups_fd >= 0)
> > +		close(setgroups_fd);
> > +
> > +	return fret;
> > +}
> > +
> > +static int map_ids_from_idmap(struct list *idmap, pid_t pid)
> > +{
> > +	int fill, left;
> > +	char mapbuf[4096] = {};
> > +	bool had_entry = false;
> > +
> > +	for (idmap_type_t map_type = ID_TYPE_UID, u_or_g = 'u';
> > +	     map_type <= ID_TYPE_GID; map_type++, u_or_g = 'g') {
> > +		char *pos = mapbuf;
> > +		int ret;
> > +		struct list *iterator;
> > +
> > +
> > +		list_for_each(iterator, idmap) {
> > +			struct id_map *map = iterator->elem;
> > +			if (map->map_type != map_type)
> > +				continue;
> > +
> > +			had_entry = true;
> > +
> > +			left = 4096 - (pos - mapbuf);
> > +			fill = snprintf(pos, left, "%u %u %u\n", map->nsid, map->hostid, map->range);
> > +			/*
> > +			 * The kernel only takes <= 4k for writes to
> > +			 * /proc/<pid>/{g,u}id_map
> > +			 */
> > +			if (fill <= 0 || fill >= left)
> > +				return syserror_set(-E2BIG, "Too many %cid mappings defined", u_or_g);
> > +
> > +			pos += fill;
> > +		}
> > +		if (!had_entry)
> > +			continue;
> > +
> > +		ret = write_id_mapping(map_type, pid, mapbuf, pos - mapbuf);
> > +		if (ret < 0)
> > +			return syserror("Failed to write mapping: %s", mapbuf);
> > +
> > +		memset(mapbuf, 0, sizeof(mapbuf));
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int get_userns_fd_from_idmap(struct list *idmap)
> > +{
> > +	int ret;
> > +	pid_t pid;
> > +	char path_ns[STRLITERALLEN("/proc") + INTTYPE_TO_STRLEN(pid_t) +
> > +		  STRLITERALLEN("/ns/user") + 1];
> > +
> > +	pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER | CLONE_NEWNS);
> > +	if (pid < 0)
> > +		return -errno;
> > +
> > +	ret = map_ids_from_idmap(idmap, pid);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	ret = snprintf(path_ns, sizeof(path_ns), "/proc/%d/ns/user", pid);
> > +	if (ret < 0 || (size_t)ret >= sizeof(path_ns))
> > +		ret = -EIO;
> > +	else
> > +		ret = open(path_ns, O_RDONLY | O_CLOEXEC | O_NOCTTY);
> > +
> > +	(void)kill(pid, SIGKILL);
> > +	(void)wait_for_pid(pid);
> > +	return ret;
> > +}
> > +
> > +static inline bool strnequal(const char *str, const char *eq, size_t len)
> > +{
> > +	return strncmp(str, eq, len) == 0;
> > +}
> > +
> > +static void usage(void)
> > +{
> > +	const char *text = "\
> > +mount-idmapped --map-mount=<idmap> <source> <target>\n\
> > +\n\
> > +Create an idmapped mount of <source> at <target>\n\
> > +Options:\n\
> > +  --map-mount=<idmap>\n\
> > +	Specify an idmap for the <target> mount in the format\n\
> > +	<idmap-type>:<id-from>:<id-to>:<id-range>\n\
> > +	The <idmap-type> can be:\n\
> > +	\"b\" or \"both\"	-> map both uids and gids\n\
> > +	\"u\" or \"uid\"	-> map uids\n\
> > +	\"g\" or \"gid\"	-> map gids\n\
> > +	For example, specifying:\n\
> > +	both:1000:1001:1	-> map uid and gid 1000 to uid and gid 1001 in <target> and no other ids\n\
> > +	uid:20000:100000:1000	-> map uid 20000 to uid 100000, uid 20001 to uid 100001 [...] in <target>\n\
> > +	Currently up to 340 separate idmappings may be specified.\n\n\
> > +  --map-mount=/proc/<pid>/ns/user\n\
> > +	Specify a path to a user namespace whose idmap is to be used.\n\n\
> > +  --recursive\n\
> > +	Copy the whole mount tree from <source> and apply the idmap to everyone at <target>.\n\n\
> > +Examples:\n\
> > +  - Create an idmapped mount of /source on /target with both ('b') uids and gids mapped:\n\
> > +	mount-idmapped --map-mount b:0:10000:10000 /source /target\n\n\
> > +  - Create an idmapped mount of /source on /target with uids ('u') and gids ('g') mapped separately:\n\
> > +	mount-idmapped --map-mount u:0:10000:10000 g:0:20000:20000 /source /target\n\n\
> > +";
> > +	fprintf(stderr, "%s", text);
> > +	_exit(EXIT_SUCCESS);
> > +}
> > +
> > +#define exit_usage(format, ...)                         \
> > +	({                                              \
> > +		fprintf(stderr, format, ##__VA_ARGS__); \
> > +		usage();                                \
> > +	})
> > +
> > +#define exit_log(format, ...)                           \
> > +	({                                              \
> > +		fprintf(stderr, format, ##__VA_ARGS__); \
> > +		exit(EXIT_FAILURE);                     \
> > +	})
> > +
> > +static const struct option longopts[] = {
> > +	{"map-mount",	required_argument,	0,	'a'},
> > +	{"help",	no_argument,		0,	'c'},
> > +	{"recursive",	no_argument,		0,	'd'},
> > +	{ NULL,		0,			0,	0  },
> > +};
> > +
> > +int main(int argc, char *argv[])
> > +{
> > +	int fd_userns = -EBADF;
> > +	int index = 0;
> > +	const char *source = NULL, *target = NULL;
> > +	bool recursive = false;
> > +	int fd_tree, new_argc, ret;
> > +	char *const *new_argv;
> > +
> > +	list_init(&active_map);
> > +	while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
> > +		switch (ret) {
> > +		case 'a':
> > +			if (strnequal(optarg, "/proc", STRLITERALLEN("/proc/"))) {
> > +				fd_userns = open(optarg, O_RDONLY | O_CLOEXEC);
> > +				if (fd_userns < 0)
> > +					exit_log("%m - Failed top open user namespace path %s\n", optarg);
> > +				break;
> > +			}
> > +
> > +			ret = parse_map(optarg);
> > +			if (ret < 0)
> > +				exit_log("Failed to parse idmaps for mount\n");
> > +			break;
> > +		case 'd':
> > +			recursive = true;
> > +			break;
> > +		case 'c':
> > +			/* fallthrough */
> > +		default:
> > +			usage();
> > +		}
> > +	}
> > +
> > +	new_argv = &argv[optind];
> > +	new_argc = argc - optind;
> > +	if (new_argc < 2)
> > +		exit_usage("Missing source or target mountpoint\n\n");
> > +	source = new_argv[0];
> > +	target = new_argv[1];
> > +
> > +	fd_tree = sys_open_tree(-EBADF, source,
> > +			        OPEN_TREE_CLONE |
> > +			        OPEN_TREE_CLOEXEC |
> > +			        AT_EMPTY_PATH |
> > +			        (recursive ? AT_RECURSIVE : 0));
> > +	if (fd_tree < 0) {
> > +		exit_log("%m - Failed to open %s\n", source);
> > +		exit(EXIT_FAILURE);
> > +	}
> > +
> > +	if (!list_empty(&active_map)) {
> > +		struct mount_attr attr = {
> > +			.attr_set = MOUNT_ATTR_IDMAP,
> > +		};
> > +
> > +		attr.userns_fd = get_userns_fd_from_idmap(&active_map);
> > +		if (attr.userns_fd < 0)
> > +			exit_log("%m - Failed to create user namespace\n");
> > +
> > +		ret = sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH | AT_RECURSIVE,
> > +					&attr, sizeof(attr));
> > +		if (ret < 0)
> > +			exit_log("%m - Failed to change mount attributes\n");
> > +		close(attr.userns_fd);
> > +	}
> > +
> > +	ret = sys_move_mount(fd_tree, "", -EBADF, target,
> > +			     MOVE_MOUNT_F_EMPTY_PATH);
> > +	if (ret < 0)
> > +		exit_log("%m - Failed to attach mount to %s\n", target);
> > +	close(fd_tree);
> > +
> > +	exit(EXIT_SUCCESS);
> > +}
> > diff --git a/src/idmapped-mounts/utils.c b/src/idmapped-mounts/utils.c
> > index b27ba445..977443f1 100644
> > --- a/src/idmapped-mounts/utils.c
> > +++ b/src/idmapped-mounts/utils.c
> > @@ -88,7 +88,7 @@ pid_t do_clone(int (*fn)(void *), void *arg, int flags)
> >  #endif
> >  }
> >  
> > -static int get_userns_fd_cb(void *data)
> > +int get_userns_fd_cb(void *data)
> >  {
> >  	return kill(getpid(), SIGSTOP);
> >  }
> > diff --git a/src/idmapped-mounts/utils.h b/src/idmapped-mounts/utils.h
> > index 93425731..efbf3bc3 100644
> > --- a/src/idmapped-mounts/utils.h
> > +++ b/src/idmapped-mounts/utils.h
> > @@ -20,6 +20,7 @@
> >  #include "missing.h"
> >  
> >  extern pid_t do_clone(int (*fn)(void *), void *arg, int flags);
> > +extern int get_userns_fd_cb(void *data);
> >  extern int get_userns_fd(unsigned long nsid, unsigned long hostid,
> >  			 unsigned long range);
> >  extern ssize_t read_nointr(int fd, void *buf, size_t count);
> > diff --git a/tests/xfs/529 b/tests/xfs/529
> > new file mode 100644
> > index 00000000..67251818
> > --- /dev/null
> > +++ b/tests/xfs/529
> > @@ -0,0 +1,373 @@
> > +#! /bin/bash
> > +# SPDX-License-Identifier: GPL-2.0+
> > +#
> > +# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
> > +# All Rights Reserved.
> > +#
> > +# FS QA Test No. 529
> > +#
> > +# Exercise basic xfs_quota functionality (user/group/project quota)
> > +# Use of "sync" mount option here is an attempt to get deterministic
> > +# allocator behaviour.
> > +#
> > +seq=`basename $0`
> > +seqres=$RESULT_DIR/$seq
> > +echo "QA output created by $seq"
> > +
> > +here=`pwd`
> > +tmp=/tmp/$$
> > +status=1	# failure is the default!
> > +trap "_cleanup; exit \$status" 0 1 2 3 15
> > +
> > +_cleanup()
> > +{
> > +	cd /
> > +	rm -f $tmp.*
> 
> Seems we should do wipe_mounts in _cleanup as well, in case test exits
> in the middle of the test with idmapped mount still mounted.

added

> 
> > +}
> > +
> > +# get standard environment, filters and checks
> > +. ./common/rc
> > +. ./common/filter
> > +. ./common/quota
> > +
> > +# remove previous $seqres.full before test
> > +rm -f $seqres.full
> > +
> > +# real QA test starts here
> > +_supported_fs xfs
> > +_require_idmapped_mounts
> > +_require_scratch
> > +_require_xfs_quota
> > +_require_user fsgqa
> > +_require_user fsgqa2
> > +_require_group fsgqa
> > +_require_group fsgqa2
> > +
> > +_scratch_mkfs_xfs >>$seqres.full 2>&1
> > +
> > +uqid=`id -u fsgqa`
> > +gqid=`id -g fsgqa`
> > +
> > +uqid2=`id -u fsgqa2`
> > +gqid2=`id -g fsgqa2`
> > +
> > +pqid=10
> > +cat >$tmp.projects <<EOF
> > +$pqid:$SCRATCH_MNT
> > +EOF
> > +
> > +cat >$tmp.projid <<EOF
> > +root:0
> > +fsgqa:$pqid
> > +EOF
> > +
> > +create_files_unmapped()
> > +{
> > +	local bs=$1
> > +	local inum=$2
> > +
> > +	echo "Using type=$type id=$id" >> $seqres.full
> > +
> > +	for ((i=0; i<$((inum-1)); i++)); do
> > +		_file_as_id $SCRATCH_MNT/unmapped/inode$i $id $type 1024 0
> > +	done
> > +
> > +	_file_as_id $SCRATCH_MNT/unmapped/block $id $type $bs 1
> > +}
> > +
> > +create_files_idmapped()
> > +{
> > +	local bs=$1
> > +	local inum=$2
> > +
> > +	echo "Using type=$type id=$id2" >> $seqres.full
> > +
> > +	for ((i=0; i<$((inum-1)); i++)); do
> > +		_file_as_id $SCRATCH_MNT/idmapped/inode$i $id2 $type 1024 0
> > +	done
> > +
> > +	_file_as_id $SCRATCH_MNT/idmapped/block $id2 $type $bs 1
> > +}
> > +
> > +clean_files()
> > +{
> > +	rm -rf $SCRATCH_MNT/unmapped 2>/dev/null
> > +	rm -rf $SCRATCH_MNT/idmapped 2>/dev/null
> > +	rm -rf $tmp.quot 2>/dev/null
> > +	rm -rf $tmp.quota 2>/dev/null
> > +}
> > +
> > +filter_quot()
> > +{
> > +	_filter_quota | grep -v "root \|\#0 " \
> > +		| sed -e '/#[0-9]*/s/#[0-9]*/#ID/g'
> > +}
> > +
> > +filter_report()
> > +{
> > +	_filter_quota | grep -v "^root \|^\#0 " \
> > +		| sed -e '/^#[0-9]*/s/^#[0-9]*/#ID/g'
> > +}
> > +
> > +filter_quota()
> > +{
> > +	_filter_quota | sed -e "/Disk quotas for/s/([0-9]*)/(ID)/g" \
> > +			    -e "/Disk quotas for/s/#[0-9]*/#ID/g"
> > +}
> > +
> > +filter_state()
> > +{
> > +	_filter_quota | sed -e "s/Inode: #[0-9]* (0 blocks, 0 extents)/Inode: #[INO] (0 blocks, 0 extents)/g" \
> > +			    -e "s/Inode: #[0-9]* ([0-9]* blocks, [0-9]* extents)/Inode: #[INO] (X blocks, Y extents)/g" \
> > +			    -e "/[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/s/ [0-9][0-9]:[0-9][0-9]:[0-9][0-9]//g" \
> > +			    -e '/max warnings:/d'
> > +}
> > +
> > +test_quot()
> > +{
> > +	local opt="$*"
> > +
> > +	echo "checking quot command (type=$type)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "quot -$type $opt -bi" $SCRATCH_MNT | filter_quot
> > +}
> > +
> > +test_report()
> > +{
> > +	local opt="$*"
> > +
> > +	echo "checking report command (type=$type)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "report -$type $opt -bi" \
> > +			$SCRATCH_MNT | filter_report
> > +}
> > +
> > +test_quota()
> > +{
> > +	local opt="$*"
> > +
> > +	echo "checking quota command (type=$type)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "quota -$type $opt -bi $id" \
> > +			$SCRATCH_MNT | filter_quota
> > +}
> > +
> > +test_limit()
> > +{
> > +	local bs=$1
> > +	local bh=$2
> > +	local is=$3
> > +	local ih=$4
> > +
> > +	echo "checking limit command (type=$type, bsoft=$bs, bhard=$bh, isoft=$is, ihard=$ih)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "limit -$type bsoft=$bs bhard=$bh fsgqa" \
> > +			-c "limit -$type isoft=$is ihard=$ih fsgqa" \
> > +			$SCRATCH_MNT
> > +
> > +	# let the timer day transition happen
> > +	sleep 2
> > +}
> > +
> > +test_timer()
> > +{
> > +	echo "checking timer command (type=$type)"
> > +	# set 3days+1h for time won't become 2days soon
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "timer -$type -bi 73h" \
> > +			$SCRATCH_MNT | _filter_scratch
> > +}
> > +
> > +test_disable()
> > +{
> > +	echo "checking disable command (type=$type)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "disable -$type -v" \
> > +			$SCRATCH_MNT | filter_state
> > +}
> > +
> > +test_enable()
> > +{
> > +	echo "checking enable command (type=$type)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "enable -$type -v" $SCRATCH_MNT | filter_state
> > +}
> > +
> > +test_off()
> > +{
> > +	echo "checking off command (type=$type)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "off -$type -v" $SCRATCH_MNT | _filter_scratch
> > +}
> > +
> > +test_remove()
> > +{
> > +	echo "checking remove command (type=$type)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "remove -$type -v" \
> > +			$SCRATCH_MNT | _filter_scratch
> > +}
> > +
> > +test_state()
> > +{
> > +	echo "checking state command (type=$type)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "state -$type" $SCRATCH_MNT | filter_state
> > +}
> > +
> > +test_dump()
> > +{
> > +	echo "checking dump command (type=$type)"
> > +	rm -f $tmp.backup 2>>/dev/null
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "dump -$type -f $tmp.backup" \
> > +			$SCRATCH_MNT | _filter_scratch
> > +}
> > +
> > +test_restore()
> > +{
> > +	echo "checking restore command (type=$type)"
> > +	$XFS_QUOTA_PROG -D $tmp.projects -P $tmp.projid -x \
> > +			-c "restore -$type -f $tmp.backup" \
> > +			$SCRATCH_MNT | _filter_scratch
> > +}
> > +
> > +_wipe_mounts()
> 
> Local functions don't need the leading "_" in function name.

Ok, removed the leading "_" from _wipe_mounts() and _wipe_scratch().

> 
> > +{
> > +	umount -l -q "${SCRATCH_MNT}/idmapped" >/dev/null 2>&1
> 
> $UMOUNT_PROG, and you already dump stderr and stdout to /dev/null, -q
> seems not necessary.

Removed "-q" option.

> 
> > +	_scratch_unmount >/dev/null 2>&1
> > +}
> > +
> > +_wipe_scratch()
> > +{
> > +	_wipe_mounts
> > +	_scratch_mkfs_xfs >>$seqres.full 2>&1
> > +}
> > +
> > +_qmount_idmapped()
> > +{
> > +	_wipe_mounts
> > +	_try_scratch_mount || _fail "qmount failed"
> > +
> > +	mkdir -p "${SCRATCH_MNT}/unmapped"
> > +	mkdir -p "${SCRATCH_MNT}/idmapped"
> > +
> > +	$here/src/idmapped-mounts/mount-idmapped --map-mount b:$id:$id2:1 --map-mount b:0:0:1 "$SCRATCH_MNT/unmapped" "$SCRATCH_MNT/idmapped" || _fail "mount-idmapped failed"
> > +
> > +	chmod ugo+rwx $SCRATCH_MNT
> > +	chmod ugo+rwx $SCRATCH_MNT/unmapped
> > +	chmod ugo+rwx $SCRATCH_MNT/idmapped
> > +}
> > +
> > +test_xfs_quota()
> > +{
> > +	# init quota
> > +	echo "init quota limit and timer, and dump it"
> > +	if [ "$idmapped" -eq 1 ]; then
> > +		echo "create_files_idmapped 1024k 15"; create_files_idmapped 1024k 15
> > +	else
> > +		echo "create_files_unmapped 1024k 15"; create_files_unmapped 1024k 15
> > +	fi
> > +	echo "quota remount"; _qmount_idmapped
> > +	echo ; test_quot
> > +	echo ; test_timer
> > +	echo ; test_limit 512k 2048k 10 20
> > +	echo ; test_dump
> > +
> > +	# report options test
> > +	echo "report options test"
> > +	echo ; test_report
> > +	echo "-N option"; test_report -N
> > +	echo "-L -U options"; test_report -L $id -U $id
> > +	echo "-t option"; test_report -t
> > +	echo "-n option"; test_report -n
> > +	echo "-h option"; test_report -h
> > +
> > +	# quot options test
> > +	echo "quot options test"
> > +	echo ; test_quot
> > +	echo "-f option"; test_quot -f $tmp.quot
> > +	cat $tmp.quot | filter_quot
> > +	echo "-n option"; test_quot -n
> > +
> > +	# quota options test
> > +	echo ; test_quota
> > +	echo "-f option"; test_quota -f $tmp.quota
> > +	cat $tmp.quota | filter_quota
> > +	echo "-N option"; test_quota -N
> > +	echo "-n option"; test_quota -n
> > +	echo "-h option"; test_quota -h
> > +
> > +	# disable/enable test
> > +	echo "disable quota"
> > +	echo ; test_disable
> > +	echo ; test_report -N
> > +	echo "expect a remove error at here"; test_remove
> > +	echo ; test_enable
> > +	echo ; test_report -N
> > +
> > +	# off and remove test
> > +	echo "off and remove test"
> > +	echo ; test_limit 100m 100m 100 100
> > +	echo ; test_quota -N
> > +	echo ; test_off
> > +	echo ; test_state
> > +	echo ; test_remove
> > +	echo ; test_report -N
> > +	echo "quota remount"; _qmount_idmapped
> > +	echo ; test_report -N
> > +
> > +	# restore test
> > +	echo "restore quota"
> > +	echo ; test_restore
> > +	echo ; test_report -N
> > +	echo ; test_state
> > +	echo "cleanup files"; clean_files
> > +}
> > +
> > +echo "----------------------- uquota,sync,unmapped ---------------------------"
> > +_wipe_scratch
> > +_qmount_option "uquota,sync"
> > +type=u
> > +id=$uqid
> > +id2=$uqid2
> > +idmapped=0
> > +_qmount_idmapped
> > +test_xfs_quota
> > +
> > +echo "----------------------- uquota,sync,idmapped ---------------------------"
> > +_wipe_scratch
> > +_qmount_option "uquota,sync"
> > +type=u
> > +id=$uqid
> > +id2=$uqid2
> > +idmapped=1
> > +_qmount_idmapped
> > +test_xfs_quota
> > +
> > +echo "----------------------- gquota,sync,unmapped ---------------------------"
> > +_wipe_scratch
> > +_qmount_option "gquota,sync"
> > +type=g
> > +id=$gqid
> > +id2=$gqid2
> > +idmapped=0
> > +_qmount_idmapped
> > +test_xfs_quota
> > +
> > +echo "----------------------- gquota,sync,idmapped ---------------------------"
> > +_wipe_scratch
> > +_qmount_option "gquota,sync"
> > +type=g
> > +id=$gqid
> > +id2=$gqid2
> > +idmapped=1
> > +_qmount_idmapped
> > +test_xfs_quota
> > +
> > +umount -l -q "${SCRATCH_MNT}/idmapped" >/dev/null 2>&1
> > +_scratch_unmount
> 
> Could use wipe_mounts here?

Yes, added.

Christian

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v9 4/4] xfs/530: quotas on idmapped mounts
  2021-03-21 14:51   ` Eryu Guan
@ 2021-03-22 12:25     ` Christian Brauner
  0 siblings, 0 replies; 22+ messages in thread
From: Christian Brauner @ 2021-03-22 12:25 UTC (permalink / raw)
  To: Eryu Guan; +Cc: Christoph Hellwig, Darrick J . Wong, fstests, David Howells

On Sun, Mar 21, 2021 at 10:51:39PM +0800, Eryu Guan wrote:
> On Tue, Mar 16, 2021 at 11:36:27AM +0100, Christian Brauner wrote:
> > This is basically a re-implementation of xfs/050 but each file creation
> > call is done through an idmapped mount which verifies that the semantics
> > are identical even when the mount is idmapped.
> > 
> > Cc: Christoph Hellwig <hch@lst.de>
> > Cc: Darrick J. Wong <djwong@kernel.org>
> > Cc: fstests@vger.kernel.org
> > Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
> > ---
> > /* v1 */
> > patch not present
> > 
> > /* v2 */
> > patch not present
> > 
> > /* v3 */
> > patch not present
> > 
> > /* v4 */
> > patch not present
> > 
> > /* v5 */
> > patch not present
> > 
> > /* v6 */
> > patch not present
> > 
> > /* v7 */
> > patch not present
> > 
> > /* v8 */
> > patch introduced
> > 
> > /* v9 */
> > - Christian Brauner <christian.brauner@ubuntu.com>:
> >   - Rebased onto current master.
> > ---
> >  tests/xfs/530     | 274 ++++++++++++++++++++++++++++++++++++++++++++++
> >  tests/xfs/530.out | 129 ++++++++++++++++++++++
> >  tests/xfs/group   |   1 +
> >  3 files changed, 404 insertions(+)
> >  create mode 100644 tests/xfs/530
> >  create mode 100644 tests/xfs/530.out
> > 
> > diff --git a/tests/xfs/530 b/tests/xfs/530
> > new file mode 100644
> > index 00000000..9e10c40d
> > --- /dev/null
> > +++ b/tests/xfs/530
> > @@ -0,0 +1,274 @@
> > +#! /bin/bash
> > +# SPDX-License-Identifier: GPL-2.0+
> > +#
> > +# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
> > +# All Rights Reserved.
> > +#
> > +# FS QA Test No. 530
> > +#
> > +# Exercises basic XFS quota functionality
> > +#       uquota, gquota, uqnoenforce, gqnoenforce
> > +#
> > +seq=`basename $0`
> > +seqres=$RESULT_DIR/$seq
> > +echo "QA output created by $seq"
> > +
> > +here=`pwd`
> > +tmp=/tmp/$$
> > +status=1	# failure is the default!
> > +
> > +# get standard environment, filters and checks
> > +. ./common/rc
> > +. ./common/filter
> > +. ./common/quota
> > +
> > +_cleanup()
> > +{
> > +	cd /
> > +	_scratch_unmount 2>/dev/null
> > +	rm -f $tmp.*
> > +}
> > +trap "_cleanup; exit \$status" 0 1 2 3 15
> > +
> > +# real QA test starts here
> > +_supported_fs xfs
> > +
> > +cp /dev/null $seqres.full
> > +chmod a+rwx $seqres.full	# arbitrary users will write here
> > +
> > +_require_scratch
> > +_require_xfs_quota
> > +_require_user fsgqa
> > +
> > +_scratch_mkfs >/dev/null 2>&1
> > +_scratch_mount
> > +bsize=$(_get_file_block_size $SCRATCH_MNT)
> > +_scratch_unmount
> > +
> > +bsoft=$(( 200 * $bsize ))
> > +bhard=$(( 1000 * $bsize ))
> > +isoft=4
> > +ihard=10
> > +
> > +_filter_report()
> 
> Seems this is mostly copied from _filter_quota_report(), just remove the
> $id and $bsize check in the beginning, is it possible to re-use
> _filter_quota_report?

Yeah, I think even without that check I can just directly use
_filter_quota_report. Not sure why I didn't use it right away. Probably
an oversight on my part that it was available as a generic helper.
Thanks for the pointer.

> 
> > +{
> > +	tr -s '[:space:]' | \
> > +	perl -npe '
> > +		s/^\#'$id' /[NAME] /g;
> > +		s/^\#0 \d+ /[ROOT] 0 /g;
> > +		s/6 days/7 days/g' |
> > +	perl -npe '
> > +		$val = 0;
> > +		if ($ENV{'LARGE_SCRATCH_DEV'}) {
> > +			$val = $ENV{'NUM_SPACE_FILES'};
> > +		}
> > +		s/(^\[ROOT\] \S+ \S+ \S+ \S+ \[--------\] )(\S+)/$1@{[$2 - $val]}/g' |
> > +	sed -e 's/ 65535 \[--------\]/ 00 \[--------\]/g' |
> > +	perl -npe '
> > +		s|^(.*?) (\d+) (\d+) (\d+)|$1 @{[$2 * 1024 /'$bsize']} @{[$3 * 1024 /'$bsize']} @{[$4 * 1024 /'$bsize']}|'
> > +}
> > +
> > +# The actual point at which limit enforcement takes place for the
> > +# hard block limit is variable depending on filesystem blocksize,
> > +# and iosize.  What we want to test is that the limit is enforced
> > +# (ie. blksize less than limit but not unduly less - ~85% is kind)
> > +# nowadays we actually get much closer to the limit before EDQUOT.
> > +#
> > +_filter_and_check_blks()
> > +{
> > +	perl -npe '
> > +		if (/^\#'$id'\s+(\d+)/ && '$enforce') {
> > +			$maximum = '$bhard';
> > +			$minimum = '$bhard' * 85/100;
> > +			$used = $1 * 1024;
> > +			if (($used < $minimum || $used > $maximum) && '$noextsz') {
> > +				printf(" URK %d: %d is out of range! [%d,%d]\n",
> > +					'$id', $used, $minimum, $maximum);
> > +			}
> > +			s/^(\#'$id'\s+)(\d+)/\1 =OK=/g;
> > +		}
> > +	' | _filter_report
> > +}
> > +
> > +_qsetup()
> > +{
> > +	opt=$1
> > +	enforce=0
> > +	if [ $opt = "u" -o $opt = "uno" ]; then
> > +		type=u
> > +		eval `_choose_uid`
> > +	elif [ $opt = "g" -o $opt = "gno" ]; then
> > +		type=g
> > +		eval `_choose_gid`
> > +	elif [ $opt = "p" -o $opt = "pno" ]; then
> > +		type=p
> > +		eval `_choose_prid`
> > +	fi
> > +	[ $opt = "u" -o $opt = "g" -o $opt = "p" ] && enforce=1
> > +
> > +	echo "Using type=$type id=$id" >> $seqres.full
> > +}
> 
> And above two functions are copied from xfs/050 and xfs/299, could you
> please re-factor them into a common helper so they could be shared by
> tests?

Yeah, I've done that.

> 
> > +
> > +_mount_idmapped()
> 
> Seems this could be made into a generic _scratch_mount_idmapped()
> helper.

I've added this as a simpler helper in its current form with the ability
to map a single id for now. We can later add a more complex helper if we
need to but handling variable number of arguments isn't really pleasant
so I'd defer this part until we need it.

> 
> > +{
> > +	if [ "$type" == "u" ]; then
> > +		# This means root will be able to create files as uid %id in
> > +		# the underlying filesystem by going through the idmapped mount.
> > +		$here/src/idmapped-mounts/mount-idmapped --map-mount u:0:$id:1 \
> > +							 --map-mount u:$id:0:1 \
> > +							 --map-mount g:0:0:1 \
> > +							 "$SCRATCH_MNT" "$SCRATCH_MNT" || _fail "mount-idmapped failed"
> > +	else
> > +		# This means root will be able to create files as gid %id in
> > +		# the underlying filesystem by going through the idmapped mount.
> > +		$here/src/idmapped-mounts/mount-idmapped --map-mount g:0:$id:1 \
> > +							 --map-mount g:$id:0:1 \
> > +							 --map-mount u:0:0:1 \
> > +							 "$SCRATCH_MNT" "$SCRATCH_MNT" || _fail "mount-idmapped failed"
> > +	fi
> > +}
> > +
> > +_umount_idmapped()
> 
> And turn this into _scratch_umount_idmapped()

Added.

> 
> > +{
> > +	# ls -al "$SCRATCH_MNT"
> 
> Could be removed?

Yeah, debugging residual.

> 
> > +	umount -l -q "${SCRATCH_MNT}" >/dev/null 2>&1
> 
> s/umount/$UMOUNT_PROG/. And it'd be good to add some comments to desribe
> why lazy umount is needed.

We don't need the lazy umount. I just use this as my default umount
strategy.

> 
> > +	# ls -al "$SCRATCH_MNT"
> 
> Could be removed?
> 
> > +}
> > +
> > +_exercise()
> 
> Remove the leading "_" for local functions.

Done and rename to run_test()

Thanks!
Christian

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2021-03-22 12:26 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-16 10:36 [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests Christian Brauner
2021-03-16 10:36 ` [PATCH v9 1/4] generic/631: add test for detached mount propagation Christian Brauner
2021-03-18  6:23   ` Christoph Hellwig
2021-03-18 15:02     ` Darrick J. Wong
2021-03-18 15:14       ` Christian Brauner
2021-03-18 15:57         ` Darrick J. Wong
2021-03-18 16:31           ` Darrick J. Wong
2021-03-21 14:28   ` Eryu Guan
2021-03-22 10:00     ` Christian Brauner
2021-03-16 10:36 ` [PATCH v9 3/4] xfs/529: quotas and idmapped mounts Christian Brauner
2021-03-18  6:24   ` Christoph Hellwig
2021-03-21 14:42   ` Eryu Guan
2021-03-22 10:11     ` Christian Brauner
2021-03-16 10:36 ` [PATCH v9 4/4] xfs/530: quotas on " Christian Brauner
2021-03-18  6:24   ` Christoph Hellwig
2021-03-21 14:51   ` Eryu Guan
2021-03-22 12:25     ` Christian Brauner
     [not found] ` <20210316103627.2954121-3-christian.brauner@ubuntu.com>
2021-03-18  6:23   ` [PATCH v9 2/4] generic/632: add fstests for " Christoph Hellwig
2021-03-21 14:26 ` [PATCH v9 0/4 REBASED] fstests: add idmapped mounts tests Eryu Guan
2021-03-21 15:32   ` Christian Brauner
2021-03-22  2:37     ` Eryu Guan
2021-03-22  9:49       ` Christian Brauner

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.