linux-fsdevel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/8] Add quota support to UBIFS
@ 2020-01-24 13:13 Sascha Hauer
  2020-01-24 13:13 ` [PATCH 1/8] quota: Allow to pass mount path to quotactl Sascha Hauer
                   ` (7 more replies)
  0 siblings, 8 replies; 19+ messages in thread
From: Sascha Hauer @ 2020-01-24 13:13 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-mtd, Jan Kara, Richard Weinberger, kernel, Sascha Hauer


This series adds quota support to UBIFS.

Integrated more review feedback this time, see changelog.

This series follows a very simple approach to quota: Neither the quota
limits nor the quota usage are ever written to the medium. The quota
usage is reconstructed from the filesystem during mount time. The quota
limits must be set by the user each time after mount. This is probably
not very convenient for systems that are used interactively, but UBIFS
is targetted to embedded systems and here running a script after mount
shouldn't be a problem. This of course isn't the way quota was thought
to be, but I believe this is a good compromise for a feature that I predict
is only rarely used on UBIFS. The big upside of this approach is that
no on-disk format changes are required and thus we can't get any
broken/corrupt filesystems because of quota support. Reconstructing the
quota data each time during mount has an noticable but I think for many
cases acceptable time overhead. I mounted a ~56MiB rootfs with 1920 files
which takes around 0.7s longer when quota is enabled.

As UBIFS works on mtd there is no block_device involved. The quotactl
system call requires a path to a block device as argument. To overcome
this we add support for passing the mount point instead. This is done
with a new Q_PATH flag to the quotactl syscall indicating that the special
argument belongs to the mount path rather than a path to the block device
file

The UBIFS quota support itself is based on a series by Dongsheng Yang
posted here:
http://lists.infradead.org/pipermail/linux-mtd/2015-September/061812.html
This part hasn't changed much, except that the code for reading and writing
quota files has been dropped.

Sascha

changes since v3:
- implement ubifs_dqblk_find() using ubifs_dqblk_find_next()
- Fix copy/paste errors in flag settings
- drop unnecessary inline declaration
- add ubifs_assert() for catching wrong usage of setflags()
- add helper function for projid checking
- Add a feature flag for supporting projid

changes since v2:
- Rebase on Jans quota-without-inode series
- Use recently introduced vfs_ioc_fssetxattr_check() and simple_fill_fsxattr()
- fix project quota support (was broken in v2 due to upstream changes in UBIFS)
- check for illegal renames due to different project id

Changes since v1:
- Introduce Q_PATH flag to make passing a mountpath explicit
- Do not mess with fs layer as suggested by Al Viro
- create separate usrquota, grpquota and prjquota options rather than just
  a single quota option
- register a UBIFS specific quota_format and use dquot_enable()
- drop "quota: Only module_put the format when existing" which is no
  longer necesary

Sascha Hauer (8):
  quota: Allow to pass mount path to quotactl
  ubifs: move checks and preparation into setflags()
  ubifs: Add support for FS_IOC_FS[SG]ETXATTR ioctls
  ubifs: do not ubifs_inode() on potentially NULL pointer
  ubifs: Factor out ubifs_set_feature_flag()
  ubifs: Add support for project id
  ubifs: export get_znode
  ubifs: Add quota support


Sascha Hauer (8):
  quota: Allow to pass mount path to quotactl
  ubifs: move checks and preparation into setflags()
  ubifs: Add support for FS_IOC_FS[SG]ETXATTR ioctls
  ubifs: do not ubifs_inode() on potentially NULL pointer
  ubifs: Factor out ubifs_set_feature_flag()
  ubifs: Add support for project id
  ubifs: export get_znode
  ubifs: Add quota support

 Documentation/filesystems/ubifs.txt |   7 +-
 fs/quota/quota.c                    |  53 ++-
 fs/ubifs/Makefile                   |   1 +
 fs/ubifs/dir.c                      |  56 ++-
 fs/ubifs/file.c                     |  43 ++
 fs/ubifs/ioctl.c                    | 220 ++++++++--
 fs/ubifs/journal.c                  |   4 +-
 fs/ubifs/quota.c                    | 603 ++++++++++++++++++++++++++++
 fs/ubifs/sb.c                       |  29 +-
 fs/ubifs/super.c                    |  87 +++-
 fs/ubifs/tnc.c                      |  34 +-
 fs/ubifs/ubifs-media.h              |  10 +-
 fs/ubifs/ubifs.h                    |  43 ++
 include/uapi/linux/quota.h          |   2 +
 14 files changed, 1117 insertions(+), 75 deletions(-)
 create mode 100644 fs/ubifs/quota.c

-- 
2.25.0


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

* [PATCH 1/8] quota: Allow to pass mount path to quotactl
  2020-01-24 13:13 [PATCH v4 0/8] Add quota support to UBIFS Sascha Hauer
@ 2020-01-24 13:13 ` Sascha Hauer
  2020-01-27 10:45   ` Jan Kara
  2020-01-24 13:13 ` [PATCH 2/8] ubifs: move checks and preparation into setflags() Sascha Hauer
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 19+ messages in thread
From: Sascha Hauer @ 2020-01-24 13:13 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-mtd, Jan Kara, Richard Weinberger, kernel, Sascha Hauer

This patch introduces the Q_PATH flag to the quotactl cmd argument.
When given, the path given in the special argument to quotactl will
be the mount path where the filesystem is mounted, instead of a path
to the block device.
This is necessary for filesystems which do not have a block device as
backing store. Particularly this is done for upcoming UBIFS support.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 fs/quota/quota.c           | 53 +++++++++++++++++++++++++++-----------
 include/uapi/linux/quota.h |  1 +
 2 files changed, 39 insertions(+), 15 deletions(-)

diff --git a/fs/quota/quota.c b/fs/quota/quota.c
index 5444d3c4d93f..9ef51d02d5a5 100644
--- a/fs/quota/quota.c
+++ b/fs/quota/quota.c
@@ -19,6 +19,7 @@
 #include <linux/types.h>
 #include <linux/writeback.h>
 #include <linux/nospec.h>
+#include <linux/mount.h>
 
 static int check_quotactl_permission(struct super_block *sb, int type, int cmd,
 				     qid_t id)
@@ -821,15 +822,20 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
 {
 	uint cmds, type;
 	struct super_block *sb = NULL;
-	struct path path, *pathp = NULL;
+	struct path file_path, *file_pathp = NULL, sb_path;
 	int ret;
+	bool q_path;
 
 	cmds = cmd >> SUBCMDSHIFT;
 	type = cmd & SUBCMDMASK;
 
+
 	if (type >= MAXQUOTAS)
 		return -EINVAL;
 
+	q_path = cmds & Q_PATH;
+	cmds &= ~Q_PATH;
+
 	/*
 	 * As a special case Q_SYNC can be called without a specific device.
 	 * It will iterate all superblocks that have quota enabled and call
@@ -847,28 +853,45 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
 	 * resolution (think about autofs) and thus deadlocks could arise.
 	 */
 	if (cmds == Q_QUOTAON) {
-		ret = user_path_at(AT_FDCWD, addr, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &path);
+		ret = user_path_at(AT_FDCWD, addr,
+				   LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
+				   &file_path);
 		if (ret)
-			pathp = ERR_PTR(ret);
+			file_pathp = ERR_PTR(ret);
 		else
-			pathp = &path;
+			file_pathp = &file_path;
 	}
 
-	sb = quotactl_block(special, cmds);
-	if (IS_ERR(sb)) {
-		ret = PTR_ERR(sb);
-		goto out;
+	if (q_path) {
+		ret = user_path_at(AT_FDCWD, special,
+				   LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
+				   &sb_path);
+		if (ret)
+			goto out;
+
+		sb = sb_path.mnt->mnt_sb;
+	} else {
+		sb = quotactl_block(special, cmds);
+		if (IS_ERR(sb)) {
+			ret = PTR_ERR(sb);
+			goto out;
+		}
 	}
 
-	ret = do_quotactl(sb, type, cmds, id, addr, pathp);
+	ret = do_quotactl(sb, type, cmds, id, addr, file_pathp);
+
+	if (q_path) {
+		path_put(&sb_path);
+	} else {
+		if (!quotactl_cmd_onoff(cmds))
+			drop_super(sb);
+		else
+			drop_super_exclusive(sb);
+	}
 
-	if (!quotactl_cmd_onoff(cmds))
-		drop_super(sb);
-	else
-		drop_super_exclusive(sb);
 out:
-	if (pathp && !IS_ERR(pathp))
-		path_put(pathp);
+	if (file_pathp && !IS_ERR(file_pathp))
+		path_put(file_pathp);
 	return ret;
 }
 
diff --git a/include/uapi/linux/quota.h b/include/uapi/linux/quota.h
index f17c9636a859..e1787c0df601 100644
--- a/include/uapi/linux/quota.h
+++ b/include/uapi/linux/quota.h
@@ -71,6 +71,7 @@
 #define Q_GETQUOTA 0x800007	/* get user quota structure */
 #define Q_SETQUOTA 0x800008	/* set user quota structure */
 #define Q_GETNEXTQUOTA 0x800009	/* get disk limits and usage >= ID */
+#define Q_PATH     0x400000	/* quotactl special arg contains mount path */
 
 /* Quota format type IDs */
 #define	QFMT_VFS_OLD 1
-- 
2.25.0


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

* [PATCH 2/8] ubifs: move checks and preparation into setflags()
  2020-01-24 13:13 [PATCH v4 0/8] Add quota support to UBIFS Sascha Hauer
  2020-01-24 13:13 ` [PATCH 1/8] quota: Allow to pass mount path to quotactl Sascha Hauer
@ 2020-01-24 13:13 ` Sascha Hauer
  2020-01-24 13:13 ` [PATCH 3/8] ubifs: Add support for FS_IOC_FS[SG]ETXATTR ioctls Sascha Hauer
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Sascha Hauer @ 2020-01-24 13:13 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-mtd, Jan Kara, Richard Weinberger, kernel, Sascha Hauer

setflags() can be reused for upcoming FS_IOC_FS[SG]ETXATTR ioctl support.
In preparation for that move the checks and preparation into that
function so we can reuse them as well.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 fs/ubifs/ioctl.c | 46 +++++++++++++++++++++++++++-------------------
 1 file changed, 27 insertions(+), 19 deletions(-)

diff --git a/fs/ubifs/ioctl.c b/fs/ubifs/ioctl.c
index 5dc5abca11c7..8230dba5fd74 100644
--- a/fs/ubifs/ioctl.c
+++ b/fs/ubifs/ioctl.c
@@ -95,18 +95,35 @@ static int ubifs2ioctl(int ubifs_flags)
 	return ioctl_flags;
 }
 
-static int setflags(struct inode *inode, int flags)
+static int setflags(struct file *file, int flags)
 {
 	int oldflags, err, release;
+	struct inode *inode = file_inode(file);
 	struct ubifs_inode *ui = ubifs_inode(inode);
 	struct ubifs_info *c = inode->i_sb->s_fs_info;
 	struct ubifs_budget_req req = { .dirtied_ino = 1,
 					.dirtied_ino_d = ui->data_len };
 
-	err = ubifs_budget_space(c, &req);
+	if (IS_RDONLY(inode))
+		return -EROFS;
+
+	if (!inode_owner_or_capable(inode))
+		return -EACCES;
+
+	/*
+	 * Make sure the file-system is read-write and make sure it
+	 * will not become read-only while we are changing the flags.
+	 */
+	err = mnt_want_write_file(file);
 	if (err)
 		return err;
 
+	dbg_gen("set flags: %#x, i_flags %#x", flags, inode->i_flags);
+
+	err = ubifs_budget_space(c, &req);
+	if (err)
+		goto out_drop;
+
 	mutex_lock(&ui->ui_mutex);
 	oldflags = ubifs2ioctl(ui->flags);
 	err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
@@ -124,12 +141,18 @@ static int setflags(struct inode *inode, int flags)
 		ubifs_release_budget(c, &req);
 	if (IS_SYNC(inode))
 		err = write_inode_now(inode, 1);
+
+	mnt_drop_write_file(file);
+
 	return err;
 
 out_unlock:
 	ubifs_err(c, "can't modify inode %lu attributes", inode->i_ino);
 	mutex_unlock(&ui->ui_mutex);
 	ubifs_release_budget(c, &req);
+out_drop:
+	mnt_drop_write_file(file);
+
 	return err;
 }
 
@@ -146,12 +169,6 @@ long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 		return put_user(flags, (int __user *) arg);
 
 	case FS_IOC_SETFLAGS: {
-		if (IS_RDONLY(inode))
-			return -EROFS;
-
-		if (!inode_owner_or_capable(inode))
-			return -EACCES;
-
 		if (get_user(flags, (int __user *) arg))
 			return -EFAULT;
 
@@ -161,17 +178,8 @@ long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 		if (!S_ISDIR(inode->i_mode))
 			flags &= ~FS_DIRSYNC_FL;
 
-		/*
-		 * Make sure the file-system is read-write and make sure it
-		 * will not become read-only while we are changing the flags.
-		 */
-		err = mnt_want_write_file(file);
-		if (err)
-			return err;
-		dbg_gen("set flags: %#x, i_flags %#x", flags, inode->i_flags);
-		err = setflags(inode, flags);
-		mnt_drop_write_file(file);
-		return err;
+
+		return setflags(file, flags);
 	}
 	case FS_IOC_SET_ENCRYPTION_POLICY: {
 		struct ubifs_info *c = inode->i_sb->s_fs_info;
-- 
2.25.0


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

* [PATCH 3/8] ubifs: Add support for FS_IOC_FS[SG]ETXATTR ioctls
  2020-01-24 13:13 [PATCH v4 0/8] Add quota support to UBIFS Sascha Hauer
  2020-01-24 13:13 ` [PATCH 1/8] quota: Allow to pass mount path to quotactl Sascha Hauer
  2020-01-24 13:13 ` [PATCH 2/8] ubifs: move checks and preparation into setflags() Sascha Hauer
@ 2020-01-24 13:13 ` Sascha Hauer
  2020-01-24 13:13 ` [PATCH 4/8] ubifs: do not ubifs_inode() on potentially NULL pointer Sascha Hauer
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Sascha Hauer @ 2020-01-24 13:13 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-mtd, Jan Kara, Richard Weinberger, kernel, Sascha Hauer

The FS_IOC_FS[SG]ETXATTR ioctls are an alternative to FS_IOC_[GS]ETFLAGS
with additional features. This patch adds support for these ioctls.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 fs/ubifs/ioctl.c | 105 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 100 insertions(+), 5 deletions(-)

diff --git a/fs/ubifs/ioctl.c b/fs/ubifs/ioctl.c
index 8230dba5fd74..23967031b947 100644
--- a/fs/ubifs/ioctl.c
+++ b/fs/ubifs/ioctl.c
@@ -95,9 +95,46 @@ static int ubifs2ioctl(int ubifs_flags)
 	return ioctl_flags;
 }
 
-static int setflags(struct file *file, int flags)
+/* Transfer xflags flags to internal */
+static unsigned long ubifs_xflags_to_iflags(__u32 xflags)
 {
-	int oldflags, err, release;
+	unsigned long iflags = 0;
+
+	if (xflags & FS_XFLAG_SYNC)
+		iflags |= UBIFS_SYNC_FL;
+	if (xflags & FS_XFLAG_IMMUTABLE)
+		iflags |= UBIFS_IMMUTABLE_FL;
+	if (xflags & FS_XFLAG_APPEND)
+		iflags |= UBIFS_APPEND_FL;
+
+        return iflags;
+}
+
+/* Transfer internal flags to xflags */
+static __u32 ubifs_iflags_to_xflags(unsigned long flags)
+{
+	__u32 xflags = 0;
+
+	if (flags & UBIFS_SYNC_FL)
+		xflags |= FS_XFLAG_SYNC;
+	if (flags & UBIFS_IMMUTABLE_FL)
+		xflags |= FS_XFLAG_IMMUTABLE;
+	if (flags & UBIFS_APPEND_FL)
+		xflags |= FS_XFLAG_APPEND;
+
+        return xflags;
+}
+
+static void ubifs_fill_fsxattr(struct inode *inode, struct fsxattr *fa)
+{
+	struct ubifs_inode *ui = ubifs_inode(inode);
+
+	simple_fill_fsxattr(fa, ubifs_iflags_to_xflags(ui->flags));
+}
+
+static int setflags(struct file *file, int flags, struct fsxattr *fa)
+{
+	int ubi_flags, oldflags, err, release;
 	struct inode *inode = file_inode(file);
 	struct ubifs_inode *ui = ubifs_inode(inode);
 	struct ubifs_info *c = inode->i_sb->s_fs_info;
@@ -110,6 +147,13 @@ static int setflags(struct file *file, int flags)
 	if (!inode_owner_or_capable(inode))
 		return -EACCES;
 
+	ubifs_assert(c, !(flags && fa));
+
+	if (fa)
+		ubi_flags = ubifs_xflags_to_iflags(fa->fsx_xflags);
+	else
+		ubi_flags = ioctl2ubifs(flags);
+
 	/*
 	 * Make sure the file-system is read-write and make sure it
 	 * will not become read-only while we are changing the flags.
@@ -126,11 +170,21 @@ static int setflags(struct file *file, int flags)
 
 	mutex_lock(&ui->ui_mutex);
 	oldflags = ubifs2ioctl(ui->flags);
-	err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
+	err = vfs_ioc_setflags_prepare(inode, oldflags, ubifs2ioctl(ubi_flags));
 	if (err)
 		goto out_unlock;
 
-	ui->flags = ioctl2ubifs(flags);
+	if (fa) {
+		struct fsxattr old_fa;
+
+		ubifs_fill_fsxattr(inode, &old_fa);
+
+		err = vfs_ioc_fssetxattr_check(inode, &old_fa, fa);
+		if (err)
+			goto out_unlock;
+	}
+
+	ui->flags = ubi_flags;
 	ubifs_set_inode_flags(inode);
 	inode->i_ctime = current_time(inode);
 	release = ui->dirty;
@@ -156,6 +210,41 @@ static int setflags(struct file *file, int flags)
 	return err;
 }
 
+static int ubifs_ioc_fsgetxattr(struct file *file, void __user *arg)
+{
+	struct inode *inode = file_inode(file);
+	struct fsxattr fa;
+
+	ubifs_fill_fsxattr(inode, &fa);
+
+	if (copy_to_user(arg, &fa, sizeof(fa)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int check_xflags(unsigned int flags)
+{
+	if (flags & ~(FS_XFLAG_SYNC | FS_XFLAG_IMMUTABLE | FS_XFLAG_APPEND))
+		return -EOPNOTSUPP;
+	return 0;
+}
+
+static int ubifs_ioc_fssetxattr(struct file *file, const void __user *arg)
+{
+	struct fsxattr fa;
+	int err;
+
+	if (copy_from_user(&fa, (struct fsxattr __user *)arg, sizeof(fa)))
+		return -EFAULT;
+
+	err = check_xflags(fa.fsx_xflags);
+	if (err)
+		return err;
+
+	return setflags(file, 0, &fa);
+}
+
 long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
 	int flags, err;
@@ -179,7 +268,7 @@ long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 			flags &= ~FS_DIRSYNC_FL;
 
 
-		return setflags(file, flags);
+		return setflags(file, flags, NULL);
 	}
 	case FS_IOC_SET_ENCRYPTION_POLICY: {
 		struct ubifs_info *c = inode->i_sb->s_fs_info;
@@ -208,6 +297,12 @@ long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
 		return fscrypt_ioctl_get_key_status(file, (void __user *)arg);
 
+	case FS_IOC_FSGETXATTR:
+		return ubifs_ioc_fsgetxattr(file, (void __user *)arg);
+
+	case FS_IOC_FSSETXATTR:
+		return ubifs_ioc_fssetxattr(file, (const void __user *)arg);
+
 	default:
 		return -ENOTTY;
 	}
-- 
2.25.0


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

* [PATCH 4/8] ubifs: do not ubifs_inode() on potentially NULL pointer
  2020-01-24 13:13 [PATCH v4 0/8] Add quota support to UBIFS Sascha Hauer
                   ` (2 preceding siblings ...)
  2020-01-24 13:13 ` [PATCH 3/8] ubifs: Add support for FS_IOC_FS[SG]ETXATTR ioctls Sascha Hauer
@ 2020-01-24 13:13 ` Sascha Hauer
  2020-01-24 13:13 ` [PATCH 5/8] ubifs: Factor out ubifs_set_feature_flag() Sascha Hauer
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Sascha Hauer @ 2020-01-24 13:13 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-mtd, Jan Kara, Richard Weinberger, kernel, Sascha Hauer

new_inode() may return NULL, so only derefence the return inode when
non NULL. This is merely a cleanup as calling ubifs_inode() on a NULL
pointer doesn't do any harm, only using the return value would.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 fs/ubifs/dir.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c
index 0b98e3c8b461..cfce5fee9262 100644
--- a/fs/ubifs/dir.c
+++ b/fs/ubifs/dir.c
@@ -95,10 +95,10 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
 	}
 
 	inode = new_inode(c->vfs_sb);
-	ui = ubifs_inode(inode);
 	if (!inode)
 		return ERR_PTR(-ENOMEM);
 
+	ui = ubifs_inode(inode);
 	/*
 	 * Set 'S_NOCMTIME' to prevent VFS form updating [mc]time of inodes and
 	 * marking them dirty in file write path (see 'file_update_time()').
-- 
2.25.0


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

* [PATCH 5/8] ubifs: Factor out ubifs_set_feature_flag()
  2020-01-24 13:13 [PATCH v4 0/8] Add quota support to UBIFS Sascha Hauer
                   ` (3 preceding siblings ...)
  2020-01-24 13:13 ` [PATCH 4/8] ubifs: do not ubifs_inode() on potentially NULL pointer Sascha Hauer
@ 2020-01-24 13:13 ` Sascha Hauer
  2020-01-24 13:13 ` [PATCH 6/8] ubifs: Add support for project id Sascha Hauer
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Sascha Hauer @ 2020-01-24 13:13 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-mtd, Jan Kara, Richard Weinberger, kernel, Sascha Hauer

The code setting a feature flag can be reused for upcoming projid
support. Factor out a function to share the code.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 fs/ubifs/sb.c | 24 +++++++++++++++---------
 1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/fs/ubifs/sb.c b/fs/ubifs/sb.c
index 2b7c04bf8983..628b9adec865 100644
--- a/fs/ubifs/sb.c
+++ b/fs/ubifs/sb.c
@@ -926,28 +926,34 @@ int ubifs_fixup_free_space(struct ubifs_info *c)
 	return err;
 }
 
-int ubifs_enable_encryption(struct ubifs_info *c)
+static int ubifs_set_feature_flag(struct ubifs_info *c, unsigned int flag)
 {
-	int err;
 	struct ubifs_sb_node *sup = c->sup_node;
 
-	if (!IS_ENABLED(CONFIG_FS_ENCRYPTION))
-		return -EOPNOTSUPP;
-
-	if (c->encrypted)
+	if ((sup->flags & cpu_to_le32(flag)) == cpu_to_le32(flag))
 		return 0;
 
 	if (c->ro_mount || c->ro_media)
 		return -EROFS;
 
 	if (c->fmt_version < 5) {
-		ubifs_err(c, "on-flash format version 5 is needed for encryption");
+		ubifs_err(c, "on-flash format version 5 is needed for feature flags");
 		return -EINVAL;
 	}
 
-	sup->flags |= cpu_to_le32(UBIFS_FLG_ENCRYPTION);
+	sup->flags |= cpu_to_le32(flag);
+
+	return ubifs_write_sb_node(c, sup);
+}
+
+int ubifs_enable_encryption(struct ubifs_info *c)
+{
+	int err;
+
+	if (!IS_ENABLED(CONFIG_FS_ENCRYPTION))
+		return -EOPNOTSUPP;
 
-	err = ubifs_write_sb_node(c, sup);
+	err = ubifs_set_feature_flag(c, UBIFS_FLG_ENCRYPTION);
 	if (!err)
 		c->encrypted = 1;
 
-- 
2.25.0


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

* [PATCH 6/8] ubifs: Add support for project id
  2020-01-24 13:13 [PATCH v4 0/8] Add quota support to UBIFS Sascha Hauer
                   ` (4 preceding siblings ...)
  2020-01-24 13:13 ` [PATCH 5/8] ubifs: Factor out ubifs_set_feature_flag() Sascha Hauer
@ 2020-01-24 13:13 ` Sascha Hauer
  2020-01-24 13:13 ` [PATCH 7/8] ubifs: export get_znode Sascha Hauer
  2020-01-24 13:13 ` [PATCH 8/8] ubifs: Add quota support Sascha Hauer
  7 siblings, 0 replies; 19+ messages in thread
From: Sascha Hauer @ 2020-01-24 13:13 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-mtd, Jan Kara, Richard Weinberger, kernel, Sascha Hauer

The project id is necessary for quota project id support. This adds
support for the project id to UBIFS as well as support for the
FS_PROJINHERIT_FL flag.

This includes a change for the UBIFS on-disk format. struct
ubifs_ino_node gains a project id number and a UBIFS_PROJINHERIT_FL
flag. A feature flag is added to prevent older UBIFS implementations
from mounting images with project ids. The feature flag is set when
a projid is set for the first time.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 fs/ubifs/dir.c         | 36 +++++++++++++++++++++++++++++++++--
 fs/ubifs/ioctl.c       | 43 ++++++++++++++++++++++++++++++++++++++++--
 fs/ubifs/journal.c     |  2 +-
 fs/ubifs/sb.c          |  5 +++++
 fs/ubifs/super.c       |  1 +
 fs/ubifs/ubifs-media.h | 10 +++++++---
 fs/ubifs/ubifs.h       |  5 +++++
 7 files changed, 94 insertions(+), 8 deletions(-)

diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c
index cfce5fee9262..06d9192000cb 100644
--- a/fs/ubifs/dir.c
+++ b/fs/ubifs/dir.c
@@ -56,7 +56,8 @@ static int inherit_flags(const struct inode *dir, umode_t mode)
 		 */
 		return 0;
 
-	flags = ui->flags & (UBIFS_COMPR_FL | UBIFS_SYNC_FL | UBIFS_DIRSYNC_FL);
+	flags = ui->flags & (UBIFS_COMPR_FL | UBIFS_SYNC_FL | UBIFS_DIRSYNC_FL |
+			     UBIFS_PROJINHERIT_FL);
 	if (!S_ISDIR(mode))
 		/* The "DIRSYNC" flag only applies to directories */
 		flags &= ~UBIFS_DIRSYNC_FL;
@@ -112,6 +113,11 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
 			 current_time(inode);
 	inode->i_mapping->nrpages = 0;
 
+	if (ubifs_inode(dir)->flags & UBIFS_PROJINHERIT_FL)
+		ui->projid = ubifs_inode(dir)->projid;
+	else
+		ui->projid = make_kprojid(&init_user_ns, UBIFS_DEF_PROJID);
+
 	switch (mode & S_IFMT) {
 	case S_IFREG:
 		inode->i_mapping->a_ops = &ubifs_file_address_operations;
@@ -705,6 +711,9 @@ static int ubifs_link(struct dentry *old_dentry, struct inode *dir,
 	ubifs_assert(c, inode_is_locked(dir));
 	ubifs_assert(c, inode_is_locked(inode));
 
+	if (!projid_eq(dir_ui->projid, ui->projid))
+                return -EXDEV;
+
 	err = fscrypt_prepare_link(old_dentry, dir, dentry);
 	if (err)
 		return err;
@@ -1538,6 +1547,20 @@ static int ubifs_xrename(struct inode *old_dir, struct dentry *old_dentry,
 	return err;
 }
 
+static int ubifs_rename_check_projid(struct inode *dir, struct inode *inode)
+{
+	struct ubifs_inode *ud = ubifs_inode(dir);
+	struct ubifs_inode *ui = ubifs_inode(inode);
+
+	if (!(ud->flags & UBIFS_PROJINHERIT_FL))
+		return 0;
+
+	if (projid_eq(ud->projid, ui->projid))
+		return 0;
+
+	return -EXDEV;
+}
+
 static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
 			struct inode *new_dir, struct dentry *new_dentry,
 			unsigned int flags)
@@ -1556,8 +1579,17 @@ static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
 	if (err)
 		return err;
 
-	if (flags & RENAME_EXCHANGE)
+	err = ubifs_rename_check_projid(new_dir, old_dentry->d_inode);
+	if (err)
+		return err;
+
+	if (flags & RENAME_EXCHANGE) {
+		err = ubifs_rename_check_projid(old_dir, new_dentry->d_inode);
+		if (err)
+			return err;
+
 		return ubifs_xrename(old_dir, old_dentry, new_dir, new_dentry);
+	}
 
 	return do_rename(old_dir, old_dentry, new_dir, new_dentry, flags);
 }
diff --git a/fs/ubifs/ioctl.c b/fs/ubifs/ioctl.c
index 23967031b947..e5c684b6d7a7 100644
--- a/fs/ubifs/ioctl.c
+++ b/fs/ubifs/ioctl.c
@@ -19,7 +19,7 @@
 /* Need to be kept consistent with checked flags in ioctl2ubifs() */
 #define UBIFS_SUPPORTED_IOCTL_FLAGS \
 	(FS_COMPR_FL | FS_SYNC_FL | FS_APPEND_FL | \
-	 FS_IMMUTABLE_FL | FS_DIRSYNC_FL)
+	 FS_IMMUTABLE_FL | FS_DIRSYNC_FL | FS_PROJINHERIT_FL)
 
 /**
  * ubifs_set_inode_flags - set VFS inode flags.
@@ -66,6 +66,8 @@ static int ioctl2ubifs(int ioctl_flags)
 		ubifs_flags |= UBIFS_IMMUTABLE_FL;
 	if (ioctl_flags & FS_DIRSYNC_FL)
 		ubifs_flags |= UBIFS_DIRSYNC_FL;
+	if (ioctl_flags & FS_PROJINHERIT_FL)
+		ubifs_flags |= UBIFS_PROJINHERIT_FL;
 
 	return ubifs_flags;
 }
@@ -91,6 +93,8 @@ static int ubifs2ioctl(int ubifs_flags)
 		ioctl_flags |= FS_IMMUTABLE_FL;
 	if (ubifs_flags & UBIFS_DIRSYNC_FL)
 		ioctl_flags |= FS_DIRSYNC_FL;
+	if (ubifs_flags & UBIFS_PROJINHERIT_FL)
+		ioctl_flags |= FS_PROJINHERIT_FL;
 
 	return ioctl_flags;
 }
@@ -106,6 +110,8 @@ static unsigned long ubifs_xflags_to_iflags(__u32 xflags)
 		iflags |= UBIFS_IMMUTABLE_FL;
 	if (xflags & FS_XFLAG_APPEND)
 		iflags |= UBIFS_APPEND_FL;
+	if (xflags & FS_XFLAG_PROJINHERIT)
+		iflags |= UBIFS_PROJINHERIT_FL;
 
         return iflags;
 }
@@ -121,15 +127,34 @@ static __u32 ubifs_iflags_to_xflags(unsigned long flags)
 		xflags |= FS_XFLAG_IMMUTABLE;
 	if (flags & UBIFS_APPEND_FL)
 		xflags |= FS_XFLAG_APPEND;
+	if (flags & UBIFS_PROJINHERIT_FL)
+		xflags |= FS_XFLAG_PROJINHERIT;
 
         return xflags;
 }
 
+static int ubifs_ioc_setproject(struct file *file, __u32 projid)
+{
+	struct inode *inode = file_inode(file);
+	struct ubifs_inode *ui = ubifs_inode(inode);
+	kprojid_t kprojid;
+
+	kprojid = make_kprojid(&init_user_ns, (projid_t)projid);
+	if (projid_eq(kprojid, ui->projid))
+		return 0;
+
+	ui->projid = kprojid;
+
+	return 0;
+}
+
 static void ubifs_fill_fsxattr(struct inode *inode, struct fsxattr *fa)
 {
 	struct ubifs_inode *ui = ubifs_inode(inode);
 
 	simple_fill_fsxattr(fa, ubifs_iflags_to_xflags(ui->flags));
+
+	fa->fsx_projid = (__u32)from_kprojid(&init_user_ns, ui->projid);
 }
 
 static int setflags(struct file *file, int flags, struct fsxattr *fa)
@@ -140,6 +165,7 @@ static int setflags(struct file *file, int flags, struct fsxattr *fa)
 	struct ubifs_info *c = inode->i_sb->s_fs_info;
 	struct ubifs_budget_req req = { .dirtied_ino = 1,
 					.dirtied_ino_d = ui->data_len };
+	__u32 projid = UBIFS_DEF_PROJID;
 
 	if (IS_RDONLY(inode))
 		return -EROFS;
@@ -182,9 +208,21 @@ static int setflags(struct file *file, int flags, struct fsxattr *fa)
 		err = vfs_ioc_fssetxattr_check(inode, &old_fa, fa);
 		if (err)
 			goto out_unlock;
+
+		projid = fa->fsx_projid;
+
+		err = ubifs_ioc_setproject(file, projid);
+		if (err)
+			goto out_unlock;
 	}
 
 	ui->flags = ubi_flags;
+	if (ui->flags & UBIFS_FLG_PROJID || projid != UBIFS_DEF_PROJID) {
+		err = ubifs_enable_projid(c);
+		if (err)
+			goto out_unlock;
+	}
+
 	ubifs_set_inode_flags(inode);
 	inode->i_ctime = current_time(inode);
 	release = ui->dirty;
@@ -225,7 +263,8 @@ static int ubifs_ioc_fsgetxattr(struct file *file, void __user *arg)
 
 static int check_xflags(unsigned int flags)
 {
-	if (flags & ~(FS_XFLAG_SYNC | FS_XFLAG_IMMUTABLE | FS_XFLAG_APPEND))
+	if (flags & ~(FS_XFLAG_SYNC | FS_XFLAG_IMMUTABLE | FS_XFLAG_APPEND |
+		      FS_XFLAG_PROJINHERIT))
 		return -EOPNOTSUPP;
 	return 0;
 }
diff --git a/fs/ubifs/journal.c b/fs/ubifs/journal.c
index 388fe8f5dc51..0a46eefd98a0 100644
--- a/fs/ubifs/journal.c
+++ b/fs/ubifs/journal.c
@@ -54,7 +54,6 @@
  */
 static inline void zero_ino_node_unused(struct ubifs_ino_node *ino)
 {
-	memset(ino->padding1, 0, 4);
 	memset(ino->padding2, 0, 26);
 }
 
@@ -469,6 +468,7 @@ static void pack_inode(struct ubifs_info *c, struct ubifs_ino_node *ino,
 	ino->xattr_cnt   = cpu_to_le32(ui->xattr_cnt);
 	ino->xattr_size  = cpu_to_le32(ui->xattr_size);
 	ino->xattr_names = cpu_to_le32(ui->xattr_names);
+	ino->projid = cpu_to_le32(from_kprojid(&init_user_ns, ui->projid));
 	zero_ino_node_unused(ino);
 
 	/*
diff --git a/fs/ubifs/sb.c b/fs/ubifs/sb.c
index 628b9adec865..8f8a8f72d532 100644
--- a/fs/ubifs/sb.c
+++ b/fs/ubifs/sb.c
@@ -959,3 +959,8 @@ int ubifs_enable_encryption(struct ubifs_info *c)
 
 	return err;
 }
+
+int ubifs_enable_projid(struct ubifs_info *c)
+{
+	return ubifs_set_feature_flag(c, UBIFS_FLG_PROJID);
+}
diff --git a/fs/ubifs/super.c b/fs/ubifs/super.c
index 5e1e8ec0589e..e702fb4e92cf 100644
--- a/fs/ubifs/super.c
+++ b/fs/ubifs/super.c
@@ -141,6 +141,7 @@ struct inode *ubifs_iget(struct super_block *sb, unsigned long inum)
 	ui->xattr_size  = le32_to_cpu(ino->xattr_size);
 	ui->xattr_names = le32_to_cpu(ino->xattr_names);
 	ui->synced_i_size = ui->ui_size = inode->i_size;
+	ui->projid = make_kprojid(&init_user_ns, le32_to_cpu(ino->projid));
 
 	ui->xattr = (ui->flags & UBIFS_XATTR_FL) ? 1 : 0;
 
diff --git a/fs/ubifs/ubifs-media.h b/fs/ubifs/ubifs-media.h
index 3c9792cbb6ff..59077e7fddbc 100644
--- a/fs/ubifs/ubifs-media.h
+++ b/fs/ubifs/ubifs-media.h
@@ -316,6 +316,7 @@ enum {
  * UBIFS_DIRSYNC_FL: I/O on this directory inode has to be synchronous
  * UBIFS_XATTR_FL: this inode is the inode for an extended attribute value
  * UBIFS_CRYPT_FL: use encryption for this inode
+ * UBIFS_PROJINHERIT_FL: Create with parents projid
  *
  * Note, these are on-flash flags which correspond to ioctl flags
  * (@FS_COMPR_FL, etc). They have the same values now, but generally, do not
@@ -329,6 +330,7 @@ enum {
 	UBIFS_DIRSYNC_FL   = 0x10,
 	UBIFS_XATTR_FL     = 0x20,
 	UBIFS_CRYPT_FL     = 0x40,
+	UBIFS_PROJINHERIT_FL = 0x80,
 };
 
 /* Inode flag bits used by UBIFS */
@@ -427,6 +429,7 @@ enum {
  *			  support 64bit cookies for lookups by hash
  * UBIFS_FLG_ENCRYPTION: this filesystem contains encrypted files
  * UBIFS_FLG_AUTHENTICATION: this filesystem contains hashes for authentication
+ * UBIFS_FLG_PROJID: this filesystem has project id support enabled
  */
 enum {
 	UBIFS_FLG_BIGLPT = 0x02,
@@ -434,11 +437,12 @@ enum {
 	UBIFS_FLG_DOUBLE_HASH = 0x08,
 	UBIFS_FLG_ENCRYPTION = 0x10,
 	UBIFS_FLG_AUTHENTICATION = 0x20,
+	UBIFS_FLG_PROJID = 0x40,
 };
 
 #define UBIFS_FLG_MASK (UBIFS_FLG_BIGLPT | UBIFS_FLG_SPACE_FIXUP | \
 		UBIFS_FLG_DOUBLE_HASH | UBIFS_FLG_ENCRYPTION | \
-		UBIFS_FLG_AUTHENTICATION)
+		UBIFS_FLG_AUTHENTICATION | UBIFS_FLG_PROJID)
 
 /**
  * struct ubifs_ch - common header node.
@@ -497,7 +501,7 @@ union ubifs_dev_desc {
  * @data_len: inode data length
  * @xattr_cnt: count of extended attributes this inode has
  * @xattr_size: summarized size of all extended attributes in bytes
- * @padding1: reserved for future, zeroes
+ * @projid: Quota project id
  * @xattr_names: sum of lengths of all extended attribute names belonging to
  *               this inode
  * @compr_type: compression type used for this inode
@@ -531,7 +535,7 @@ struct ubifs_ino_node {
 	__le32 data_len;
 	__le32 xattr_cnt;
 	__le32 xattr_size;
-	__u8 padding1[4]; /* Watch 'zero_ino_node_unused()' if changing! */
+	__le32 projid;
 	__le32 xattr_names;
 	__le16 compr_type;
 	__u8 padding2[26]; /* Watch 'zero_ino_node_unused()' if changing! */
diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h
index c55f212dcb75..16e140420f35 100644
--- a/fs/ubifs/ubifs.h
+++ b/fs/ubifs/ubifs.h
@@ -155,6 +155,8 @@
 #define UBIFS_HMAC_ARR_SZ 0
 #endif
 
+#define UBIFS_DEF_PROJID 0
+
 /*
  * Lockdep classes for UBIFS inode @ui_mutex.
  */
@@ -362,6 +364,7 @@ struct ubifs_gced_idx_leb {
  *                 inodes
  * @ui_size: inode size used by UBIFS when writing to flash
  * @flags: inode flags (@UBIFS_COMPR_FL, etc)
+ * @projid: The project id of this inode
  * @compr_type: default compression type used for this inode
  * @last_page_read: page number of last page read (for bulk read)
  * @read_in_a_row: number of consecutive pages read in a row (for bulk read)
@@ -413,6 +416,7 @@ struct ubifs_inode {
 	loff_t synced_i_size;
 	loff_t ui_size;
 	int flags;
+	kprojid_t projid;
 	pgoff_t last_page_read;
 	pgoff_t read_in_a_row;
 	int data_len;
@@ -1901,6 +1905,7 @@ int ubifs_read_superblock(struct ubifs_info *c);
 int ubifs_write_sb_node(struct ubifs_info *c, struct ubifs_sb_node *sup);
 int ubifs_fixup_free_space(struct ubifs_info *c);
 int ubifs_enable_encryption(struct ubifs_info *c);
+int ubifs_enable_projid(struct ubifs_info *c);
 
 /* replay.c */
 int ubifs_validate_entry(struct ubifs_info *c,
-- 
2.25.0


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

* [PATCH 7/8] ubifs: export get_znode
  2020-01-24 13:13 [PATCH v4 0/8] Add quota support to UBIFS Sascha Hauer
                   ` (5 preceding siblings ...)
  2020-01-24 13:13 ` [PATCH 6/8] ubifs: Add support for project id Sascha Hauer
@ 2020-01-24 13:13 ` Sascha Hauer
  2020-01-24 13:13 ` [PATCH 8/8] ubifs: Add quota support Sascha Hauer
  7 siblings, 0 replies; 19+ messages in thread
From: Sascha Hauer @ 2020-01-24 13:13 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-mtd, Jan Kara, Richard Weinberger, kernel, Sascha Hauer

get_znode will be needed by upcoming UBIFS quota support. Rename it to
ubifs_get_znode and export it.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 fs/ubifs/tnc.c   | 32 ++++++++++++++++----------------
 fs/ubifs/ubifs.h |  2 ++
 2 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/fs/ubifs/tnc.c b/fs/ubifs/tnc.c
index e8e7b0e9532e..188fa036e655 100644
--- a/fs/ubifs/tnc.c
+++ b/fs/ubifs/tnc.c
@@ -571,15 +571,15 @@ static int matches_name(struct ubifs_info *c, struct ubifs_zbranch *zbr,
 }
 
 /**
- * get_znode - get a TNC znode that may not be loaded yet.
+ * ubifs_get_znode - get a TNC znode that may not be loaded yet.
  * @c: UBIFS file-system description object
  * @znode: parent znode
  * @n: znode branch slot number
  *
  * This function returns the znode or a negative error code.
  */
-static struct ubifs_znode *get_znode(struct ubifs_info *c,
-				     struct ubifs_znode *znode, int n)
+struct ubifs_znode *ubifs_get_znode(struct ubifs_info *c,
+				    struct ubifs_znode *znode, int n)
 {
 	struct ubifs_zbranch *zbr;
 
@@ -619,11 +619,11 @@ static int tnc_next(struct ubifs_info *c, struct ubifs_znode **zn, int *n)
 		nn = znode->iip + 1;
 		znode = zp;
 		if (nn < znode->child_cnt) {
-			znode = get_znode(c, znode, nn);
+			znode = ubifs_get_znode(c, znode, nn);
 			if (IS_ERR(znode))
 				return PTR_ERR(znode);
 			while (znode->level != 0) {
-				znode = get_znode(c, znode, 0);
+				znode = ubifs_get_znode(c, znode, 0);
 				if (IS_ERR(znode))
 					return PTR_ERR(znode);
 			}
@@ -663,12 +663,12 @@ static int tnc_prev(struct ubifs_info *c, struct ubifs_znode **zn, int *n)
 		nn = znode->iip - 1;
 		znode = zp;
 		if (nn >= 0) {
-			znode = get_znode(c, znode, nn);
+			znode = ubifs_get_znode(c, znode, nn);
 			if (IS_ERR(znode))
 				return PTR_ERR(znode);
 			while (znode->level != 0) {
 				nn = znode->child_cnt - 1;
-				znode = get_znode(c, znode, nn);
+				znode = ubifs_get_znode(c, znode, nn);
 				if (IS_ERR(znode))
 					return PTR_ERR(znode);
 			}
@@ -2572,7 +2572,7 @@ static int tnc_delete(struct ubifs_info *c, struct ubifs_znode *znode, int n)
 		while (znode->child_cnt == 1 && znode->level != 0) {
 			zp = znode;
 			zbr = &znode->zbranch[0];
-			znode = get_znode(c, znode, 0);
+			znode = ubifs_get_znode(c, znode, 0);
 			if (IS_ERR(znode))
 				return PTR_ERR(znode);
 			znode = dirty_cow_znode(c, zbr);
@@ -3096,12 +3096,12 @@ static struct ubifs_znode *left_znode(struct ubifs_info *c,
 			return NULL;
 		if (n >= 0) {
 			/* Now go down the rightmost branch to 'level' */
-			znode = get_znode(c, znode, n);
+			znode = ubifs_get_znode(c, znode, n);
 			if (IS_ERR(znode))
 				return znode;
 			while (znode->level != level) {
 				n = znode->child_cnt - 1;
-				znode = get_znode(c, znode, n);
+				znode = ubifs_get_znode(c, znode, n);
 				if (IS_ERR(znode))
 					return znode;
 			}
@@ -3133,11 +3133,11 @@ static struct ubifs_znode *right_znode(struct ubifs_info *c,
 			return NULL;
 		if (n < znode->child_cnt) {
 			/* Now go down the leftmost branch to 'level' */
-			znode = get_znode(c, znode, n);
+			znode = ubifs_get_znode(c, znode, n);
 			if (IS_ERR(znode))
 				return znode;
 			while (znode->level != level) {
-				znode = get_znode(c, znode, 0);
+				znode = ubifs_get_znode(c, znode, 0);
 				if (IS_ERR(znode))
 					return znode;
 			}
@@ -3222,13 +3222,13 @@ static struct ubifs_znode *lookup_znode(struct ubifs_info *c,
 		}
 		if (znode->level == level + 1)
 			break;
-		znode = get_znode(c, znode, n);
+		znode = ubifs_get_znode(c, znode, n);
 		if (IS_ERR(znode))
 			return znode;
 	}
 	/* Check if the child is the one we are looking for */
 	if (znode->zbranch[n].lnum == lnum && znode->zbranch[n].offs == offs)
-		return get_znode(c, znode, n);
+		return ubifs_get_znode(c, znode, n);
 	/* If the key is unique, there is nowhere else to look */
 	if (!is_hash_key(c, key))
 		return NULL;
@@ -3254,7 +3254,7 @@ static struct ubifs_znode *lookup_znode(struct ubifs_info *c,
 		/* Check it */
 		if (znode->zbranch[n].lnum == lnum &&
 		    znode->zbranch[n].offs == offs)
-			return get_znode(c, znode, n);
+			return ubifs_get_znode(c, znode, n);
 		/* Stop if the key is less than the one we are looking for */
 		if (keys_cmp(c, &znode->zbranch[n].key, key) < 0)
 			break;
@@ -3276,7 +3276,7 @@ static struct ubifs_znode *lookup_znode(struct ubifs_info *c,
 		/* Check it */
 		if (znode->zbranch[n].lnum == lnum &&
 		    znode->zbranch[n].offs == offs)
-			return get_znode(c, znode, n);
+			return ubifs_get_znode(c, znode, n);
 		/* Stop if the key is greater than the one we are looking for */
 		if (keys_cmp(c, &znode->zbranch[n].key, key) > 0)
 			break;
diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h
index 16e140420f35..66c7ed0dc610 100644
--- a/fs/ubifs/ubifs.h
+++ b/fs/ubifs/ubifs.h
@@ -1819,6 +1819,8 @@ int ubifs_find_dirty_idx_leb(struct ubifs_info *c);
 int ubifs_save_dirty_idx_lnums(struct ubifs_info *c);
 
 /* tnc.c */
+struct ubifs_znode *ubifs_get_znode(struct ubifs_info *c,
+				    struct ubifs_znode *znode, int n);
 int ubifs_lookup_level0(struct ubifs_info *c, const union ubifs_key *key,
 			struct ubifs_znode **zn, int *n);
 int ubifs_tnc_lookup_nm(struct ubifs_info *c, const union ubifs_key *key,
-- 
2.25.0


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

* [PATCH 8/8] ubifs: Add quota support
  2020-01-24 13:13 [PATCH v4 0/8] Add quota support to UBIFS Sascha Hauer
                   ` (6 preceding siblings ...)
  2020-01-24 13:13 ` [PATCH 7/8] ubifs: export get_znode Sascha Hauer
@ 2020-01-24 13:13 ` Sascha Hauer
  2020-01-26 18:30   ` kbuild test robot
  2020-01-26 18:30   ` [RFC PATCH] ubifs: tnc_next() can be static kbuild test robot
  7 siblings, 2 replies; 19+ messages in thread
From: Sascha Hauer @ 2020-01-24 13:13 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-mtd, Jan Kara, Richard Weinberger, kernel, Sascha Hauer,
	Dongsheng Yang

This introduces poor man's quota support for UBIFS. Unlike other
implementations we never store anything on the flash. This has two
big advantages:

- No possible regressions with a changed on-disk format
- no quota files can get out of sync.

There are downsides as well:

- During mount the whole index must be scanned which takes some time
- The quota limits must be set manually each time a filesystem is mounted.

UBIFS is targetted for embedded systems and quota limits are likely not
changed interactively, so having to restore the quota limits with a
script shouldn't be a big deal. The mount time penalty is a price we
must pay, but for that we get a simple and straight forward
implementation for this rather rarely used feature.

The quota data itself is stored in a red-black tree in memory. It is
implemented as a quota format. When enabled with the "quota" mount
option all three quota types (user, group, project) are enabled.

The quota integration into UBIFS is taken from a series posted earlier
by Dongsheng Yang. Like the earlier series we only account regular files
for quota. All others are counted in the number of files, but do not
require any quota space.

Signed-off-by: Dongsheng Yang <yangds.fnst@cn.fujitsu.com>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 Documentation/filesystems/ubifs.txt |   7 +-
 fs/ubifs/Makefile                   |   1 +
 fs/ubifs/dir.c                      |  18 +-
 fs/ubifs/file.c                     |  43 ++
 fs/ubifs/ioctl.c                    |  32 ++
 fs/ubifs/journal.c                  |   2 +
 fs/ubifs/quota.c                    | 603 ++++++++++++++++++++++++++++
 fs/ubifs/super.c                    |  86 +++-
 fs/ubifs/tnc.c                      |   2 +-
 fs/ubifs/ubifs.h                    |  36 ++
 include/uapi/linux/quota.h          |   1 +
 11 files changed, 826 insertions(+), 5 deletions(-)
 create mode 100644 fs/ubifs/quota.c

diff --git a/Documentation/filesystems/ubifs.txt b/Documentation/filesystems/ubifs.txt
index acc80442a3bb..86285aa17cd8 100644
--- a/Documentation/filesystems/ubifs.txt
+++ b/Documentation/filesystems/ubifs.txt
@@ -98,7 +98,12 @@ auth_key=		specify the key used for authenticating the filesystem.
 auth_hash_name=		The hash algorithm used for authentication. Used for
 			both hashing and for creating HMACs. Typical values
 			include "sha256" or "sha512"
-
+usrquota		Enable user disk quota support
+			(requires CONFIG_QUOTA).
+grpquota		Enable group disk quota support
+			(requires CONFIG_QUOTA).
+prjquota		Enable project disk quota support
+			(requires CONFIG_QUOTA).
 
 Quick usage instructions
 ========================
diff --git a/fs/ubifs/Makefile b/fs/ubifs/Makefile
index 5c4b845754a7..07f657f5ea8f 100644
--- a/fs/ubifs/Makefile
+++ b/fs/ubifs/Makefile
@@ -9,3 +9,4 @@ ubifs-y += misc.o
 ubifs-$(CONFIG_FS_ENCRYPTION) += crypto.o
 ubifs-$(CONFIG_UBIFS_FS_XATTR) += xattr.o
 ubifs-$(CONFIG_UBIFS_FS_AUTHENTICATION) += auth.o
+ubifs-$(CONFIG_QUOTA) += quota.o
diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c
index 06d9192000cb..a4522bb34ca8 100644
--- a/fs/ubifs/dir.c
+++ b/fs/ubifs/dir.c
@@ -118,6 +118,11 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
 	else
 		ui->projid = make_kprojid(&init_user_ns, UBIFS_DEF_PROJID);
 
+	dquot_initialize(inode);
+	err = dquot_alloc_inode(inode);
+	if (err)
+		goto fail_drop;
+
 	switch (mode & S_IFMT) {
 	case S_IFREG:
 		inode->i_mapping->a_ops = &ubifs_file_address_operations;
@@ -158,8 +163,8 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
 			spin_unlock(&c->cnt_lock);
 			ubifs_err(c, "out of inode numbers");
 			make_bad_inode(inode);
-			iput(inode);
-			return ERR_PTR(-EINVAL);
+			err = -EINVAL;
+			goto fail_free;
 		}
 		ubifs_warn(c, "running out of inode numbers (current %lu, max %u)",
 			   (unsigned long)c->highest_inum, INUM_WATERMARK);
@@ -187,6 +192,13 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
 	}
 
 	return inode;
+
+fail_free:
+	dquot_free_inode(inode);
+fail_drop:
+	dquot_drop(inode);
+	iput(inode);
+	return ERR_PTR(err);
 }
 
 static int dbg_check_name(const struct ubifs_info *c,
@@ -776,6 +788,8 @@ static int ubifs_unlink(struct inode *dir, struct dentry *dentry)
 	unsigned int saved_nlink = inode->i_nlink;
 	struct fscrypt_name nm;
 
+	dquot_initialize(inode);
+
 	/*
 	 * Budget request settings: deletion direntry, deletion inode (+1 for
 	 * @dirtied_ino), changing the parent directory inode. If budgeting
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index cd52585c8f4f..9ae998febcf1 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -427,8 +427,10 @@ static int ubifs_write_begin(struct file *file, struct address_space *mapping,
 	struct ubifs_inode *ui = ubifs_inode(inode);
 	pgoff_t index = pos >> PAGE_SHIFT;
 	int uninitialized_var(err), appending = !!(pos + len > inode->i_size);
+	int quota_size = 0;
 	int skipped_read = 0;
 	struct page *page;
+	int ret = 0;
 
 	ubifs_assert(c, ubifs_inode(inode)->ui_size == inode->i_size);
 	ubifs_assert(c, !c->ro_media && !c->ro_mount);
@@ -436,6 +438,16 @@ static int ubifs_write_begin(struct file *file, struct address_space *mapping,
 	if (unlikely(c->ro_error))
 		return -EROFS;
 
+	quota_size = ((pos + len) - inode->i_size);
+	if (quota_size < 0)
+		quota_size = 0;
+	if (S_ISREG(inode->i_mode)) {
+		dquot_initialize(inode);
+		ret = dquot_alloc_space_nodirty(inode, quota_size);
+		if (unlikely(ret))
+			return ret;
+	}
+
 	/* Try out the fast-path part first */
 	page = grab_cache_page_write_begin(mapping, index, flags);
 	if (unlikely(!page))
@@ -541,6 +553,7 @@ static int ubifs_write_end(struct file *file, struct address_space *mapping,
 	struct ubifs_inode *ui = ubifs_inode(inode);
 	struct ubifs_info *c = inode->i_sb->s_fs_info;
 	loff_t end_pos = pos + len;
+	int quota_size = 0;
 	int appending = !!(end_pos > inode->i_size);
 
 	dbg_gen("ino %lu, pos %llu, pg %lu, len %u, copied %d, i_size %lld",
@@ -559,6 +572,11 @@ static int ubifs_write_end(struct file *file, struct address_space *mapping,
 		dbg_gen("copied %d instead of %d, read page and repeat",
 			copied, len);
 		cancel_budget(c, page, ui, appending);
+		quota_size = ((pos + len) - inode->i_size);
+		if (quota_size < 0)
+			quota_size = 0;
+		if (S_ISREG(inode->i_mode))
+			dquot_free_space_nodirty(inode, quota_size);
 		ClearPageChecked(page);
 
 		/*
@@ -1115,6 +1133,7 @@ static int do_truncation(struct ubifs_info *c, struct inode *inode,
 	int err;
 	struct ubifs_budget_req req;
 	loff_t old_size = inode->i_size, new_size = attr->ia_size;
+	loff_t quota_size = (old_size - new_size);
 	int offset = new_size & (UBIFS_BLOCK_SIZE - 1), budgeted = 1;
 	struct ubifs_inode *ui = ubifs_inode(inode);
 
@@ -1194,6 +1213,10 @@ static int do_truncation(struct ubifs_info *c, struct inode *inode,
 	do_attr_changes(inode, attr);
 	err = ubifs_jnl_truncate(c, inode, old_size, new_size);
 	mutex_unlock(&ui->ui_mutex);
+	if (quota_size < 0)
+		quota_size = 0;
+	if (S_ISREG(inode->i_mode))
+		dquot_free_space_nodirty(inode, quota_size);
 
 out_budg:
 	if (budgeted)
@@ -1230,6 +1253,17 @@ static int do_setattr(struct ubifs_info *c, struct inode *inode,
 
 	if (attr->ia_valid & ATTR_SIZE) {
 		dbg_gen("size %lld -> %lld", inode->i_size, new_size);
+		if (S_ISREG(inode->i_mode)) {
+			if (new_size > inode->i_size) {
+				err = dquot_alloc_space_nodirty(inode, new_size - inode->i_size);
+				if (err) {
+					ubifs_release_budget(c, &req);
+					return err;
+				}
+			} else {
+				dquot_free_space_nodirty(inode, inode->i_size - new_size);
+			}
+		}
 		truncate_setsize(inode, new_size);
 	}
 
@@ -1281,6 +1315,15 @@ int ubifs_setattr(struct dentry *dentry, struct iattr *attr)
 	if (err)
 		return err;
 
+	if (is_quota_modification(inode, attr))
+		dquot_initialize(inode);
+	if ((attr->ia_valid & ATTR_UID && !uid_eq(attr->ia_uid, inode->i_uid)) ||
+	    (attr->ia_valid & ATTR_GID && !gid_eq(attr->ia_gid, inode->i_gid))) {
+		err = dquot_transfer(inode, attr);
+		if (err)
+			return err;
+	}
+
 	if ((attr->ia_valid & ATTR_SIZE) && attr->ia_size < inode->i_size)
 		/* Truncation to a smaller size */
 		err = do_truncation(c, inode, attr);
diff --git a/fs/ubifs/ioctl.c b/fs/ubifs/ioctl.c
index e5c684b6d7a7..c2c10ebe5780 100644
--- a/fs/ubifs/ioctl.c
+++ b/fs/ubifs/ioctl.c
@@ -133,16 +133,48 @@ static __u32 ubifs_iflags_to_xflags(unsigned long flags)
         return xflags;
 }
 
+#ifdef CONFIG_QUOTA
+static int ubifs_transfer_project_quota(struct inode *inode, kprojid_t kprojid)
+{
+	struct dquot *transfer_to[MAXQUOTAS] = {};
+	struct ubifs_info *c = inode->i_sb->s_fs_info;
+	struct super_block *sb = c->vfs_sb;
+	int err = 0;
+
+	err = dquot_initialize(inode);
+	if (err)
+		return err;
+
+	transfer_to[PRJQUOTA] = dqget(sb, make_kqid_projid(kprojid));
+	if (!IS_ERR(transfer_to[PRJQUOTA])) {
+		err = __dquot_transfer(inode, transfer_to);
+		dqput(transfer_to[PRJQUOTA]);
+	}
+
+	return err;
+}
+#else
+static int ubifs_transfer_project_quota(struct inode *inode, kprojid_t kprojid)
+{
+	return 0;
+}
+#endif
+
 static int ubifs_ioc_setproject(struct file *file, __u32 projid)
 {
 	struct inode *inode = file_inode(file);
 	struct ubifs_inode *ui = ubifs_inode(inode);
 	kprojid_t kprojid;
+	int err;
 
 	kprojid = make_kprojid(&init_user_ns, (projid_t)projid);
 	if (projid_eq(kprojid, ui->projid))
 		return 0;
 
+	err = ubifs_transfer_project_quota(inode, kprojid);
+	if (err)
+		return err;
+
 	ui->projid = kprojid;
 
 	return 0;
diff --git a/fs/ubifs/journal.c b/fs/ubifs/journal.c
index 0a46eefd98a0..efd54dc22c88 100644
--- a/fs/ubifs/journal.c
+++ b/fs/ubifs/journal.c
@@ -630,6 +630,8 @@ int ubifs_jnl_update(struct ubifs_info *c, const struct inode *dir,
 			goto out_finish;
 		}
 		ui->del_cmtno = c->cmt_no;
+		if (S_ISREG(inode->i_mode))
+			dquot_free_space_nodirty((struct inode *)inode, inode->i_size);
 	}
 
 	err = write_head(c, BASEHD, dent, len, &lnum, &dent_offs, sync);
diff --git a/fs/ubifs/quota.c b/fs/ubifs/quota.c
new file mode 100644
index 000000000000..f6a64762d573
--- /dev/null
+++ b/fs/ubifs/quota.c
@@ -0,0 +1,603 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file is part of UBIFS.
+ *
+ * Copyright (C) 2019 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
+ */
+#include "ubifs.h"
+
+struct ubifs_dqblk {
+	struct mem_dqblk dqblk;
+	struct rb_node rb;
+	struct kqid kqid;
+};
+
+/**
+ * ubifs_dqblk_insert - find qid in tree
+ * @c: UBIFS file-system description object
+ * @new: The new entry to insert
+ *
+ * This inserts a new entry to the dqblk tree which is sorted by qids. Caller
+ * must make sure this entry doesn't exist already.
+ */
+static void ubifs_dqblk_insert(struct ubifs_info *c, struct ubifs_dqblk *new)
+{
+	struct rb_root *root = &c->dqblk_tree[new->kqid.type];
+	struct rb_node **link = &root->rb_node, *parent = NULL;
+	struct ubifs_dqblk *ud;
+
+	/* Go to the bottom of the tree */
+	while (*link) {
+		parent = *link;
+
+		ud = rb_entry(parent, struct ubifs_dqblk, rb);
+
+		if (qid_lt(new->kqid, ud->kqid))
+			link = &(*link)->rb_left;
+		else
+			link = &(*link)->rb_right;
+	}
+
+	/* Put the new node there */
+	rb_link_node(&new->rb, parent, link);
+	rb_insert_color(&new->rb, root);
+}
+
+/**
+ * ubifs_dqblk_find_next - find the next qid
+ * @c: UBIFS file-system description object
+ * @qid: The qid to look for
+ *
+ * Find the next dqblk entry with a qid that is bigger or equally big than the
+ * given qid. Returns the next dqblk entry if found or NULL if no dqblk exists
+ * with a qid that is at least equally big.
+ */
+static struct ubifs_dqblk *ubifs_dqblk_find_next(struct ubifs_info *c,
+						 struct kqid qid)
+{
+	struct rb_node *node = c->dqblk_tree[qid.type].rb_node;
+	struct ubifs_dqblk *next = NULL;
+
+	while (node) {
+		struct ubifs_dqblk *ud = rb_entry(node, struct ubifs_dqblk, rb);
+
+		if (qid_eq(qid, ud->kqid))
+			return ud;
+
+		if (qid_lt(qid, ud->kqid)) {
+			next = ud;
+			node = node->rb_left;
+		} else {
+			node = node->rb_right;
+		}
+	}
+
+	return next;
+}
+
+/**
+ * ubifs_dqblk_find - find qid in tree
+ * @c: UBIFS file-system description object
+ * @qid: The qid to look for
+ *
+ * This walks the dqblk tree and searches a given qid. Returns the dqblk entry
+ * when found or NULL otherwise.
+ */
+static struct ubifs_dqblk *ubifs_dqblk_find(struct ubifs_info *c,
+					    struct kqid qid)
+{
+	struct ubifs_dqblk *next;
+
+	next = ubifs_dqblk_find_next(c, qid);
+
+	if (next && qid_eq(qid, next->kqid))
+		return next;
+
+	return NULL;
+}
+
+/**
+ * ubifs_dqblk_get - get dqblk entry for given qid
+ * @c: UBIFS file-system description object
+ * @qid: The qid to look for
+ *
+ * This searches the given qid in the dqblk tree and returns it if found. If not,
+ * a new dqblk entry is created, inserted into the dqblk tree and returned.
+ */
+static struct ubifs_dqblk *ubifs_dqblk_get(struct ubifs_info *c,
+					   struct kqid qid)
+{
+	struct ubifs_dqblk *ud;
+
+	ud = ubifs_dqblk_find(c, qid);
+	if (ud)
+		return ud;
+
+	ud = kzalloc(sizeof(*ud), GFP_KERNEL);
+	if (!ud)
+		return NULL;
+
+	ud->kqid = qid;
+
+	ubifs_dqblk_insert(c, ud);
+
+	return ud;
+}
+
+/**
+ * ubifs_check_quota_file - check if quota file is valid for this format
+ * @sb: The superblock
+ * @type: Quota type
+ *
+ * We currently do not store any quota file on flash. It's completely in RAM and
+ * thus always valid.
+ */
+static int ubifs_check_quota_file(struct super_block *sb, int type)
+{
+	return 1;
+}
+
+/**
+ * ubifs_read_file_info - read quota file info
+ * @sb: The superblock
+ * @type: Quota type
+ *
+ * We currently do not store any quota info on flash. Just fill in default
+ * values.
+ */
+static int ubifs_read_file_info(struct super_block *sb, int type)
+{
+	struct quota_info *dqopt = sb_dqopt(sb);
+	struct mem_dqinfo *info = &dqopt->info[type];
+
+	down_read(&dqopt->dqio_sem);
+
+	/*
+	 * Used space is stored as unsigned 64-bit value in bytes but
+	 * quota core supports only signed 64-bit values so use that
+	 * as a limit
+	 */
+	info->dqi_max_spc_limit = 0x7fffffffffffffffLL; /* 2^63-1 */
+	info->dqi_max_ino_limit = 0x7fffffffffffffffLL;
+	info->dqi_bgrace = 0;
+	info->dqi_igrace = 0;
+	info->dqi_flags = 0;
+
+	up_read(&dqopt->dqio_sem);
+
+	return 0;
+}
+
+/**
+ * ubifs_write_file_info - write quota file to device
+ * @sb: The superblock
+ * @type: Quota type
+ *
+ * We currently do not store any quota file on flash. Nothing to do here.
+ */
+static int ubifs_write_file_info(struct super_block *sb, int type)
+{
+	return 0;
+}
+
+/**
+ * ubifs_read_dquot - read dquot from device
+ * @dquot: The dquot entry to read
+ *
+ * For us this means finding the entry in the dquot tree.
+ */
+static int ubifs_read_dquot(struct dquot *dquot)
+{
+	struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
+	struct ubifs_info *c = dquot->dq_sb->s_fs_info;
+	struct ubifs_dqblk *ud;
+	int ret = 0;
+
+	down_read(&dqopt->dqio_sem);
+
+	ud = ubifs_dqblk_find(c, dquot->dq_id);
+	if (ud)
+		dquot->dq_dqb = ud->dqblk;
+	else
+		ret = -ENOENT;
+
+	up_read(&dqopt->dqio_sem);
+
+	return 0;
+}
+
+/**
+ * ubifs_write_dquot - write dquot to device
+ * @dquot: The dquot entry to read
+ *
+ * For us this means finding or creating the entry in the dquot tree and setting
+ * it to the dquots contents.
+ */
+static int ubifs_write_dquot(struct dquot *dquot)
+{
+	struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
+	struct ubifs_info *c = dquot->dq_sb->s_fs_info;
+	int ret = 0;
+	struct ubifs_dqblk *ud;
+
+	down_write(&dqopt->dqio_sem);
+
+	ud = ubifs_dqblk_get(c, dquot->dq_id);
+	if (!ud) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ud->dqblk = dquot->dq_dqb;
+
+out:
+	up_write(&dqopt->dqio_sem);
+
+	return ret;
+}
+
+/**
+ * ubifs_release_dquot - release memory associated to a dquot entry
+ * @dquot: The dquot entry
+ *
+ * Nothing to do here, we didn't allocate anything. Our data is freed at unmount
+ * time.
+ */
+static int ubifs_release_dquot(struct dquot *dquot)
+{
+	return 0;
+}
+
+/**
+ * ubifs_free_file_info - free memory allocated during reading the file info
+ * @sb: The superblock
+ * @type: Quota type
+ *
+ * Nothing to do here.
+ */
+static int ubifs_free_file_info(struct super_block *sb, int type)
+{
+	return 0;
+}
+
+static int ubifs_get_next_id(struct super_block *sb, struct kqid *qid)
+{
+	struct quota_info *dqopt = sb_dqopt(sb);
+	struct ubifs_info *c = sb->s_fs_info;
+	struct ubifs_dqblk *ud;
+	int ret = 0;
+
+	down_read(&dqopt->dqio_sem);
+
+	ud = ubifs_dqblk_find_next(c, *qid);
+	if (!ud) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	*qid = ud->kqid;
+out:
+	up_read(&dqopt->dqio_sem);
+
+	return ret;
+}
+
+static const struct quota_format_ops ubifs_format_ops = {
+	.check_quota_file	= ubifs_check_quota_file,
+	.read_file_info		= ubifs_read_file_info,
+	.write_file_info	= ubifs_write_file_info,
+	.free_file_info		= ubifs_free_file_info,
+	.read_dqblk		= ubifs_read_dquot,
+	.commit_dqblk		= ubifs_write_dquot,
+	.release_dqblk		= ubifs_release_dquot,
+	.get_next_id		= ubifs_get_next_id,
+};
+
+/**
+ * add_inode_quota - read quota informations for an inode
+ * @c: UBIFS file-system description object
+ * @zbr: The zbranch to read the inode from
+ *
+ * This reads the inode as referred to by @zbr from the medium and
+ * extracts the quota relevant informations. Returns 0 for success
+ * or a negative error code otherwise.
+ */
+static int add_inode_quota(struct ubifs_info *c, struct ubifs_zbranch *zbr)
+{
+	struct ubifs_ino_node *ino;
+	int err, type = key_type(c, &zbr->key);
+	struct kqid qid[MAXQUOTAS];
+	kprojid_t kprojid;
+	kgid_t kgid;
+	kuid_t kuid;
+	int i;
+
+	if (type != UBIFS_INO_KEY) {
+		ubifs_err(c, "%s called on non INO node %d", __func__, type);
+		return 0;
+	}
+
+	if (zbr->len < UBIFS_CH_SZ) {
+		ubifs_err(c, "bad leaf length %d (LEB %d:%d)",
+				zbr->len, zbr->lnum, zbr->offs);
+		return -EINVAL;
+	}
+
+	ino = kmalloc(zbr->len, GFP_NOFS);
+	if (!ino)
+		return -ENOMEM;
+
+	err = ubifs_tnc_read_node(c, zbr, ino);
+	if (err) {
+		ubifs_err(c, "cannot read leaf node at LEB %d:%d, error %d",
+				zbr->lnum, zbr->offs, err);
+		goto out_free;
+	}
+
+	kuid = make_kuid(&init_user_ns, le32_to_cpu(ino->uid));
+	qid[USRQUOTA] = make_kqid_uid(kuid);
+
+	kgid = make_kgid(&init_user_ns, le32_to_cpu(ino->gid));
+	qid[GRPQUOTA] = make_kqid_gid(kgid);
+
+	kprojid = make_kprojid(&init_user_ns, le32_to_cpu(ino->projid));
+	qid[PRJQUOTA] = make_kqid_projid(kprojid);
+
+	for (i = 0; i < UBIFS_MAXQUOTAS; i++) {
+		struct ubifs_dqblk *ud;
+
+		if (!(c->quota_enable & (1 << type)))
+			continue;
+
+		ud = ubifs_dqblk_get(c, qid[i]);
+		if (!ud) {
+			err = -ENOMEM;
+			goto out_free;
+		}
+
+		if (S_ISREG(le32_to_cpu(ino->mode)))
+			ud->dqblk.dqb_curspace += le64_to_cpu(ino->size);
+		ud->dqblk.dqb_curinodes++;
+	}
+
+	err = 0;
+
+out_free:
+	kfree(ino);
+
+	return err;
+}
+
+/**
+ * get_next_parent_inum - get the next inum
+ * @c: UBIFS file-system description object
+ * @znode: The znode to start searching from
+ * @inum: The next inum returned here
+ *
+ * Helper function for ubifs_quota_walk_index(). If we know the next inum we
+ * find up the tree is still the same as we just handled we do not have to walk
+ * down the tree but can skip these branches. Returns 0 for success or -ENOENT
+ * when we can't go further to the upper-right but hit the root node instead.
+ */
+static int get_next_parent_inum(struct ubifs_info *c, struct ubifs_znode *znode,
+				ino_t *inum)
+{
+	struct ubifs_znode *zp;
+
+	while (1) {
+		int nn;
+
+		zp = znode->parent;
+		if (!zp)
+			return -ENOENT;
+
+		nn = znode->iip + 1;
+		znode = zp;
+
+		if (nn < znode->child_cnt) {
+			*inum = key_inum(c, &znode->zbranch[nn].key);
+			return 0;
+		}
+	}
+}
+
+/**
+ * ubifs_quota_walk_index - Collect quota info
+ * @c: UBIFS file-system description object
+ *
+ * This function walks the whole index and collects the quota usage information
+ * for all inodes. Returns 0 for success or a negative error code otherwise.
+ */
+static int ubifs_quota_walk_index(struct ubifs_info *c)
+{
+	int err;
+	struct ubifs_znode *znode;
+	int i, nn = 0;
+	ino_t inum = 0, pinum;
+
+	if (!c->zroot.znode) {
+		c->zroot.znode = ubifs_load_znode(c, &c->zroot, NULL, 0);
+		if (IS_ERR(c->zroot.znode)) {
+			err = PTR_ERR(c->zroot.znode);
+			c->zroot.znode = NULL;
+			return err;
+		}
+	}
+
+	znode = c->zroot.znode;
+
+	while (1) {
+		while (znode->level != 0) {
+			znode = ubifs_get_znode(c, znode, nn);
+			if (IS_ERR(znode))
+				return PTR_ERR(znode);
+			nn = 0;
+		}
+
+		for (i = 0; i < znode->child_cnt; i++) {
+			if (inum == key_inum(c, &znode->zbranch[i].key))
+				continue;
+
+			add_inode_quota(c, &znode->zbranch[i]);
+			inum = key_inum(c, &znode->zbranch[i].key);
+		}
+
+		while (1) {
+			struct ubifs_znode *zp;
+
+			zp = znode->parent;
+			if (!zp)
+				return 0;
+
+			nn = znode->iip + 1;
+			znode = zp;
+
+			err = get_next_parent_inum(c, znode, &pinum);
+
+			/*
+			 * Optimization: When the next parent node is still for
+			 * the inode we just handled we can skip parsing our
+			 * children.
+			 */
+			if (!err && pinum == inum)
+				continue;
+
+			/*
+			 * Optimization: We can skip scanning all child nodes
+			 * which are for the same inode as we already looked at.
+			 */
+			while (nn < znode->child_cnt - 1 &&
+			       inum == key_inum(c, &znode->zbranch[nn + 1].key))
+				nn++;
+
+			if (nn < znode->child_cnt)
+				break;
+		}
+	}
+}
+
+/**
+ * ubifs_enable_quotas - enable quota
+ * @c: UBIFS file-system description object
+ *
+ * Enable usage tracking for all quota types.
+ */
+int ubifs_enable_quotas(struct ubifs_info *c)
+{
+	struct super_block *sb = c->vfs_sb;
+	struct quota_info *dqopt = sb_dqopt(sb);
+	int type, ret;
+
+	if (!c->quota_enable)
+		return 0;
+
+	dqopt->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
+
+	for (type = 0; type < UBIFS_MAXQUOTAS; type++) {
+		if (!(c->quota_enable & (1 << type)))
+			continue;
+
+		ret = dquot_load_quota_sb(sb, type, QFMT_UBIFS,
+				   DQUOT_USAGE_ENABLED | DQUOT_LIMITS_ENABLED);
+		if (ret)
+			goto out_quota_off;
+
+		c->dqblk_tree[type] = RB_ROOT;
+	}
+
+	return ubifs_quota_walk_index(c);
+
+out_quota_off:
+	ubifs_disable_quotas(c);
+
+	return ret;
+}
+
+/**
+ * ubifs_disable_quotas - disable quota
+ * @c: UBIFS file-system description object
+ *
+ * Disable quota for UBFIS.
+ */
+void ubifs_disable_quotas(struct ubifs_info *c)
+{
+	struct rb_node *n;
+	struct ubifs_dqblk *ud;
+	int type;
+
+	if (!c->quota_enable)
+		return;
+
+	dquot_disable(c->vfs_sb, -1, DQUOT_USAGE_ENABLED | DQUOT_LIMITS_ENABLED);
+
+	for (type = 0; type < UBIFS_MAXQUOTAS; type++) {
+		while ((n = rb_first(&c->dqblk_tree[type]))) {
+			ud = rb_entry(n, struct ubifs_dqblk, rb);
+			rb_erase(n, &c->dqblk_tree[type]);
+			kfree(ud);
+		}
+	}
+}
+
+static int ubifs_get_projid(struct inode *inode, kprojid_t *projid)
+{
+	struct ubifs_inode *ui = ubifs_inode(inode);
+
+	*projid = ui->projid;
+
+	return 0;
+}
+
+static const struct dquot_operations ubifs_dquot_operations = {
+	.write_dquot	= dquot_commit,
+	.acquire_dquot	= dquot_acquire,
+	.release_dquot	= dquot_release,
+	.mark_dirty	= dquot_mark_dquot_dirty,
+	.write_info	= dquot_commit_info,
+	.alloc_dquot	= dquot_alloc,
+	.destroy_dquot	= dquot_destroy,
+	.get_next_id	= dquot_get_next_id,
+	.get_projid	= ubifs_get_projid,
+};
+
+static const struct quotactl_ops ubifs_quotactl_ops = {
+	.get_state	= dquot_get_state,
+	.set_info	= dquot_set_dqinfo,
+	.get_dqblk	= dquot_get_dqblk,
+	.set_dqblk	= dquot_set_dqblk,
+	.get_nextdqblk	= dquot_get_next_dqblk,
+};
+
+static struct quota_format_type ubifs_quota_format = {
+	.qf_fmt_id	= QFMT_UBIFS,
+	.qf_ops		= &ubifs_format_ops,
+	.qf_owner	= THIS_MODULE
+};
+
+int ubifs_register_quota_format(void)
+{
+	return register_quota_format(&ubifs_quota_format);
+}
+
+void ubifs_unregister_quota_format(void)
+{
+	return unregister_quota_format(&ubifs_quota_format);
+}
+
+/**
+ * ubifs_init_quota - init quota ops for UBIFS
+ * @c: UBIFS file-system description object
+ *
+ * This inits the quota operations for UBIFS
+ */
+void ubifs_init_quota(struct ubifs_info *c)
+{
+	struct super_block *sb = c->vfs_sb;
+
+	if (!c->quota_enable)
+		return;
+
+	sb->dq_op = &ubifs_dquot_operations;
+	sb->s_qcop = &ubifs_quotactl_ops;
+	sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP | QTYPE_MASK_PRJ;
+}
diff --git a/fs/ubifs/super.c b/fs/ubifs/super.c
index e702fb4e92cf..e2af9ccea301 100644
--- a/fs/ubifs/super.c
+++ b/fs/ubifs/super.c
@@ -151,6 +151,7 @@ struct inode *ubifs_iget(struct super_block *sb, unsigned long inum)
 
 	switch (inode->i_mode & S_IFMT) {
 	case S_IFREG:
+		inode_set_bytes(inode, ino->size);
 		inode->i_mapping->a_ops = &ubifs_file_address_operations;
 		inode->i_op = &ubifs_file_inode_operations;
 		inode->i_fop = &ubifs_file_operations;
@@ -354,6 +355,7 @@ static void ubifs_evict_inode(struct inode *inode)
 	if (is_bad_inode(inode))
 		goto out;
 
+	dquot_initialize(inode);
 	ui->ui_size = inode->i_size = 0;
 	err = ubifs_jnl_delete_inode(c, inode);
 	if (err)
@@ -363,7 +365,7 @@ static void ubifs_evict_inode(struct inode *inode)
 		 */
 		ubifs_err(c, "can't delete inode %lu, error %d",
 			  inode->i_ino, err);
-
+	dquot_free_inode(inode);
 out:
 	if (ui->dirty)
 		ubifs_release_dirty_inode_budget(c, ui);
@@ -373,6 +375,7 @@ static void ubifs_evict_inode(struct inode *inode)
 		smp_wmb();
 	}
 done:
+	dquot_drop(inode);
 	clear_inode(inode);
 	fscrypt_put_encryption_info(inode);
 }
@@ -435,6 +438,13 @@ static int ubifs_show_options(struct seq_file *s, struct dentry *root)
 	else if (c->mount_opts.chk_data_crc == 1)
 		seq_puts(s, ",no_chk_data_crc");
 
+	if (c->quota_enable & (1 << USRQUOTA))
+		seq_puts(s, ",usrquota");
+	if (c->quota_enable & (1 << GRPQUOTA))
+		seq_puts(s, ",grpquota");
+	if (c->quota_enable & (1 << PRJQUOTA))
+		seq_puts(s, ",prjquota");
+
 	if (c->mount_opts.override_compr) {
 		seq_printf(s, ",compr=%s",
 			   ubifs_compr_name(c, c->mount_opts.compr_type));
@@ -927,6 +937,29 @@ static int check_volume_empty(struct ubifs_info *c)
 	return 0;
 }
 
+#ifdef CONFIG_QUOTA
+static struct dquot **ubifs_get_dquots(struct inode *inode)
+{
+	return ubifs_inode(inode)->i_dquot;
+}
+
+static ssize_t ubifs_quota_read(struct super_block *sb, int type, char *data,
+				size_t len, loff_t off)
+{
+	BUG();
+
+	return 0;
+}
+
+static ssize_t ubifs_quota_write(struct super_block *sb, int type, const char *data,
+				 size_t len, loff_t off)
+{
+	BUG();
+
+	return 0;
+}
+#endif
+
 /*
  * UBIFS mount options.
  *
@@ -954,6 +987,9 @@ enum {
 	Opt_auth_key,
 	Opt_auth_hash_name,
 	Opt_ignore,
+	Opt_usrquota,
+	Opt_grpquota,
+	Opt_prjquota,
 	Opt_err,
 };
 
@@ -970,6 +1006,9 @@ static const match_table_t tokens = {
 	{Opt_ignore, "ubi=%s"},
 	{Opt_ignore, "vol=%s"},
 	{Opt_assert, "assert=%s"},
+	{Opt_usrquota, "usrquota"},
+	{Opt_grpquota, "grpquota"},
+	{Opt_prjquota, "prjquota"},
 	{Opt_err, NULL},
 };
 
@@ -1104,6 +1143,23 @@ static int ubifs_parse_options(struct ubifs_info *c, char *options,
 			break;
 		case Opt_ignore:
 			break;
+#ifdef CONFIG_QUOTA
+		case Opt_usrquota:
+			c->quota_enable |= 1 << USRQUOTA;
+			break;
+		case Opt_grpquota:
+			c->quota_enable |= 1 << GRPQUOTA;
+			break;
+		case Opt_prjquota:
+			c->quota_enable |= 1 << PRJQUOTA;
+			break;
+#else
+		case Opt_usrquota:
+		case Opt_grpquota:
+		case Opt_prjquota:
+			ubifs_err(c, "quota operations not supported");
+			break;
+#endif
 		default:
 		{
 			unsigned long flag;
@@ -1800,6 +1856,7 @@ static int ubifs_remount_rw(struct ubifs_info *c)
 		 */
 		err = dbg_check_space_info(c);
 	}
+	c->vfs_sb->s_flags &= ~SB_RDONLY;
 
 	mutex_unlock(&c->umount_mutex);
 	return err;
@@ -1869,6 +1926,7 @@ static void ubifs_remount_ro(struct ubifs_info *c)
 	err = dbg_check_space_info(c);
 	if (err)
 		ubifs_ro_mode(c, err);
+	c->vfs_sb->s_flags |= SB_RDONLY;
 	mutex_unlock(&c->umount_mutex);
 }
 
@@ -1879,6 +1937,8 @@ static void ubifs_put_super(struct super_block *sb)
 
 	ubifs_msg(c, "un-mount UBI device %d", c->vi.ubi_num);
 
+	ubifs_disable_quotas(c);
+
 	/*
 	 * The following asserts are only valid if there has not been a failure
 	 * of the media. For example, there will be dirty inodes if we failed
@@ -1976,11 +2036,17 @@ static int ubifs_remount_fs(struct super_block *sb, int *flags, char *data)
 		err = ubifs_remount_rw(c);
 		if (err)
 			return err;
+		err = dquot_resume(sb, -1);
+		if (err)
+			return err;
 	} else if (!c->ro_mount && (*flags & SB_RDONLY)) {
 		if (c->ro_error) {
 			ubifs_msg(c, "cannot re-mount R/O due to prior errors");
 			return -EROFS;
 		}
+		err = dquot_suspend(sb, -1);
+		if (err)
+			return err;
 		ubifs_remount_ro(c);
 	}
 
@@ -2012,6 +2078,11 @@ const struct super_operations ubifs_super_operations = {
 	.remount_fs    = ubifs_remount_fs,
 	.show_options  = ubifs_show_options,
 	.sync_fs       = ubifs_sync_fs,
+#ifdef CONFIG_QUOTA
+	.get_dquots    = ubifs_get_dquots,
+	.quota_read    = ubifs_quota_read,
+	.quota_write   = ubifs_quota_write,
+#endif
 };
 
 /**
@@ -2172,6 +2243,8 @@ static int ubifs_fill_super(struct super_block *sb, void *data, int silent)
 #endif
 	fscrypt_set_ops(sb, &ubifs_crypt_operations);
 
+	ubifs_init_quota(c);
+
 	mutex_lock(&c->umount_mutex);
 	err = mount_ubifs(c);
 	if (err) {
@@ -2186,6 +2259,10 @@ static int ubifs_fill_super(struct super_block *sb, void *data, int silent)
 		goto out_umount;
 	}
 
+	err = ubifs_enable_quotas(c);
+	if (err)
+		goto out_umount;
+
 	sb->s_root = d_make_root(root);
 	if (!sb->s_root) {
 		err = -ENOMEM;
@@ -2392,6 +2469,10 @@ static int __init ubifs_init(void)
 
 	dbg_debugfs_init();
 
+	err = ubifs_register_quota_format();
+	if (err)
+		goto out_compressors;
+
 	err = register_filesystem(&ubifs_fs_type);
 	if (err) {
 		pr_err("UBIFS error (pid %d): cannot register file system, error %d",
@@ -2402,6 +2483,8 @@ static int __init ubifs_init(void)
 
 out_dbg:
 	dbg_debugfs_exit();
+	ubifs_unregister_quota_format();
+out_compressors:
 	ubifs_compressors_exit();
 out_shrinker:
 	unregister_shrinker(&ubifs_shrinker_info);
@@ -2427,6 +2510,7 @@ static void __exit ubifs_exit(void)
 	 */
 	rcu_barrier();
 	kmem_cache_destroy(ubifs_inode_slab);
+	ubifs_unregister_quota_format();
 	unregister_filesystem(&ubifs_fs_type);
 }
 module_exit(ubifs_exit);
diff --git a/fs/ubifs/tnc.c b/fs/ubifs/tnc.c
index 188fa036e655..624568365ad3 100644
--- a/fs/ubifs/tnc.c
+++ b/fs/ubifs/tnc.c
@@ -600,7 +600,7 @@ struct ubifs_znode *ubifs_get_znode(struct ubifs_info *c,
  * This function returns %0 if the next TNC entry is found, %-ENOENT if there is
  * no next entry, or a negative error code otherwise.
  */
-static int tnc_next(struct ubifs_info *c, struct ubifs_znode **zn, int *n)
+int tnc_next(struct ubifs_info *c, struct ubifs_znode **zn, int *n)
 {
 	struct ubifs_znode *znode = *zn;
 	int nn = *n;
diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h
index 66c7ed0dc610..285472100b96 100644
--- a/fs/ubifs/ubifs.h
+++ b/fs/ubifs/ubifs.h
@@ -27,6 +27,7 @@
 #include <linux/security.h>
 #include <linux/xattr.h>
 #include <linux/random.h>
+#include <linux/quotaops.h>
 #include <crypto/hash_info.h>
 #include <crypto/hash.h>
 #include <crypto/algapi.h>
@@ -157,6 +158,8 @@
 
 #define UBIFS_DEF_PROJID 0
 
+#define UBIFS_MAXQUOTAS 3
+
 /*
  * Lockdep classes for UBIFS inode @ui_mutex.
  */
@@ -416,6 +419,9 @@ struct ubifs_inode {
 	loff_t synced_i_size;
 	loff_t ui_size;
 	int flags;
+#ifdef CONFIG_QUOTA
+	struct dquot *i_dquot[UBIFS_MAXQUOTAS];
+#endif
 	kprojid_t projid;
 	pgoff_t last_page_read;
 	pgoff_t read_in_a_row;
@@ -1045,6 +1051,7 @@ struct ubifs_debug_info;
  * @rw_incompat: the media is not R/W compatible
  * @assert_action: action to take when a ubifs_assert() fails
  * @authenticated: flag indigating the FS is mounted in authenticated mode
+ * @quota_enable: If true, quota is enabled on this filesystem
  *
  * @tnc_mutex: protects the Tree Node Cache (TNC), @zroot, @cnext, @enext, and
  *             @calc_idx_sz
@@ -1386,6 +1393,9 @@ struct ubifs_info {
 	struct ubi_device_info di;
 	struct ubi_volume_info vi;
 
+	unsigned int quota_enable;
+	struct rb_root dqblk_tree[UBIFS_MAXQUOTAS];
+
 	struct rb_root orph_tree;
 	struct list_head orph_list;
 	struct list_head orph_new;
@@ -2109,6 +2119,32 @@ static inline bool ubifs_crypt_is_encrypted(const struct inode *inode)
 	return ui->flags & UBIFS_CRYPT_FL;
 }
 
+/* quota.c */
+#ifdef CONFIG_QUOTA
+int ubifs_register_quota_format(void);
+void ubifs_unregister_quota_format(void);
+void ubifs_init_quota(struct ubifs_info *c);
+int ubifs_enable_quotas(struct ubifs_info *c);
+void ubifs_disable_quotas(struct ubifs_info *c);
+#else
+static inline int ubifs_register_quota_format(void)
+{
+	return 0;
+}
+static inline void ubifs_unregister_quota_format(void)
+{
+}
+static inline void ubifs_init_quota(struct ubifs_info *c)
+{
+}
+static inline int ubifs_enable_quotas(struct ubifs_info *c)
+{
+	return 0;
+}
+static inline void ubifs_disable_quotas(struct ubifs_info *c)
+{
+}
+#endif
 /* Normal UBIFS messages */
 __printf(2, 3)
 void ubifs_msg(const struct ubifs_info *c, const char *fmt, ...);
diff --git a/include/uapi/linux/quota.h b/include/uapi/linux/quota.h
index e1787c0df601..7082868ca225 100644
--- a/include/uapi/linux/quota.h
+++ b/include/uapi/linux/quota.h
@@ -78,6 +78,7 @@
 #define	QFMT_VFS_V0 2
 #define QFMT_OCFS2 3
 #define	QFMT_VFS_V1 4
+#define QFMT_UBIFS 5
 
 /* Size of block in which space limits are passed through the quota
  * interface */
-- 
2.25.0


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

* Re: [PATCH 8/8] ubifs: Add quota support
  2020-01-24 13:13 ` [PATCH 8/8] ubifs: Add quota support Sascha Hauer
@ 2020-01-26 18:30   ` kbuild test robot
  2020-01-26 18:30   ` [RFC PATCH] ubifs: tnc_next() can be static kbuild test robot
  1 sibling, 0 replies; 19+ messages in thread
From: kbuild test robot @ 2020-01-26 18:30 UTC (permalink / raw)
  To: Sascha Hauer
  Cc: kbuild-all, linux-fsdevel, linux-mtd, Jan Kara,
	Richard Weinberger, kernel, Sascha Hauer, Dongsheng Yang

Hi Sascha,

I love your patch! Perhaps something to improve:

[auto build test WARNING on ext3/for_next]
[also build test WARNING on linus/master v5.5-rc7]
[cannot apply to rw-ubifs/next next-20200122]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Sascha-Hauer/Add-quota-support-to-UBIFS/20200125-175919
base:   https://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs.git for_next
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.1-153-g47b6dfef-dirty
        make ARCH=x86_64 allmodconfig
        make C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__'

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)

>> fs/ubifs/super.c:154:43: sparse: sparse: incorrect type in argument 2 (different base types)
>> fs/ubifs/super.c:154:43: sparse:    expected long long [usertype] bytes
>> fs/ubifs/super.c:154:43: sparse:    got restricted __le64 [usertype] size
--
>> fs/ubifs/tnc.c:603:5: sparse: sparse: symbol 'tnc_next' was not declared. Should it be static?
   fs/ubifs/tnc.c:2870:35: sparse: sparse: Using plain integer as NULL pointer

Please review and possibly fold the followup patch.

vim +154 fs/ubifs/super.c

    88	
    89	struct inode *ubifs_iget(struct super_block *sb, unsigned long inum)
    90	{
    91		int err;
    92		union ubifs_key key;
    93		struct ubifs_ino_node *ino;
    94		struct ubifs_info *c = sb->s_fs_info;
    95		struct inode *inode;
    96		struct ubifs_inode *ui;
    97	
    98		dbg_gen("inode %lu", inum);
    99	
   100		inode = iget_locked(sb, inum);
   101		if (!inode)
   102			return ERR_PTR(-ENOMEM);
   103		if (!(inode->i_state & I_NEW))
   104			return inode;
   105		ui = ubifs_inode(inode);
   106	
   107		ino = kmalloc(UBIFS_MAX_INO_NODE_SZ, GFP_NOFS);
   108		if (!ino) {
   109			err = -ENOMEM;
   110			goto out;
   111		}
   112	
   113		ino_key_init(c, &key, inode->i_ino);
   114	
   115		err = ubifs_tnc_lookup(c, &key, ino);
   116		if (err)
   117			goto out_ino;
   118	
   119		inode->i_flags |= S_NOCMTIME;
   120	
   121		if (!IS_ENABLED(CONFIG_UBIFS_ATIME_SUPPORT))
   122			inode->i_flags |= S_NOATIME;
   123	
   124		set_nlink(inode, le32_to_cpu(ino->nlink));
   125		i_uid_write(inode, le32_to_cpu(ino->uid));
   126		i_gid_write(inode, le32_to_cpu(ino->gid));
   127		inode->i_atime.tv_sec  = (int64_t)le64_to_cpu(ino->atime_sec);
   128		inode->i_atime.tv_nsec = le32_to_cpu(ino->atime_nsec);
   129		inode->i_mtime.tv_sec  = (int64_t)le64_to_cpu(ino->mtime_sec);
   130		inode->i_mtime.tv_nsec = le32_to_cpu(ino->mtime_nsec);
   131		inode->i_ctime.tv_sec  = (int64_t)le64_to_cpu(ino->ctime_sec);
   132		inode->i_ctime.tv_nsec = le32_to_cpu(ino->ctime_nsec);
   133		inode->i_mode = le32_to_cpu(ino->mode);
   134		inode->i_size = le64_to_cpu(ino->size);
   135	
   136		ui->data_len    = le32_to_cpu(ino->data_len);
   137		ui->flags       = le32_to_cpu(ino->flags);
   138		ui->compr_type  = le16_to_cpu(ino->compr_type);
   139		ui->creat_sqnum = le64_to_cpu(ino->creat_sqnum);
   140		ui->xattr_cnt   = le32_to_cpu(ino->xattr_cnt);
   141		ui->xattr_size  = le32_to_cpu(ino->xattr_size);
   142		ui->xattr_names = le32_to_cpu(ino->xattr_names);
   143		ui->synced_i_size = ui->ui_size = inode->i_size;
   144		ui->projid = make_kprojid(&init_user_ns, le32_to_cpu(ino->projid));
   145	
   146		ui->xattr = (ui->flags & UBIFS_XATTR_FL) ? 1 : 0;
   147	
   148		err = validate_inode(c, inode);
   149		if (err)
   150			goto out_invalid;
   151	
   152		switch (inode->i_mode & S_IFMT) {
   153		case S_IFREG:
 > 154			inode_set_bytes(inode, ino->size);
   155			inode->i_mapping->a_ops = &ubifs_file_address_operations;
   156			inode->i_op = &ubifs_file_inode_operations;
   157			inode->i_fop = &ubifs_file_operations;
   158			if (ui->xattr) {
   159				ui->data = kmalloc(ui->data_len + 1, GFP_NOFS);
   160				if (!ui->data) {
   161					err = -ENOMEM;
   162					goto out_ino;
   163				}
   164				memcpy(ui->data, ino->data, ui->data_len);
   165				((char *)ui->data)[ui->data_len] = '\0';
   166			} else if (ui->data_len != 0) {
   167				err = 10;
   168				goto out_invalid;
   169			}
   170			break;
   171		case S_IFDIR:
   172			inode->i_op  = &ubifs_dir_inode_operations;
   173			inode->i_fop = &ubifs_dir_operations;
   174			if (ui->data_len != 0) {
   175				err = 11;
   176				goto out_invalid;
   177			}
   178			break;
   179		case S_IFLNK:
   180			inode->i_op = &ubifs_symlink_inode_operations;
   181			if (ui->data_len <= 0 || ui->data_len > UBIFS_MAX_INO_DATA) {
   182				err = 12;
   183				goto out_invalid;
   184			}
   185			ui->data = kmalloc(ui->data_len + 1, GFP_NOFS);
   186			if (!ui->data) {
   187				err = -ENOMEM;
   188				goto out_ino;
   189			}
   190			memcpy(ui->data, ino->data, ui->data_len);
   191			((char *)ui->data)[ui->data_len] = '\0';
   192			break;
   193		case S_IFBLK:
   194		case S_IFCHR:
   195		{
   196			dev_t rdev;
   197			union ubifs_dev_desc *dev;
   198	
   199			ui->data = kmalloc(sizeof(union ubifs_dev_desc), GFP_NOFS);
   200			if (!ui->data) {
   201				err = -ENOMEM;
   202				goto out_ino;
   203			}
   204	
   205			dev = (union ubifs_dev_desc *)ino->data;
   206			if (ui->data_len == sizeof(dev->new))
   207				rdev = new_decode_dev(le32_to_cpu(dev->new));
   208			else if (ui->data_len == sizeof(dev->huge))
   209				rdev = huge_decode_dev(le64_to_cpu(dev->huge));
   210			else {
   211				err = 13;
   212				goto out_invalid;
   213			}
   214			memcpy(ui->data, ino->data, ui->data_len);
   215			inode->i_op = &ubifs_file_inode_operations;
   216			init_special_inode(inode, inode->i_mode, rdev);
   217			break;
   218		}
   219		case S_IFSOCK:
   220		case S_IFIFO:
   221			inode->i_op = &ubifs_file_inode_operations;
   222			init_special_inode(inode, inode->i_mode, 0);
   223			if (ui->data_len != 0) {
   224				err = 14;
   225				goto out_invalid;
   226			}
   227			break;
   228		default:
   229			err = 15;
   230			goto out_invalid;
   231		}
   232	
   233		kfree(ino);
   234		ubifs_set_inode_flags(inode);
   235		unlock_new_inode(inode);
   236		return inode;
   237	
   238	out_invalid:
   239		ubifs_err(c, "inode %lu validation failed, error %d", inode->i_ino, err);
   240		ubifs_dump_node(c, ino);
   241		ubifs_dump_inode(c, inode);
   242		err = -EINVAL;
   243	out_ino:
   244		kfree(ino);
   245	out:
   246		ubifs_err(c, "failed to read inode %lu, error %d", inode->i_ino, err);
   247		iget_failed(inode);
   248		return ERR_PTR(err);
   249	}
   250	

---
0-DAY kernel test infrastructure                 Open Source Technology Center
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org Intel Corporation

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

* [RFC PATCH] ubifs: tnc_next() can be static
  2020-01-24 13:13 ` [PATCH 8/8] ubifs: Add quota support Sascha Hauer
  2020-01-26 18:30   ` kbuild test robot
@ 2020-01-26 18:30   ` kbuild test robot
  1 sibling, 0 replies; 19+ messages in thread
From: kbuild test robot @ 2020-01-26 18:30 UTC (permalink / raw)
  To: Sascha Hauer
  Cc: kbuild-all, linux-fsdevel, linux-mtd, Jan Kara,
	Richard Weinberger, kernel, Sascha Hauer, Dongsheng Yang


Fixes: 7eb604185a12 ("ubifs: Add quota support")
Signed-off-by: kbuild test robot <lkp@intel.com>
---
 tnc.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/ubifs/tnc.c b/fs/ubifs/tnc.c
index 624568365ad3d..188fa036e6556 100644
--- a/fs/ubifs/tnc.c
+++ b/fs/ubifs/tnc.c
@@ -600,7 +600,7 @@ struct ubifs_znode *ubifs_get_znode(struct ubifs_info *c,
  * This function returns %0 if the next TNC entry is found, %-ENOENT if there is
  * no next entry, or a negative error code otherwise.
  */
-int tnc_next(struct ubifs_info *c, struct ubifs_znode **zn, int *n)
+static int tnc_next(struct ubifs_info *c, struct ubifs_znode **zn, int *n)
 {
 	struct ubifs_znode *znode = *zn;
 	int nn = *n;

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

* Re: [PATCH 1/8] quota: Allow to pass mount path to quotactl
  2020-01-24 13:13 ` [PATCH 1/8] quota: Allow to pass mount path to quotactl Sascha Hauer
@ 2020-01-27 10:45   ` Jan Kara
  2020-01-28 10:06     ` Sascha Hauer
  0 siblings, 1 reply; 19+ messages in thread
From: Jan Kara @ 2020-01-27 10:45 UTC (permalink / raw)
  To: Sascha Hauer
  Cc: linux-fsdevel, linux-mtd, Jan Kara, Richard Weinberger, kernel

On Fri 24-01-20 14:13:16, Sascha Hauer wrote:
> This patch introduces the Q_PATH flag to the quotactl cmd argument.
> When given, the path given in the special argument to quotactl will
> be the mount path where the filesystem is mounted, instead of a path
> to the block device.
> This is necessary for filesystems which do not have a block device as
> backing store. Particularly this is done for upcoming UBIFS support.
> 
> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>

Thanks for the patch. Some comments are below.

> @@ -821,15 +822,20 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
>  {
>  	uint cmds, type;
>  	struct super_block *sb = NULL;
> -	struct path path, *pathp = NULL;
> +	struct path file_path, *file_pathp = NULL, sb_path;
>  	int ret;
> +	bool q_path;
>  
>  	cmds = cmd >> SUBCMDSHIFT;
>  	type = cmd & SUBCMDMASK;
>  
> +

Unnecessary empty line added...

>  	if (type >= MAXQUOTAS)
>  		return -EINVAL;
>  
> +	q_path = cmds & Q_PATH;
> +	cmds &= ~Q_PATH;
> +
>  	/*
>  	 * As a special case Q_SYNC can be called without a specific device.
>  	 * It will iterate all superblocks that have quota enabled and call
> @@ -847,28 +853,45 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
>  	 * resolution (think about autofs) and thus deadlocks could arise.
>  	 */
>  	if (cmds == Q_QUOTAON) {
> -		ret = user_path_at(AT_FDCWD, addr, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &path);
> +		ret = user_path_at(AT_FDCWD, addr,
> +				   LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> +				   &file_path);
>  		if (ret)
> -			pathp = ERR_PTR(ret);
> +			file_pathp = ERR_PTR(ret);
>  		else
> -			pathp = &path;
> +			file_pathp = &file_path;
>  	}
>  
> -	sb = quotactl_block(special, cmds);
> -	if (IS_ERR(sb)) {
> -		ret = PTR_ERR(sb);
> -		goto out;
> +	if (q_path) {
> +		ret = user_path_at(AT_FDCWD, special,
> +				   LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> +				   &sb_path);
> +		if (ret)
> +			goto out;
> +
> +		sb = sb_path.mnt->mnt_sb;

So I've realized that just looking up superblock with user_path_at() is not
enough. Quota code also expects that the superblock will be locked
(sb->s_umount) and filesystem will not be frozen (in case the quota
operation is going to modify the filesystem). This is needed to serialize
e.g. remount and quota operations or quota operations among themselves.
So you still need something like following to get superblock from the path:

static int quotactl_path(const char __user *special, int cmd, struct path *path)
{
	struct super_block *sb;

	ret = user_path_at(AT_FDCWD, special, LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
			   path);
	if (ret)
		return ret;
	sb = sb_path.mnt->mnt_sb;
restart:
	if (quotactl_cmd_onoff(cmd))
		down_write(&sb->s_umount);
	else
		down_read(&sb->s_umount);

	if (quotactl_cmd_write(cmd) && sb->s_writers.frozen != SB_UNFROZEN) {
		if (quotactl_cmd_onoff(cmd))
			up_write(&sb->s_umount);
		else
			up_read(&sb->s_umount);
		wait_event(sb->s_writers.wait_unfrozen,
			   sb->s_writers.frozen == SB_UNFROZEN);
		goto restart;

	}
	return sb;
}

And then appropriate counterparts when releasing the superblock.

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH 1/8] quota: Allow to pass mount path to quotactl
  2020-01-27 10:45   ` Jan Kara
@ 2020-01-28 10:06     ` Sascha Hauer
  2020-01-28 11:41       ` Jan Kara
  2020-01-29  1:29       ` Al Viro
  0 siblings, 2 replies; 19+ messages in thread
From: Sascha Hauer @ 2020-01-28 10:06 UTC (permalink / raw)
  To: Jan Kara; +Cc: linux-fsdevel, linux-mtd, Jan Kara, Richard Weinberger, kernel

Hi Jan,

On Mon, Jan 27, 2020 at 11:45:18AM +0100, Jan Kara wrote:
> >  	cmds = cmd >> SUBCMDSHIFT;
> >  	type = cmd & SUBCMDMASK;
> >  
> > +
> 
> Unnecessary empty line added...

Fixed

> > +	if (q_path) {
> > +		ret = user_path_at(AT_FDCWD, special,
> > +				   LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> > +				   &sb_path);
> > +		if (ret)
> > +			goto out;
> > +
> > +		sb = sb_path.mnt->mnt_sb;
> 
> So I've realized that just looking up superblock with user_path_at() is not
> enough. Quota code also expects that the superblock will be locked
> (sb->s_umount) and filesystem will not be frozen (in case the quota
> operation is going to modify the filesystem). This is needed to serialize
> e.g. remount and quota operations or quota operations among themselves.
> So you still need something like following to get superblock from the path:

Ok, here's an updated version. I'll send an update for the whole series
when Richard had a look over it.

Sascha

----------------------------8<-----------------------------

From 9c91395f2667c8a48f52a80896e559daf16f4a4c Mon Sep 17 00:00:00 2001
From: Sascha Hauer <s.hauer@pengutronix.de>
Date: Wed, 30 Oct 2019 08:35:11 +0100
Subject: [PATCH] quota: Allow to pass mount path to quotactl

This patch introduces the Q_PATH flag to the quotactl cmd argument.
When given, the path given in the special argument to quotactl will
be the mount path where the filesystem is mounted, instead of a path
to the block device.
This is necessary for filesystems which do not have a block device as
backing store. Particularly this is done for upcoming UBIFS support.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 fs/quota/quota.c           | 75 ++++++++++++++++++++++++++++++++------
 include/uapi/linux/quota.h |  1 +
 2 files changed, 64 insertions(+), 12 deletions(-)

diff --git a/fs/quota/quota.c b/fs/quota/quota.c
index 5444d3c4d93f..712b71760f9d 100644
--- a/fs/quota/quota.c
+++ b/fs/quota/quota.c
@@ -19,6 +19,7 @@
 #include <linux/types.h>
 #include <linux/writeback.h>
 #include <linux/nospec.h>
+#include <linux/mount.h>
 
 static int check_quotactl_permission(struct super_block *sb, int type, int cmd,
 				     qid_t id)
@@ -810,6 +811,36 @@ static struct super_block *quotactl_block(const char __user *special, int cmd)
 #endif
 }
 
+static struct super_block *quotactl_path(const char __user *special, int cmd,
+					 struct path *path)
+{
+	struct super_block *sb;
+	int ret;
+
+	ret = user_path_at(AT_FDCWD, special, LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
+			   path);
+	if (ret)
+		return ERR_PTR(ret);
+
+	sb = path->mnt->mnt_sb;
+restart:
+	if (quotactl_cmd_onoff(cmd))
+		down_write(&sb->s_umount);
+	else
+		down_read(&sb->s_umount);
+
+	if (quotactl_cmd_write(cmd) && sb->s_writers.frozen != SB_UNFROZEN) {
+		if (quotactl_cmd_onoff(cmd))
+			up_write(&sb->s_umount);
+		else
+			up_read(&sb->s_umount);
+		wait_event(sb->s_writers.wait_unfrozen,
+			   sb->s_writers.frozen == SB_UNFROZEN);
+		goto restart;
+	}
+
+	return sb;
+}
 /*
  * This is the system call interface. This communicates with
  * the user-level programs. Currently this only supports diskquota
@@ -821,8 +852,9 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
 {
 	uint cmds, type;
 	struct super_block *sb = NULL;
-	struct path path, *pathp = NULL;
+	struct path file_path, *file_pathp = NULL, sb_path;
 	int ret;
+	bool q_path;
 
 	cmds = cmd >> SUBCMDSHIFT;
 	type = cmd & SUBCMDMASK;
@@ -830,6 +862,9 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
 	if (type >= MAXQUOTAS)
 		return -EINVAL;
 
+	q_path = cmds & Q_PATH;
+	cmds &= ~Q_PATH;
+
 	/*
 	 * As a special case Q_SYNC can be called without a specific device.
 	 * It will iterate all superblocks that have quota enabled and call
@@ -847,28 +882,44 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
 	 * resolution (think about autofs) and thus deadlocks could arise.
 	 */
 	if (cmds == Q_QUOTAON) {
-		ret = user_path_at(AT_FDCWD, addr, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &path);
+		ret = user_path_at(AT_FDCWD, addr,
+				   LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
+				   &file_path);
 		if (ret)
-			pathp = ERR_PTR(ret);
+			file_pathp = ERR_PTR(ret);
 		else
-			pathp = &path;
+			file_pathp = &file_path;
 	}
 
-	sb = quotactl_block(special, cmds);
+	if (q_path)
+		sb = quotactl_path(special, cmds, &sb_path);
+	else
+		sb = quotactl_block(special, cmds);
+
 	if (IS_ERR(sb)) {
 		ret = PTR_ERR(sb);
 		goto out;
 	}
 
-	ret = do_quotactl(sb, type, cmds, id, addr, pathp);
+	ret = do_quotactl(sb, type, cmds, id, addr, file_pathp);
+
+	if (q_path) {
+		if (quotactl_cmd_onoff(cmd))
+			up_write(&sb->s_umount);
+		else
+			up_read(&sb->s_umount);
+
+		path_put(&sb_path);
+	} else {
+		if (!quotactl_cmd_onoff(cmds))
+			drop_super(sb);
+		else
+			drop_super_exclusive(sb);
+	}
 
-	if (!quotactl_cmd_onoff(cmds))
-		drop_super(sb);
-	else
-		drop_super_exclusive(sb);
 out:
-	if (pathp && !IS_ERR(pathp))
-		path_put(pathp);
+	if (file_pathp && !IS_ERR(file_pathp))
+		path_put(file_pathp);
 	return ret;
 }
 
diff --git a/include/uapi/linux/quota.h b/include/uapi/linux/quota.h
index f17c9636a859..e1787c0df601 100644
--- a/include/uapi/linux/quota.h
+++ b/include/uapi/linux/quota.h
@@ -71,6 +71,7 @@
 #define Q_GETQUOTA 0x800007	/* get user quota structure */
 #define Q_SETQUOTA 0x800008	/* set user quota structure */
 #define Q_GETNEXTQUOTA 0x800009	/* get disk limits and usage >= ID */
+#define Q_PATH     0x400000	/* quotactl special arg contains mount path */
 
 /* Quota format type IDs */
 #define	QFMT_VFS_OLD 1
-- 
2.25.0


-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

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

* Re: [PATCH 1/8] quota: Allow to pass mount path to quotactl
  2020-01-28 10:06     ` Sascha Hauer
@ 2020-01-28 11:41       ` Jan Kara
  2020-01-29  1:29       ` Al Viro
  1 sibling, 0 replies; 19+ messages in thread
From: Jan Kara @ 2020-01-28 11:41 UTC (permalink / raw)
  To: Sascha Hauer
  Cc: Jan Kara, linux-fsdevel, linux-mtd, Jan Kara, Richard Weinberger, kernel

On Tue 28-01-20 11:06:31, Sascha Hauer wrote:
> Hi Jan,
> 
> On Mon, Jan 27, 2020 at 11:45:18AM +0100, Jan Kara wrote:
> > >  	cmds = cmd >> SUBCMDSHIFT;
> > >  	type = cmd & SUBCMDMASK;
> > >  
> > > +
> > 
> > Unnecessary empty line added...
> 
> Fixed
> 
> > > +	if (q_path) {
> > > +		ret = user_path_at(AT_FDCWD, special,
> > > +				   LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> > > +				   &sb_path);
> > > +		if (ret)
> > > +			goto out;
> > > +
> > > +		sb = sb_path.mnt->mnt_sb;
> > 
> > So I've realized that just looking up superblock with user_path_at() is not
> > enough. Quota code also expects that the superblock will be locked
> > (sb->s_umount) and filesystem will not be frozen (in case the quota
> > operation is going to modify the filesystem). This is needed to serialize
> > e.g. remount and quota operations or quota operations among themselves.
> > So you still need something like following to get superblock from the path:
> 
> Ok, here's an updated version. I'll send an update for the whole series
> when Richard had a look over it.
> 
> Sascha
> 
> ----------------------------8<-----------------------------
> 
> From 9c91395f2667c8a48f52a80896e559daf16f4a4c Mon Sep 17 00:00:00 2001
> From: Sascha Hauer <s.hauer@pengutronix.de>
> Date: Wed, 30 Oct 2019 08:35:11 +0100
> Subject: [PATCH] quota: Allow to pass mount path to quotactl
> 
> This patch introduces the Q_PATH flag to the quotactl cmd argument.
> When given, the path given in the special argument to quotactl will
> be the mount path where the filesystem is mounted, instead of a path
> to the block device.
> This is necessary for filesystems which do not have a block device as
> backing store. Particularly this is done for upcoming UBIFS support.
> 
> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>

Thanks. The patch looks good to me now. You can add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  fs/quota/quota.c           | 75 ++++++++++++++++++++++++++++++++------
>  include/uapi/linux/quota.h |  1 +
>  2 files changed, 64 insertions(+), 12 deletions(-)
> 
> diff --git a/fs/quota/quota.c b/fs/quota/quota.c
> index 5444d3c4d93f..712b71760f9d 100644
> --- a/fs/quota/quota.c
> +++ b/fs/quota/quota.c
> @@ -19,6 +19,7 @@
>  #include <linux/types.h>
>  #include <linux/writeback.h>
>  #include <linux/nospec.h>
> +#include <linux/mount.h>
>  
>  static int check_quotactl_permission(struct super_block *sb, int type, int cmd,
>  				     qid_t id)
> @@ -810,6 +811,36 @@ static struct super_block *quotactl_block(const char __user *special, int cmd)
>  #endif
>  }
>  
> +static struct super_block *quotactl_path(const char __user *special, int cmd,
> +					 struct path *path)
> +{
> +	struct super_block *sb;
> +	int ret;
> +
> +	ret = user_path_at(AT_FDCWD, special, LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> +			   path);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	sb = path->mnt->mnt_sb;
> +restart:
> +	if (quotactl_cmd_onoff(cmd))
> +		down_write(&sb->s_umount);
> +	else
> +		down_read(&sb->s_umount);
> +
> +	if (quotactl_cmd_write(cmd) && sb->s_writers.frozen != SB_UNFROZEN) {
> +		if (quotactl_cmd_onoff(cmd))
> +			up_write(&sb->s_umount);
> +		else
> +			up_read(&sb->s_umount);
> +		wait_event(sb->s_writers.wait_unfrozen,
> +			   sb->s_writers.frozen == SB_UNFROZEN);
> +		goto restart;
> +	}
> +
> +	return sb;
> +}
>  /*
>   * This is the system call interface. This communicates with
>   * the user-level programs. Currently this only supports diskquota
> @@ -821,8 +852,9 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
>  {
>  	uint cmds, type;
>  	struct super_block *sb = NULL;
> -	struct path path, *pathp = NULL;
> +	struct path file_path, *file_pathp = NULL, sb_path;
>  	int ret;
> +	bool q_path;
>  
>  	cmds = cmd >> SUBCMDSHIFT;
>  	type = cmd & SUBCMDMASK;
> @@ -830,6 +862,9 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
>  	if (type >= MAXQUOTAS)
>  		return -EINVAL;
>  
> +	q_path = cmds & Q_PATH;
> +	cmds &= ~Q_PATH;
> +
>  	/*
>  	 * As a special case Q_SYNC can be called without a specific device.
>  	 * It will iterate all superblocks that have quota enabled and call
> @@ -847,28 +882,44 @@ int kernel_quotactl(unsigned int cmd, const char __user *special,
>  	 * resolution (think about autofs) and thus deadlocks could arise.
>  	 */
>  	if (cmds == Q_QUOTAON) {
> -		ret = user_path_at(AT_FDCWD, addr, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &path);
> +		ret = user_path_at(AT_FDCWD, addr,
> +				   LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> +				   &file_path);
>  		if (ret)
> -			pathp = ERR_PTR(ret);
> +			file_pathp = ERR_PTR(ret);
>  		else
> -			pathp = &path;
> +			file_pathp = &file_path;
>  	}
>  
> -	sb = quotactl_block(special, cmds);
> +	if (q_path)
> +		sb = quotactl_path(special, cmds, &sb_path);
> +	else
> +		sb = quotactl_block(special, cmds);
> +
>  	if (IS_ERR(sb)) {
>  		ret = PTR_ERR(sb);
>  		goto out;
>  	}
>  
> -	ret = do_quotactl(sb, type, cmds, id, addr, pathp);
> +	ret = do_quotactl(sb, type, cmds, id, addr, file_pathp);
> +
> +	if (q_path) {
> +		if (quotactl_cmd_onoff(cmd))
> +			up_write(&sb->s_umount);
> +		else
> +			up_read(&sb->s_umount);
> +
> +		path_put(&sb_path);
> +	} else {
> +		if (!quotactl_cmd_onoff(cmds))
> +			drop_super(sb);
> +		else
> +			drop_super_exclusive(sb);
> +	}
>  
> -	if (!quotactl_cmd_onoff(cmds))
> -		drop_super(sb);
> -	else
> -		drop_super_exclusive(sb);
>  out:
> -	if (pathp && !IS_ERR(pathp))
> -		path_put(pathp);
> +	if (file_pathp && !IS_ERR(file_pathp))
> +		path_put(file_pathp);
>  	return ret;
>  }
>  
> diff --git a/include/uapi/linux/quota.h b/include/uapi/linux/quota.h
> index f17c9636a859..e1787c0df601 100644
> --- a/include/uapi/linux/quota.h
> +++ b/include/uapi/linux/quota.h
> @@ -71,6 +71,7 @@
>  #define Q_GETQUOTA 0x800007	/* get user quota structure */
>  #define Q_SETQUOTA 0x800008	/* set user quota structure */
>  #define Q_GETNEXTQUOTA 0x800009	/* get disk limits and usage >= ID */
> +#define Q_PATH     0x400000	/* quotactl special arg contains mount path */
>  
>  /* Quota format type IDs */
>  #define	QFMT_VFS_OLD 1
> -- 
> 2.25.0
> 
> 
> -- 
> Pengutronix e.K.                           |                             |
> Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
> 31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
> Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH 1/8] quota: Allow to pass mount path to quotactl
  2020-01-28 10:06     ` Sascha Hauer
  2020-01-28 11:41       ` Jan Kara
@ 2020-01-29  1:29       ` Al Viro
  2020-01-29 16:14         ` Jan Kara
  2020-02-04 10:35         ` Sascha Hauer
  1 sibling, 2 replies; 19+ messages in thread
From: Al Viro @ 2020-01-29  1:29 UTC (permalink / raw)
  To: Sascha Hauer
  Cc: Jan Kara, linux-fsdevel, linux-mtd, Jan Kara, Richard Weinberger, kernel

On Tue, Jan 28, 2020 at 11:06:31AM +0100, Sascha Hauer wrote:
> Hi Jan,

> @@ -810,6 +811,36 @@ static struct super_block *quotactl_block(const char __user *special, int cmd)
>  #endif
>  }
>  
> +static struct super_block *quotactl_path(const char __user *special, int cmd,
> +					 struct path *path)
> +{
> +	struct super_block *sb;
> +	int ret;
> +
> +	ret = user_path_at(AT_FDCWD, special, LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> +			   path);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	sb = path->mnt->mnt_sb;
> +restart:
> +	if (quotactl_cmd_onoff(cmd))
> +		down_write(&sb->s_umount);
> +	else
> +		down_read(&sb->s_umount);
> +
> +	if (quotactl_cmd_write(cmd) && sb->s_writers.frozen != SB_UNFROZEN) {
> +		if (quotactl_cmd_onoff(cmd))
> +			up_write(&sb->s_umount);
> +		else
> +			up_read(&sb->s_umount);
> +		wait_event(sb->s_writers.wait_unfrozen,
> +			   sb->s_writers.frozen == SB_UNFROZEN);
> +		goto restart;
> +	}
> +
> +	return sb;
> +}

This partial duplicate of __get_super_thawed() guts does *not* belong here,
especially not interleaved with quota-specific checks.

> +	if (q_path) {
> +		if (quotactl_cmd_onoff(cmd))
> +			up_write(&sb->s_umount);
> +		else
> +			up_read(&sb->s_umount);
> +
> +		path_put(&sb_path);
> +	} else {
> +		if (!quotactl_cmd_onoff(cmds))
> +			drop_super(sb);
> +		else
> +			drop_super_exclusive(sb);
> +	}

Er...  Why not have the same code that you've used to lock the damn thing
(needs to be moved to fs/super.c) simply get a passive ref to it?  Then
you could do the same thing, q_path or no q_path...

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

* Re: [PATCH 1/8] quota: Allow to pass mount path to quotactl
  2020-01-29  1:29       ` Al Viro
@ 2020-01-29 16:14         ` Jan Kara
  2020-02-04 10:35         ` Sascha Hauer
  1 sibling, 0 replies; 19+ messages in thread
From: Jan Kara @ 2020-01-29 16:14 UTC (permalink / raw)
  To: Al Viro
  Cc: Sascha Hauer, Jan Kara, linux-fsdevel, linux-mtd, Jan Kara,
	Richard Weinberger, kernel

On Wed 29-01-20 01:29:29, Al Viro wrote:
> On Tue, Jan 28, 2020 at 11:06:31AM +0100, Sascha Hauer wrote:
> > Hi Jan,
> 
> > @@ -810,6 +811,36 @@ static struct super_block *quotactl_block(const char __user *special, int cmd)
> >  #endif
> >  }
> >  
> > +static struct super_block *quotactl_path(const char __user *special, int cmd,
> > +					 struct path *path)
> > +{
> > +	struct super_block *sb;
> > +	int ret;
> > +
> > +	ret = user_path_at(AT_FDCWD, special, LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> > +			   path);
> > +	if (ret)
> > +		return ERR_PTR(ret);
> > +
> > +	sb = path->mnt->mnt_sb;
> > +restart:
> > +	if (quotactl_cmd_onoff(cmd))
> > +		down_write(&sb->s_umount);
> > +	else
> > +		down_read(&sb->s_umount);
> > +
> > +	if (quotactl_cmd_write(cmd) && sb->s_writers.frozen != SB_UNFROZEN) {
> > +		if (quotactl_cmd_onoff(cmd))
> > +			up_write(&sb->s_umount);
> > +		else
> > +			up_read(&sb->s_umount);
> > +		wait_event(sb->s_writers.wait_unfrozen,
> > +			   sb->s_writers.frozen == SB_UNFROZEN);
> > +		goto restart;
> > +	}
> > +
> > +	return sb;
> > +}
> 
> This partial duplicate of __get_super_thawed() guts does *not* belong here,
> especially not interleaved with quota-specific checks.

OK, so some primitive in fs/super.c like:

void hold_super_thawed(struct super_block *sb, bool excl);

that would implement the above functionality and grab passive reference?

> > +	if (q_path) {
> > +		if (quotactl_cmd_onoff(cmd))
> > +			up_write(&sb->s_umount);
> > +		else
> > +			up_read(&sb->s_umount);
> > +
> > +		path_put(&sb_path);
> > +	} else {
> > +		if (!quotactl_cmd_onoff(cmds))
> > +			drop_super(sb);
> > +		else
> > +			drop_super_exclusive(sb);
> > +	}
> 
> Er...  Why not have the same code that you've used to lock the damn thing
> (needs to be moved to fs/super.c) simply get a passive ref to it?  Then
> you could do the same thing, q_path or no q_path...

Yes.

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH 1/8] quota: Allow to pass mount path to quotactl
  2020-01-29  1:29       ` Al Viro
  2020-01-29 16:14         ` Jan Kara
@ 2020-02-04 10:35         ` Sascha Hauer
  2020-02-18  9:22           ` Jan Kara
  1 sibling, 1 reply; 19+ messages in thread
From: Sascha Hauer @ 2020-02-04 10:35 UTC (permalink / raw)
  To: Al Viro
  Cc: Jan Kara, linux-fsdevel, linux-mtd, Jan Kara, Richard Weinberger, kernel

On Wed, Jan 29, 2020 at 01:29:29AM +0000, Al Viro wrote:
> On Tue, Jan 28, 2020 at 11:06:31AM +0100, Sascha Hauer wrote:
> > Hi Jan,
> 
> > @@ -810,6 +811,36 @@ static struct super_block *quotactl_block(const char __user *special, int cmd)
> >  #endif
> >  }
> >  
> > +static struct super_block *quotactl_path(const char __user *special, int cmd,
> > +					 struct path *path)
> > +{
> > +	struct super_block *sb;
> > +	int ret;
> > +
> > +	ret = user_path_at(AT_FDCWD, special, LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> > +			   path);
> > +	if (ret)
> > +		return ERR_PTR(ret);
> > +
> > +	sb = path->mnt->mnt_sb;
> > +restart:
> > +	if (quotactl_cmd_onoff(cmd))
> > +		down_write(&sb->s_umount);
> > +	else
> > +		down_read(&sb->s_umount);
> > +
> > +	if (quotactl_cmd_write(cmd) && sb->s_writers.frozen != SB_UNFROZEN) {
> > +		if (quotactl_cmd_onoff(cmd))
> > +			up_write(&sb->s_umount);
> > +		else
> > +			up_read(&sb->s_umount);
> > +		wait_event(sb->s_writers.wait_unfrozen,
> > +			   sb->s_writers.frozen == SB_UNFROZEN);
> > +		goto restart;
> > +	}
> > +
> > +	return sb;
> > +}
> 
> This partial duplicate of __get_super_thawed() guts does *not* belong here,
> especially not interleaved with quota-specific checks.
> 
> > +	if (q_path) {
> > +		if (quotactl_cmd_onoff(cmd))
> > +			up_write(&sb->s_umount);
> > +		else
> > +			up_read(&sb->s_umount);
> > +
> > +		path_put(&sb_path);
> > +	} else {
> > +		if (!quotactl_cmd_onoff(cmds))
> > +			drop_super(sb);
> > +		else
> > +			drop_super_exclusive(sb);
> > +	}
> 
> Er...  Why not have the same code that you've used to lock the damn thing
> (needs to be moved to fs/super.c) simply get a passive ref to it?  Then
> you could do the same thing, q_path or no q_path...

I am getting confused here. To an earlier version of this series you
responded:

> And for path-based you don't need to mess with superblock
> references - just keep the struct path until the end.  That
> will keep the superblock alive and active just fine.

I did that and got the objection from Jan:

> So I've realized that just looking up superblock with user_path_at() is not
> enough. Quota code also expects that the superblock will be locked
> (sb->s_umount) and filesystem will not be frozen (in case the quota
> operation is going to modify the filesystem). This is needed to serialize
> e.g. remount and quota operations or quota operations among themselves.

So after drawing circles we now seem to be back at passive references.
What I have now in my tree is this in fs/super.c, untested currently:

static bool __grab_super_thawed(struct super_block *sb, bool excl)
{
	while (1) {
		bool again = false;

		spin_lock(&sb_lock);

		if (hlist_unhashed(&sb->s_instances)) {
			spin_unlock(&sb_lock);
			return false;
		}

		sb->s_count++;
		spin_unlock(&sb_lock);

		if (excl)
			down_write(&sb->s_umount);
		else
			down_read(&sb->s_umount);

		if (sb->s_root && (sb->s_flags & SB_BORN)) {
			if (sb->s_writers.frozen == SB_UNFROZEN)
				return true;
			else
				again = true;
		}

		if (excl)
			up_write(&sb->s_umount);
		else
			up_read(&sb->s_umount);

		if (again)
			wait_event(sb->s_writers.wait_unfrozen,
				   sb->s_writers.frozen == SB_UNFROZEN);

		put_super(sb);

		if (!again)
			return false;
	}

	return ret;
}

int grab_super_thawed(struct super_block *sb)
{
	return __grab_super_thawed(sb, false);
}

int grab_super_exclusive_thawed(struct super_block *sb)
{
	return __grab_super_thawed(sb, true);
}

Does this look ok now?

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

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

* Re: [PATCH 1/8] quota: Allow to pass mount path to quotactl
  2020-02-04 10:35         ` Sascha Hauer
@ 2020-02-18  9:22           ` Jan Kara
  0 siblings, 0 replies; 19+ messages in thread
From: Jan Kara @ 2020-02-18  9:22 UTC (permalink / raw)
  To: Sascha Hauer
  Cc: Al Viro, Jan Kara, linux-fsdevel, linux-mtd, Jan Kara,
	Richard Weinberger, kernel

[-- Attachment #1: Type: text/plain, Size: 3472 bytes --]

I'm sorry for the late reply, I was busy with other things and I wasn't
quite sure how I'd like this to be handled :)

On Tue 04-02-20 11:35:23, Sascha Hauer wrote:
> On Wed, Jan 29, 2020 at 01:29:29AM +0000, Al Viro wrote:
> > On Tue, Jan 28, 2020 at 11:06:31AM +0100, Sascha Hauer wrote:
> > > Hi Jan,
> > 
> > > @@ -810,6 +811,36 @@ static struct super_block *quotactl_block(const char __user *special, int cmd)
> > >  #endif
> > >  }
> > >  
> > > +static struct super_block *quotactl_path(const char __user *special, int cmd,
> > > +					 struct path *path)
> > > +{
> > > +	struct super_block *sb;
> > > +	int ret;
> > > +
> > > +	ret = user_path_at(AT_FDCWD, special, LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT,
> > > +			   path);
> > > +	if (ret)
> > > +		return ERR_PTR(ret);
> > > +
> > > +	sb = path->mnt->mnt_sb;
> > > +restart:
> > > +	if (quotactl_cmd_onoff(cmd))
> > > +		down_write(&sb->s_umount);
> > > +	else
> > > +		down_read(&sb->s_umount);
> > > +
> > > +	if (quotactl_cmd_write(cmd) && sb->s_writers.frozen != SB_UNFROZEN) {
> > > +		if (quotactl_cmd_onoff(cmd))
> > > +			up_write(&sb->s_umount);
> > > +		else
> > > +			up_read(&sb->s_umount);
> > > +		wait_event(sb->s_writers.wait_unfrozen,
> > > +			   sb->s_writers.frozen == SB_UNFROZEN);
> > > +		goto restart;
> > > +	}
> > > +
> > > +	return sb;
> > > +}
> > 
> > This partial duplicate of __get_super_thawed() guts does *not* belong here,
> > especially not interleaved with quota-specific checks.
> > 
> > > +	if (q_path) {
> > > +		if (quotactl_cmd_onoff(cmd))
> > > +			up_write(&sb->s_umount);
> > > +		else
> > > +			up_read(&sb->s_umount);
> > > +
> > > +		path_put(&sb_path);
> > > +	} else {
> > > +		if (!quotactl_cmd_onoff(cmds))
> > > +			drop_super(sb);
> > > +		else
> > > +			drop_super_exclusive(sb);
> > > +	}
> > 
> > Er...  Why not have the same code that you've used to lock the damn thing
> > (needs to be moved to fs/super.c) simply get a passive ref to it?  Then
> > you could do the same thing, q_path or no q_path...
> 
> I am getting confused here. To an earlier version of this series you
> responded:
> 
> > And for path-based you don't need to mess with superblock
> > references - just keep the struct path until the end.  That
> > will keep the superblock alive and active just fine.
> 
> I did that and got the objection from Jan:
> 
> > So I've realized that just looking up superblock with user_path_at() is not
> > enough. Quota code also expects that the superblock will be locked
> > (sb->s_umount) and filesystem will not be frozen (in case the quota
> > operation is going to modify the filesystem). This is needed to serialize
> > e.g. remount and quota operations or quota operations among themselves.

Yes, using passive reference is not necessary. On the other hand the
symmetry with how get_super() and friends work has some appeal too so if Al
wants that, well, he's the maintainer ;)

> So after drawing circles we now seem to be back at passive references.
> What I have now in my tree is this in fs/super.c, untested currently:

I was thinking how to make the API most sensible. In the end I've decided
for a variant that is attached - we pass in struct path which enforces
active reference to a superblock and thus we don't have to be afraid of the
superblock going away or similar problems. Also the operation "get me
superblock for a path" kind of makes sense...

Guys, what do you think?

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

[-- Attachment #2: 0001-fs-Provide-functions-for-getting-locked-and-thawed-s.patch --]
[-- Type: text/x-patch, Size: 3714 bytes --]

From 18c7913475342f10b4723c7e22409acc573e6436 Mon Sep 17 00:00:00 2001
From: Jan Kara <jack@suse.cz>
Date: Tue, 18 Feb 2020 10:05:21 +0100
Subject: [PATCH] fs: Provide functions for getting locked and thawed
 superblock from a path

Provide functions to get locked (with s_umount) and if desired also thawed
superblock for a given struct path. We additionally also get passive
reference count of the superblock so that the superblock can be unlocked
with put_super() / put_super_exlusive() similarly to how get_super_*()
class of functions operates.

Signed-off-by: Jan Kara <jack@suse.cz>
---
 fs/super.c         | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/fs.h |  3 +++
 2 files changed, 67 insertions(+)

diff --git a/fs/super.c b/fs/super.c
index cd352530eca9..2c78eac57fa5 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -836,6 +836,70 @@ struct super_block *get_super_exclusive_thawed(struct block_device *bdev)
 }
 EXPORT_SYMBOL(get_super_exclusive_thawed);
 
+static struct super_block *__hold_super(struct path *path, bool excl,
+					bool thawed)
+{
+	struct super_block *sb = path->mnt->mnt_sb;
+
+	while (1) {
+		if (excl)
+			down_write(&sb->s_umount);
+		else
+			down_read(&sb->s_umount);
+		if (!thawed || sb->s_writers.frozen == SB_UNFROZEN) {
+			spin_lock(&sb_lock);
+			sb->s_count++;
+			spin_unlock(&sb_lock);
+			return sb;
+		}
+		if (excl)
+			up_write(&sb->s_umount);
+		else
+			up_read(&sb->s_umount);
+		wait_event(sb->s_writers.wait_unfrozen,
+					sb->s_writers.frozen == SB_UNFROZEN);
+	}
+}
+
+/**
+ *	hold_super - get locked superblock for a path
+ *	@path: path to get superblock for
+ *
+ *	This function gets superblock for @path and returns it with s_umount
+ *	held in shared mode and superblock's passive refcount (sb->s_count)
+ *	incremented.
+ */
+struct super_block *hold_super(struct path *path)
+{
+	return __hold_super(path, false, false);
+}
+
+/**
+ *	hold_super_thawed - get locked thawed superblock for a path
+ *	@path: path to get superblock for
+ *
+ *	This function gets superblock for @path, makes sure it is not frozen,
+ *	and returns it with s_umount held in shared mode and superblock's
+ *	passive refcount (sb->s_count) incremented.
+ */
+struct super_block *hold_super_thawed(struct path *path)
+{
+	return __hold_super(path, false, true);
+}
+
+/**
+ *	hold_super_thawed_exclusive - get locked thawed superblock for a path
+ *	@path: path to get superblock for
+ *
+ *	This function gets superblock for @path, makes sure it is not frozen,
+ *	and returns it with s_umount held in exclusive mode and superblock's
+ *	passive refcount (sb->s_count) incremented.
+ */
+struct super_block *hold_super_thawed_exclusive(struct path *path)
+{
+	return __hold_super(path, true, true);
+}
+
 /**
  * get_active_super - get an active reference to the superblock of a device
  * @bdev: device to get the superblock for
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3cd4fe6b845e..2406494d2f54 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3305,6 +3305,9 @@ extern struct file_system_type *get_fs_type(const char *name);
 extern struct super_block *get_super(struct block_device *);
 extern struct super_block *get_super_thawed(struct block_device *);
 extern struct super_block *get_super_exclusive_thawed(struct block_device *bdev);
+struct super_block *hold_super(struct path *path);
+struct super_block *hold_super_thawed(struct path *path);
+struct super_block *hold_super_thawed_exclusive(struct path *path);
 extern struct super_block *get_active_super(struct block_device *bdev);
 extern void drop_super(struct super_block *sb);
 extern void drop_super_exclusive(struct super_block *sb);
-- 
2.16.4


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

* [PATCH 8/8] ubifs: Add quota support
  2021-01-22 15:15 [PATCH v5 0/8] Add quota support to UBIFS Sascha Hauer
@ 2021-01-22 15:15 ` Sascha Hauer
  0 siblings, 0 replies; 19+ messages in thread
From: Sascha Hauer @ 2021-01-22 15:15 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Richard Weinberger, linux-mtd, kernel, Jan Kara, Sascha Hauer,
	Dongsheng Yang

This introduces poor man's quota support for UBIFS. Unlike other
implementations we never store anything on the flash. This has two
big advantages:

- No possible regressions with a changed on-disk format
- no quota files can get out of sync.

There are downsides as well:

- During mount the whole index must be scanned which takes some time
- The quota limits must be set manually each time a filesystem is mounted.

UBIFS is targetted for embedded systems and quota limits are likely not
changed interactively, so having to restore the quota limits with a
script shouldn't be a big deal. The mount time penalty is a price we
must pay, but for that we get a simple and straight forward
implementation for this rather rarely used feature.

The quota data itself is stored in a red-black tree in memory. It is
implemented as a quota format. When enabled with the "quota" mount
option all three quota types (user, group, project) are enabled.

The quota integration into UBIFS is taken from a series posted earlier
by Dongsheng Yang. Like the earlier series we only account regular files
for quota. All others are counted in the number of files, but do not
require any quota space.

Signed-off-by: Dongsheng Yang <yangds.fnst@cn.fujitsu.com>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 Documentation/filesystems/ubifs.rst |   6 +
 fs/ubifs/Makefile                   |   1 +
 fs/ubifs/dir.c                      | 103 +++--
 fs/ubifs/file.c                     |  43 ++
 fs/ubifs/ioctl.c                    |  32 ++
 fs/ubifs/journal.c                  |   2 +
 fs/ubifs/quota.c                    | 606 ++++++++++++++++++++++++++++
 fs/ubifs/super.c                    |  86 +++-
 fs/ubifs/ubifs.h                    |  37 ++
 fs/ubifs/xattr.c                    |   5 +-
 include/uapi/linux/quota.h          |   1 +
 11 files changed, 896 insertions(+), 26 deletions(-)
 create mode 100644 fs/ubifs/quota.c

diff --git a/Documentation/filesystems/ubifs.rst b/Documentation/filesystems/ubifs.rst
index e6ee99762534..999b815afb38 100644
--- a/Documentation/filesystems/ubifs.rst
+++ b/Documentation/filesystems/ubifs.rst
@@ -105,6 +105,12 @@ auth_key=		specify the key used for authenticating the filesystem.
 auth_hash_name=		The hash algorithm used for authentication. Used for
 			both hashing and for creating HMACs. Typical values
 			include "sha256" or "sha512"
+usrquota		Enable user disk quota support
+			(requires CONFIG_QUOTA).
+grpquota		Enable group disk quota support
+			(requires CONFIG_QUOTA).
+prjquota		Enable project disk quota support
+			(requires CONFIG_QUOTA).
 ====================	=======================================================
 
 
diff --git a/fs/ubifs/Makefile b/fs/ubifs/Makefile
index 5c4b845754a7..07f657f5ea8f 100644
--- a/fs/ubifs/Makefile
+++ b/fs/ubifs/Makefile
@@ -9,3 +9,4 @@ ubifs-y += misc.o
 ubifs-$(CONFIG_FS_ENCRYPTION) += crypto.o
 ubifs-$(CONFIG_UBIFS_FS_XATTR) += xattr.o
 ubifs-$(CONFIG_UBIFS_FS_AUTHENTICATION) += auth.o
+ubifs-$(CONFIG_QUOTA) += quota.o
diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c
index e724fc91e2d1..335b5e8e139c 100644
--- a/fs/ubifs/dir.c
+++ b/fs/ubifs/dir.c
@@ -64,28 +64,13 @@ static int inherit_flags(const struct inode *dir, umode_t mode)
 	return flags;
 }
 
-/**
- * ubifs_new_inode - allocate new UBIFS inode object.
- * @c: UBIFS file-system description object
- * @dir: parent directory inode
- * @mode: inode mode flags
- *
- * This function finds an unused inode number, allocates new inode and
- * initializes it. Returns new inode in case of success and an error code in
- * case of failure.
- */
-struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
-			      umode_t mode)
+static int ubifs_init_inode(struct ubifs_info *c, struct inode *inode,
+			    struct inode *dir, umode_t mode)
 {
 	int err;
-	struct inode *inode;
 	struct ubifs_inode *ui;
 	bool encrypted = false;
 
-	inode = new_inode(c->vfs_sb);
-	if (!inode)
-		return ERR_PTR(-ENOMEM);
-
 	ui = ubifs_inode(inode);
 	/*
 	 * Set 'S_NOCMTIME' to prevent VFS form updating [mc]time of inodes and
@@ -103,7 +88,7 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
 	err = fscrypt_prepare_new_inode(dir, inode, &encrypted);
 	if (err) {
 		ubifs_err(c, "fscrypt_prepare_new_inode failed: %i", err);
-		goto out_iput;
+		return err;
 	}
 
 	if (ubifs_inode(dir)->flags & UBIFS_PROJINHERIT_FL)
@@ -111,6 +96,11 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
 	else
 		ui->projid = make_kprojid(&init_user_ns, UBIFS_DEF_PROJID);
 
+	dquot_initialize(inode);
+	err = dquot_alloc_inode(inode);
+	if (err)
+		return err;
+
 	switch (mode & S_IFMT) {
 	case S_IFREG:
 		inode->i_mapping->a_ops = &ubifs_file_address_operations;
@@ -150,7 +140,7 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
 			spin_unlock(&c->cnt_lock);
 			ubifs_err(c, "out of inode numbers");
 			err = -EINVAL;
-			goto out_iput;
+			goto out_drop;
 		}
 		ubifs_warn(c, "running out of inode numbers (current %lu, max %u)",
 			   (unsigned long)c->highest_inum, INUM_WATERMARK);
@@ -171,16 +161,79 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
 		err = fscrypt_set_context(inode, NULL);
 		if (err) {
 			ubifs_err(c, "fscrypt_set_context failed: %i", err);
-			goto out_iput;
+			goto out_bad;
 		}
 	}
 
-	return inode;
+	return 0;
 
-out_iput:
+out_bad:
 	make_bad_inode(inode);
-	iput(inode);
-	return ERR_PTR(err);
+
+	dquot_free_inode(inode);
+out_drop:
+	dquot_drop(inode);
+
+	return err;
+}
+
+/**
+ * ubifs_new_inode - allocate new UBIFS inode object.
+ * @c: UBIFS file-system description object
+ * @dir: parent directory inode
+ * @mode: inode mode flags
+ *
+ * This function finds an unused inode number, allocates new inode and
+ * initializes it. Returns new inode in case of success and an error code in
+ * case of failure.
+ */
+struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
+			      umode_t mode)
+{
+	struct inode *inode;
+	int err;
+
+	inode = new_inode(c->vfs_sb);
+	if (!inode)
+		return ERR_PTR(-ENOMEM);
+
+	err = ubifs_init_inode(c, inode, dir, mode);
+	if (err) {
+		iput(inode);
+		return ERR_PTR(err);
+	}
+
+	return inode;
+}
+
+/**
+ * ubifs_new_xattr_inode - allocate new UBIFS inode object to be used as xattr
+ * @c: UBIFS file-system description object
+ * @dir: parent directory inode
+ * @mode: inode mode flags
+ *
+ * like ubifs_new_inode, but allocates an inode without quota accounting to be
+ * used for xattrs.
+ */
+struct inode *ubifs_new_xattr_inode(struct ubifs_info *c, struct inode *dir,
+			      umode_t mode)
+{
+	struct inode *inode;
+	int err;
+
+	inode = new_inode(c->vfs_sb);
+	if (!inode)
+		return ERR_PTR(-ENOMEM);
+
+	inode->i_flags |= S_NOQUOTA;
+
+	err = ubifs_init_inode(c, inode, dir, mode);
+	if (err) {
+		iput(inode);
+		return ERR_PTR(err);
+	}
+
+	return inode;
 }
 
 static int dbg_check_name(const struct ubifs_info *c,
@@ -780,6 +833,8 @@ static int ubifs_unlink(struct inode *dir, struct dentry *dentry)
 	unsigned int saved_nlink = inode->i_nlink;
 	struct fscrypt_name nm;
 
+	dquot_initialize(inode);
+
 	/*
 	 * Budget request settings: deletion direntry, deletion inode (+1 for
 	 * @dirtied_ino), changing the parent directory inode. If budgeting
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index 2bc7780d2963..d74134692d75 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -427,8 +427,10 @@ static int ubifs_write_begin(struct file *file, struct address_space *mapping,
 	struct ubifs_inode *ui = ubifs_inode(inode);
 	pgoff_t index = pos >> PAGE_SHIFT;
 	int err, appending = !!(pos + len > inode->i_size);
+	int quota_size = 0;
 	int skipped_read = 0;
 	struct page *page;
+	int ret = 0;
 
 	ubifs_assert(c, ubifs_inode(inode)->ui_size == inode->i_size);
 	ubifs_assert(c, !c->ro_media && !c->ro_mount);
@@ -436,6 +438,16 @@ static int ubifs_write_begin(struct file *file, struct address_space *mapping,
 	if (unlikely(c->ro_error))
 		return -EROFS;
 
+	quota_size = ((pos + len) - inode->i_size);
+	if (quota_size < 0)
+		quota_size = 0;
+	if (S_ISREG(inode->i_mode)) {
+		dquot_initialize(inode);
+		ret = dquot_alloc_space_nodirty(inode, quota_size);
+		if (unlikely(ret))
+			return ret;
+	}
+
 	/* Try out the fast-path part first */
 	page = grab_cache_page_write_begin(mapping, index, flags);
 	if (unlikely(!page))
@@ -541,6 +553,7 @@ static int ubifs_write_end(struct file *file, struct address_space *mapping,
 	struct ubifs_inode *ui = ubifs_inode(inode);
 	struct ubifs_info *c = inode->i_sb->s_fs_info;
 	loff_t end_pos = pos + len;
+	int quota_size = 0;
 	int appending = !!(end_pos > inode->i_size);
 
 	dbg_gen("ino %lu, pos %llu, pg %lu, len %u, copied %d, i_size %lld",
@@ -559,6 +572,11 @@ static int ubifs_write_end(struct file *file, struct address_space *mapping,
 		dbg_gen("copied %d instead of %d, read page and repeat",
 			copied, len);
 		cancel_budget(c, page, ui, appending);
+		quota_size = ((pos + len) - inode->i_size);
+		if (quota_size < 0)
+			quota_size = 0;
+		if (S_ISREG(inode->i_mode))
+			dquot_free_space_nodirty(inode, quota_size);
 		ClearPageChecked(page);
 
 		/*
@@ -1111,6 +1129,7 @@ static int do_truncation(struct ubifs_info *c, struct inode *inode,
 	int err;
 	struct ubifs_budget_req req;
 	loff_t old_size = inode->i_size, new_size = attr->ia_size;
+	loff_t quota_size = (old_size - new_size);
 	int offset = new_size & (UBIFS_BLOCK_SIZE - 1), budgeted = 1;
 	struct ubifs_inode *ui = ubifs_inode(inode);
 
@@ -1190,6 +1209,10 @@ static int do_truncation(struct ubifs_info *c, struct inode *inode,
 	do_attr_changes(inode, attr);
 	err = ubifs_jnl_truncate(c, inode, old_size, new_size);
 	mutex_unlock(&ui->ui_mutex);
+	if (quota_size < 0)
+		quota_size = 0;
+	if (S_ISREG(inode->i_mode))
+		dquot_free_space_nodirty(inode, quota_size);
 
 out_budg:
 	if (budgeted)
@@ -1226,6 +1249,17 @@ static int do_setattr(struct ubifs_info *c, struct inode *inode,
 
 	if (attr->ia_valid & ATTR_SIZE) {
 		dbg_gen("size %lld -> %lld", inode->i_size, new_size);
+		if (S_ISREG(inode->i_mode)) {
+			if (new_size > inode->i_size) {
+				err = dquot_alloc_space_nodirty(inode, new_size - inode->i_size);
+				if (err) {
+					ubifs_release_budget(c, &req);
+					return err;
+				}
+			} else {
+				dquot_free_space_nodirty(inode, inode->i_size - new_size);
+			}
+		}
 		truncate_setsize(inode, new_size);
 	}
 
@@ -1277,6 +1311,15 @@ int ubifs_setattr(struct dentry *dentry, struct iattr *attr)
 	if (err)
 		return err;
 
+	if (is_quota_modification(inode, attr))
+		dquot_initialize(inode);
+	if ((attr->ia_valid & ATTR_UID && !uid_eq(attr->ia_uid, inode->i_uid)) ||
+	    (attr->ia_valid & ATTR_GID && !gid_eq(attr->ia_gid, inode->i_gid))) {
+		err = dquot_transfer(inode, attr);
+		if (err)
+			return err;
+	}
+
 	if ((attr->ia_valid & ATTR_SIZE) && attr->ia_size < inode->i_size)
 		/* Truncation to a smaller size */
 		err = do_truncation(c, inode, attr);
diff --git a/fs/ubifs/ioctl.c b/fs/ubifs/ioctl.c
index a9a79935cc13..f63af4c2e194 100644
--- a/fs/ubifs/ioctl.c
+++ b/fs/ubifs/ioctl.c
@@ -139,16 +139,48 @@ static __u32 ubifs_iflags_to_xflags(unsigned long flags)
         return xflags;
 }
 
+#ifdef CONFIG_QUOTA
+static int ubifs_transfer_project_quota(struct inode *inode, kprojid_t kprojid)
+{
+	struct dquot *transfer_to[MAXQUOTAS] = {};
+	struct ubifs_info *c = inode->i_sb->s_fs_info;
+	struct super_block *sb = c->vfs_sb;
+	int err = 0;
+
+	err = dquot_initialize(inode);
+	if (err)
+		return err;
+
+	transfer_to[PRJQUOTA] = dqget(sb, make_kqid_projid(kprojid));
+	if (!IS_ERR(transfer_to[PRJQUOTA])) {
+		err = __dquot_transfer(inode, transfer_to);
+		dqput(transfer_to[PRJQUOTA]);
+	}
+
+	return err;
+}
+#else
+static int ubifs_transfer_project_quota(struct inode *inode, kprojid_t kprojid)
+{
+	return 0;
+}
+#endif
+
 static int ubifs_ioc_setproject(struct file *file, __u32 projid)
 {
 	struct inode *inode = file_inode(file);
 	struct ubifs_inode *ui = ubifs_inode(inode);
 	kprojid_t kprojid;
+	int err;
 
 	kprojid = make_kprojid(&init_user_ns, (projid_t)projid);
 	if (projid_eq(kprojid, ui->projid))
 		return 0;
 
+	err = ubifs_transfer_project_quota(inode, kprojid);
+	if (err)
+		return err;
+
 	ui->projid = kprojid;
 
 	return 0;
diff --git a/fs/ubifs/journal.c b/fs/ubifs/journal.c
index 2463d417bf6c..b16855259977 100644
--- a/fs/ubifs/journal.c
+++ b/fs/ubifs/journal.c
@@ -631,6 +631,8 @@ int ubifs_jnl_update(struct ubifs_info *c, const struct inode *dir,
 		}
 		ui->del_cmtno = c->cmt_no;
 		orphan_added = 1;
+		if (S_ISREG(inode->i_mode))
+			dquot_free_space_nodirty((struct inode *)inode, inode->i_size);
 	}
 
 	err = write_head(c, BASEHD, dent, len, &lnum, &dent_offs, sync);
diff --git a/fs/ubifs/quota.c b/fs/ubifs/quota.c
new file mode 100644
index 000000000000..bebefb846b1b
--- /dev/null
+++ b/fs/ubifs/quota.c
@@ -0,0 +1,606 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file is part of UBIFS.
+ *
+ * Copyright (C) 2019 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
+ */
+#include "ubifs.h"
+
+struct ubifs_dqblk {
+	struct mem_dqblk dqblk;
+	struct rb_node rb;
+	struct kqid kqid;
+};
+
+/**
+ * ubifs_dqblk_insert - find qid in tree
+ * @c: UBIFS file-system description object
+ * @new: The new entry to insert
+ *
+ * This inserts a new entry to the dqblk tree which is sorted by qids. Caller
+ * must make sure this entry doesn't exist already.
+ */
+static void ubifs_dqblk_insert(struct ubifs_info *c, struct ubifs_dqblk *new)
+{
+	struct rb_root *root = &c->dqblk_tree[new->kqid.type];
+	struct rb_node **link = &root->rb_node, *parent = NULL;
+	struct ubifs_dqblk *ud;
+
+	/* Go to the bottom of the tree */
+	while (*link) {
+		parent = *link;
+
+		ud = rb_entry(parent, struct ubifs_dqblk, rb);
+
+		if (qid_lt(new->kqid, ud->kqid))
+			link = &(*link)->rb_left;
+		else
+			link = &(*link)->rb_right;
+	}
+
+	/* Put the new node there */
+	rb_link_node(&new->rb, parent, link);
+	rb_insert_color(&new->rb, root);
+}
+
+/**
+ * ubifs_dqblk_find_next - find the next qid
+ * @c: UBIFS file-system description object
+ * @qid: The qid to look for
+ *
+ * Find the next dqblk entry with a qid that is bigger or equally big than the
+ * given qid. Returns the next dqblk entry if found or NULL if no dqblk exists
+ * with a qid that is at least equally big.
+ */
+static struct ubifs_dqblk *ubifs_dqblk_find_next(struct ubifs_info *c,
+						 struct kqid qid)
+{
+	struct rb_node *node = c->dqblk_tree[qid.type].rb_node;
+	struct ubifs_dqblk *next = NULL;
+
+	while (node) {
+		struct ubifs_dqblk *ud = rb_entry(node, struct ubifs_dqblk, rb);
+
+		if (qid_eq(qid, ud->kqid))
+			return ud;
+
+		if (qid_lt(qid, ud->kqid)) {
+			next = ud;
+			node = node->rb_left;
+		} else {
+			node = node->rb_right;
+		}
+	}
+
+	return next;
+}
+
+/**
+ * ubifs_dqblk_find - find qid in tree
+ * @c: UBIFS file-system description object
+ * @qid: The qid to look for
+ *
+ * This walks the dqblk tree and searches a given qid. Returns the dqblk entry
+ * when found or NULL otherwise.
+ */
+static struct ubifs_dqblk *ubifs_dqblk_find(struct ubifs_info *c,
+					    struct kqid qid)
+{
+	struct ubifs_dqblk *next;
+
+	next = ubifs_dqblk_find_next(c, qid);
+
+	if (next && qid_eq(qid, next->kqid))
+		return next;
+
+	return NULL;
+}
+
+/**
+ * ubifs_dqblk_get - get dqblk entry for given qid
+ * @c: UBIFS file-system description object
+ * @qid: The qid to look for
+ *
+ * This searches the given qid in the dqblk tree and returns it if found. If not,
+ * a new dqblk entry is created, inserted into the dqblk tree and returned.
+ */
+static struct ubifs_dqblk *ubifs_dqblk_get(struct ubifs_info *c,
+					   struct kqid qid)
+{
+	struct ubifs_dqblk *ud;
+
+	ud = ubifs_dqblk_find(c, qid);
+	if (ud)
+		return ud;
+
+	ud = kzalloc(sizeof(*ud), GFP_KERNEL);
+	if (!ud)
+		return NULL;
+
+	ud->kqid = qid;
+
+	ubifs_dqblk_insert(c, ud);
+
+	return ud;
+}
+
+/**
+ * ubifs_check_quota_file - check if quota file is valid for this format
+ * @sb: The superblock
+ * @type: Quota type
+ *
+ * We currently do not store any quota file on flash. It's completely in RAM and
+ * thus always valid.
+ */
+static int ubifs_check_quota_file(struct super_block *sb, int type)
+{
+	return 1;
+}
+
+/**
+ * ubifs_read_file_info - read quota file info
+ * @sb: The superblock
+ * @type: Quota type
+ *
+ * We currently do not store any quota info on flash. Just fill in default
+ * values.
+ */
+static int ubifs_read_file_info(struct super_block *sb, int type)
+{
+	struct quota_info *dqopt = sb_dqopt(sb);
+	struct mem_dqinfo *info = &dqopt->info[type];
+
+	down_read(&dqopt->dqio_sem);
+
+	/*
+	 * Used space is stored as unsigned 64-bit value in bytes but
+	 * quota core supports only signed 64-bit values so use that
+	 * as a limit
+	 */
+	info->dqi_max_spc_limit = 0x7fffffffffffffffLL; /* 2^63-1 */
+	info->dqi_max_ino_limit = 0x7fffffffffffffffLL;
+	info->dqi_bgrace = 0;
+	info->dqi_igrace = 0;
+	info->dqi_flags = 0;
+
+	up_read(&dqopt->dqio_sem);
+
+	return 0;
+}
+
+/**
+ * ubifs_write_file_info - write quota file to device
+ * @sb: The superblock
+ * @type: Quota type
+ *
+ * We currently do not store any quota file on flash. Nothing to do here.
+ */
+static int ubifs_write_file_info(struct super_block *sb, int type)
+{
+	return 0;
+}
+
+/**
+ * ubifs_read_dquot - read dquot from device
+ * @dquot: The dquot entry to read
+ *
+ * For us this means finding the entry in the dquot tree.
+ */
+static int ubifs_read_dquot(struct dquot *dquot)
+{
+	struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
+	struct ubifs_info *c = dquot->dq_sb->s_fs_info;
+	struct ubifs_dqblk *ud;
+	int ret = 0;
+
+	down_read(&dqopt->dqio_sem);
+
+	ud = ubifs_dqblk_find(c, dquot->dq_id);
+	if (ud)
+		dquot->dq_dqb = ud->dqblk;
+	else
+		ret = -ENOENT;
+
+	up_read(&dqopt->dqio_sem);
+
+	return 0;
+}
+
+/**
+ * ubifs_write_dquot - write dquot to device
+ * @dquot: The dquot entry to read
+ *
+ * For us this means finding or creating the entry in the dquot tree and setting
+ * it to the dquots contents.
+ */
+static int ubifs_write_dquot(struct dquot *dquot)
+{
+	struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
+	struct ubifs_info *c = dquot->dq_sb->s_fs_info;
+	int ret = 0;
+	struct ubifs_dqblk *ud;
+
+	down_write(&dqopt->dqio_sem);
+
+	ud = ubifs_dqblk_get(c, dquot->dq_id);
+	if (!ud) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ud->dqblk = dquot->dq_dqb;
+
+out:
+	up_write(&dqopt->dqio_sem);
+
+	return ret;
+}
+
+/**
+ * ubifs_release_dquot - release memory associated to a dquot entry
+ * @dquot: The dquot entry
+ *
+ * Nothing to do here, we didn't allocate anything. Our data is freed at unmount
+ * time.
+ */
+static int ubifs_release_dquot(struct dquot *dquot)
+{
+	return 0;
+}
+
+/**
+ * ubifs_free_file_info - free memory allocated during reading the file info
+ * @sb: The superblock
+ * @type: Quota type
+ *
+ * Nothing to do here.
+ */
+static int ubifs_free_file_info(struct super_block *sb, int type)
+{
+	return 0;
+}
+
+static int ubifs_get_next_id(struct super_block *sb, struct kqid *qid)
+{
+	struct quota_info *dqopt = sb_dqopt(sb);
+	struct ubifs_info *c = sb->s_fs_info;
+	struct ubifs_dqblk *ud;
+	int ret = 0;
+
+	down_read(&dqopt->dqio_sem);
+
+	ud = ubifs_dqblk_find_next(c, *qid);
+	if (!ud) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	*qid = ud->kqid;
+out:
+	up_read(&dqopt->dqio_sem);
+
+	return ret;
+}
+
+static const struct quota_format_ops ubifs_format_ops = {
+	.check_quota_file	= ubifs_check_quota_file,
+	.read_file_info		= ubifs_read_file_info,
+	.write_file_info	= ubifs_write_file_info,
+	.free_file_info		= ubifs_free_file_info,
+	.read_dqblk		= ubifs_read_dquot,
+	.commit_dqblk		= ubifs_write_dquot,
+	.release_dqblk		= ubifs_release_dquot,
+	.get_next_id		= ubifs_get_next_id,
+};
+
+/**
+ * add_inode_quota - read quota informations for an inode
+ * @c: UBIFS file-system description object
+ * @zbr: The zbranch to read the inode from
+ *
+ * This reads the inode as referred to by @zbr from the medium and
+ * extracts the quota relevant informations. Returns 0 for success
+ * or a negative error code otherwise.
+ */
+static int add_inode_quota(struct ubifs_info *c, struct ubifs_zbranch *zbr)
+{
+	struct ubifs_ino_node *ino;
+	int err, type = key_type(c, &zbr->key);
+	struct kqid qid[MAXQUOTAS];
+	kprojid_t kprojid;
+	kgid_t kgid;
+	kuid_t kuid;
+	int i;
+
+	if (type != UBIFS_INO_KEY) {
+		ubifs_err(c, "%s called on non INO node %d", __func__, type);
+		return 0;
+	}
+
+	if (zbr->len < UBIFS_CH_SZ) {
+		ubifs_err(c, "bad leaf length %d (LEB %d:%d)",
+				zbr->len, zbr->lnum, zbr->offs);
+		return -EINVAL;
+	}
+
+	ino = kmalloc(zbr->len, GFP_NOFS);
+	if (!ino)
+		return -ENOMEM;
+
+	err = ubifs_tnc_read_node(c, zbr, ino);
+	if (err) {
+		ubifs_err(c, "cannot read leaf node at LEB %d:%d, error %d",
+				zbr->lnum, zbr->offs, err);
+		goto out_free;
+	}
+
+	if (ino->flags & UBIFS_XATTR_FL)
+		goto out_free;
+
+	kuid = make_kuid(&init_user_ns, le32_to_cpu(ino->uid));
+	qid[USRQUOTA] = make_kqid_uid(kuid);
+
+	kgid = make_kgid(&init_user_ns, le32_to_cpu(ino->gid));
+	qid[GRPQUOTA] = make_kqid_gid(kgid);
+
+	kprojid = make_kprojid(&init_user_ns, le32_to_cpu(ino->projid));
+	qid[PRJQUOTA] = make_kqid_projid(kprojid);
+
+	for (i = 0; i < UBIFS_MAXQUOTAS; i++) {
+		struct ubifs_dqblk *ud;
+
+		if (!(c->quota_enable & (1 << type)))
+			continue;
+
+		ud = ubifs_dqblk_get(c, qid[i]);
+		if (!ud) {
+			err = -ENOMEM;
+			goto out_free;
+		}
+
+		if (S_ISREG(le32_to_cpu(ino->mode)))
+			ud->dqblk.dqb_curspace += le64_to_cpu(ino->size);
+		ud->dqblk.dqb_curinodes++;
+	}
+
+	err = 0;
+
+out_free:
+	kfree(ino);
+
+	return err;
+}
+
+/**
+ * get_next_parent_inum - get the next inum
+ * @c: UBIFS file-system description object
+ * @znode: The znode to start searching from
+ * @inum: The next inum returned here
+ *
+ * Helper function for ubifs_quota_walk_index(). If we know the next inum we
+ * find up the tree is still the same as we just handled we do not have to walk
+ * down the tree but can skip these branches. Returns 0 for success or -ENOENT
+ * when we can't go further to the upper-right but hit the root node instead.
+ */
+static int get_next_parent_inum(struct ubifs_info *c, struct ubifs_znode *znode,
+				ino_t *inum)
+{
+	struct ubifs_znode *zp;
+
+	while (1) {
+		int nn;
+
+		zp = znode->parent;
+		if (!zp)
+			return -ENOENT;
+
+		nn = znode->iip + 1;
+		znode = zp;
+
+		if (nn < znode->child_cnt) {
+			*inum = key_inum(c, &znode->zbranch[nn].key);
+			return 0;
+		}
+	}
+}
+
+/**
+ * ubifs_quota_walk_index - Collect quota info
+ * @c: UBIFS file-system description object
+ *
+ * This function walks the whole index and collects the quota usage information
+ * for all inodes. Returns 0 for success or a negative error code otherwise.
+ */
+static int ubifs_quota_walk_index(struct ubifs_info *c)
+{
+	int err;
+	struct ubifs_znode *znode;
+	int i, nn = 0;
+	ino_t inum = 0, pinum;
+
+	if (!c->zroot.znode) {
+		c->zroot.znode = ubifs_load_znode(c, &c->zroot, NULL, 0);
+		if (IS_ERR(c->zroot.znode)) {
+			err = PTR_ERR(c->zroot.znode);
+			c->zroot.znode = NULL;
+			return err;
+		}
+	}
+
+	znode = c->zroot.znode;
+
+	while (1) {
+		while (znode->level != 0) {
+			znode = ubifs_get_znode(c, znode, nn);
+			if (IS_ERR(znode))
+				return PTR_ERR(znode);
+			nn = 0;
+		}
+
+		for (i = 0; i < znode->child_cnt; i++) {
+			if (inum == key_inum(c, &znode->zbranch[i].key))
+				continue;
+
+			add_inode_quota(c, &znode->zbranch[i]);
+			inum = key_inum(c, &znode->zbranch[i].key);
+		}
+
+		while (1) {
+			struct ubifs_znode *zp;
+
+			zp = znode->parent;
+			if (!zp)
+				return 0;
+
+			nn = znode->iip + 1;
+			znode = zp;
+
+			err = get_next_parent_inum(c, znode, &pinum);
+
+			/*
+			 * Optimization: When the next parent node is still for
+			 * the inode we just handled we can skip parsing our
+			 * children.
+			 */
+			if (!err && pinum == inum)
+				continue;
+
+			/*
+			 * Optimization: We can skip scanning all child nodes
+			 * which are for the same inode as we already looked at.
+			 */
+			while (nn < znode->child_cnt - 1 &&
+			       inum == key_inum(c, &znode->zbranch[nn + 1].key))
+				nn++;
+
+			if (nn < znode->child_cnt)
+				break;
+		}
+	}
+}
+
+/**
+ * ubifs_enable_quotas - enable quota
+ * @c: UBIFS file-system description object
+ *
+ * Enable usage tracking for all quota types.
+ */
+int ubifs_enable_quotas(struct ubifs_info *c)
+{
+	struct super_block *sb = c->vfs_sb;
+	struct quota_info *dqopt = sb_dqopt(sb);
+	int type, ret;
+
+	if (!c->quota_enable)
+		return 0;
+
+	dqopt->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
+
+	for (type = 0; type < UBIFS_MAXQUOTAS; type++) {
+		if (!(c->quota_enable & (1 << type)))
+			continue;
+
+		ret = dquot_load_quota_sb(sb, type, QFMT_UBIFS,
+				   DQUOT_USAGE_ENABLED | DQUOT_LIMITS_ENABLED);
+		if (ret)
+			goto out_quota_off;
+
+		c->dqblk_tree[type] = RB_ROOT;
+	}
+
+	return ubifs_quota_walk_index(c);
+
+out_quota_off:
+	ubifs_disable_quotas(c);
+
+	return ret;
+}
+
+/**
+ * ubifs_disable_quotas - disable quota
+ * @c: UBIFS file-system description object
+ *
+ * Disable quota for UBFIS.
+ */
+void ubifs_disable_quotas(struct ubifs_info *c)
+{
+	struct rb_node *n;
+	struct ubifs_dqblk *ud;
+	int type;
+
+	if (!c->quota_enable)
+		return;
+
+	dquot_disable(c->vfs_sb, -1, DQUOT_USAGE_ENABLED | DQUOT_LIMITS_ENABLED);
+
+	for (type = 0; type < UBIFS_MAXQUOTAS; type++) {
+		while ((n = rb_first(&c->dqblk_tree[type]))) {
+			ud = rb_entry(n, struct ubifs_dqblk, rb);
+			rb_erase(n, &c->dqblk_tree[type]);
+			kfree(ud);
+		}
+	}
+}
+
+static int ubifs_get_projid(struct inode *inode, kprojid_t *projid)
+{
+	struct ubifs_inode *ui = ubifs_inode(inode);
+
+	*projid = ui->projid;
+
+	return 0;
+}
+
+static const struct dquot_operations ubifs_dquot_operations = {
+	.write_dquot	= dquot_commit,
+	.acquire_dquot	= dquot_acquire,
+	.release_dquot	= dquot_release,
+	.mark_dirty	= dquot_mark_dquot_dirty,
+	.write_info	= dquot_commit_info,
+	.alloc_dquot	= dquot_alloc,
+	.destroy_dquot	= dquot_destroy,
+	.get_next_id	= dquot_get_next_id,
+	.get_projid	= ubifs_get_projid,
+};
+
+static const struct quotactl_ops ubifs_quotactl_ops = {
+	.get_state	= dquot_get_state,
+	.set_info	= dquot_set_dqinfo,
+	.get_dqblk	= dquot_get_dqblk,
+	.set_dqblk	= dquot_set_dqblk,
+	.get_nextdqblk	= dquot_get_next_dqblk,
+};
+
+static struct quota_format_type ubifs_quota_format = {
+	.qf_fmt_id	= QFMT_UBIFS,
+	.qf_ops		= &ubifs_format_ops,
+	.qf_owner	= THIS_MODULE
+};
+
+int ubifs_register_quota_format(void)
+{
+	return register_quota_format(&ubifs_quota_format);
+}
+
+void ubifs_unregister_quota_format(void)
+{
+	return unregister_quota_format(&ubifs_quota_format);
+}
+
+/**
+ * ubifs_init_quota - init quota ops for UBIFS
+ * @c: UBIFS file-system description object
+ *
+ * This inits the quota operations for UBIFS
+ */
+void ubifs_init_quota(struct ubifs_info *c)
+{
+	struct super_block *sb = c->vfs_sb;
+
+	if (!c->quota_enable)
+		return;
+
+	sb->dq_op = &ubifs_dquot_operations;
+	sb->s_qcop = &ubifs_quotactl_ops;
+	sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP | QTYPE_MASK_PRJ;
+}
diff --git a/fs/ubifs/super.c b/fs/ubifs/super.c
index 11a43f17032e..b3be9b947cfa 100644
--- a/fs/ubifs/super.c
+++ b/fs/ubifs/super.c
@@ -169,6 +169,7 @@ struct inode *ubifs_iget(struct super_block *sb, unsigned long inum)
 
 	switch (inode->i_mode & S_IFMT) {
 	case S_IFREG:
+		inode_set_bytes(inode, ino->size);
 		inode->i_mapping->a_ops = &ubifs_file_address_operations;
 		inode->i_op = &ubifs_file_inode_operations;
 		inode->i_fop = &ubifs_file_operations;
@@ -372,6 +373,7 @@ static void ubifs_evict_inode(struct inode *inode)
 	if (is_bad_inode(inode))
 		goto out;
 
+	dquot_initialize(inode);
 	ui->ui_size = inode->i_size = 0;
 	err = ubifs_jnl_delete_inode(c, inode);
 	if (err)
@@ -381,7 +383,7 @@ static void ubifs_evict_inode(struct inode *inode)
 		 */
 		ubifs_err(c, "can't delete inode %lu, error %d",
 			  inode->i_ino, err);
-
+	dquot_free_inode(inode);
 out:
 	if (ui->dirty)
 		ubifs_release_dirty_inode_budget(c, ui);
@@ -391,6 +393,7 @@ static void ubifs_evict_inode(struct inode *inode)
 		smp_wmb();
 	}
 done:
+	dquot_drop(inode);
 	clear_inode(inode);
 	fscrypt_put_encryption_info(inode);
 }
@@ -453,6 +456,13 @@ static int ubifs_show_options(struct seq_file *s, struct dentry *root)
 	else if (c->mount_opts.chk_data_crc == 1)
 		seq_puts(s, ",no_chk_data_crc");
 
+	if (c->quota_enable & (1 << USRQUOTA))
+		seq_puts(s, ",usrquota");
+	if (c->quota_enable & (1 << GRPQUOTA))
+		seq_puts(s, ",grpquota");
+	if (c->quota_enable & (1 << PRJQUOTA))
+		seq_puts(s, ",prjquota");
+
 	if (c->mount_opts.override_compr) {
 		seq_printf(s, ",compr=%s",
 			   ubifs_compr_name(c, c->mount_opts.compr_type));
@@ -945,6 +955,29 @@ static int check_volume_empty(struct ubifs_info *c)
 	return 0;
 }
 
+#ifdef CONFIG_QUOTA
+static struct dquot **ubifs_get_dquots(struct inode *inode)
+{
+	return ubifs_inode(inode)->i_dquot;
+}
+
+static ssize_t ubifs_quota_read(struct super_block *sb, int type, char *data,
+				size_t len, loff_t off)
+{
+	BUG();
+
+	return 0;
+}
+
+static ssize_t ubifs_quota_write(struct super_block *sb, int type, const char *data,
+				 size_t len, loff_t off)
+{
+	BUG();
+
+	return 0;
+}
+#endif
+
 /*
  * UBIFS mount options.
  *
@@ -968,6 +1001,9 @@ enum {
 	Opt_chk_data_crc,
 	Opt_no_chk_data_crc,
 	Opt_override_compr,
+	Opt_usrquota,
+	Opt_grpquota,
+	Opt_prjquota,
 	Opt_assert,
 	Opt_auth_key,
 	Opt_auth_hash_name,
@@ -983,6 +1019,9 @@ static const match_table_t tokens = {
 	{Opt_chk_data_crc, "chk_data_crc"},
 	{Opt_no_chk_data_crc, "no_chk_data_crc"},
 	{Opt_override_compr, "compr=%s"},
+	{Opt_usrquota, "usrquota"},
+	{Opt_grpquota, "grpquota"},
+	{Opt_prjquota, "prjquota"},
 	{Opt_auth_key, "auth_key=%s"},
 	{Opt_auth_hash_name, "auth_hash_name=%s"},
 	{Opt_ignore, "ubi=%s"},
@@ -1110,6 +1149,23 @@ static int ubifs_parse_options(struct ubifs_info *c, char *options,
 			kfree(act);
 			break;
 		}
+#ifdef CONFIG_QUOTA
+		case Opt_usrquota:
+			c->quota_enable |= 1 << USRQUOTA;
+			break;
+		case Opt_grpquota:
+			c->quota_enable |= 1 << GRPQUOTA;
+			break;
+		case Opt_prjquota:
+			c->quota_enable |= 1 << PRJQUOTA;
+			break;
+#else
+		case Opt_usrquota:
+		case Opt_grpquota:
+		case Opt_prjquota:
+			ubifs_err(c, "quota operations not supported");
+			break;
+#endif
 		case Opt_auth_key:
 			if (!is_remount) {
 				c->auth_key_name = kstrdup(args[0].from,
@@ -1839,6 +1895,7 @@ static int ubifs_remount_rw(struct ubifs_info *c)
 		 */
 		err = dbg_check_space_info(c);
 	}
+	c->vfs_sb->s_flags &= ~SB_RDONLY;
 
 	mutex_unlock(&c->umount_mutex);
 	return err;
@@ -1908,6 +1965,7 @@ static void ubifs_remount_ro(struct ubifs_info *c)
 	err = dbg_check_space_info(c);
 	if (err)
 		ubifs_ro_mode(c, err);
+	c->vfs_sb->s_flags |= SB_RDONLY;
 	mutex_unlock(&c->umount_mutex);
 }
 
@@ -1918,6 +1976,8 @@ static void ubifs_put_super(struct super_block *sb)
 
 	ubifs_msg(c, "un-mount UBI device %d", c->vi.ubi_num);
 
+	ubifs_disable_quotas(c);
+
 	/*
 	 * The following asserts are only valid if there has not been a failure
 	 * of the media. For example, there will be dirty inodes if we failed
@@ -2015,11 +2075,17 @@ static int ubifs_remount_fs(struct super_block *sb, int *flags, char *data)
 		err = ubifs_remount_rw(c);
 		if (err)
 			return err;
+		err = dquot_resume(sb, -1);
+		if (err)
+			return err;
 	} else if (!c->ro_mount && (*flags & SB_RDONLY)) {
 		if (c->ro_error) {
 			ubifs_msg(c, "cannot re-mount R/O due to prior errors");
 			return -EROFS;
 		}
+		err = dquot_suspend(sb, -1);
+		if (err)
+			return err;
 		ubifs_remount_ro(c);
 	}
 
@@ -2051,6 +2117,11 @@ const struct super_operations ubifs_super_operations = {
 	.remount_fs    = ubifs_remount_fs,
 	.show_options  = ubifs_show_options,
 	.sync_fs       = ubifs_sync_fs,
+#ifdef CONFIG_QUOTA
+	.get_dquots    = ubifs_get_dquots,
+	.quota_read    = ubifs_quota_read,
+	.quota_write   = ubifs_quota_write,
+#endif
 };
 
 /**
@@ -2211,6 +2282,8 @@ static int ubifs_fill_super(struct super_block *sb, void *data, int silent)
 	sb->s_xattr = ubifs_xattr_handlers;
 	fscrypt_set_ops(sb, &ubifs_crypt_operations);
 
+	ubifs_init_quota(c);
+
 	mutex_lock(&c->umount_mutex);
 	err = mount_ubifs(c);
 	if (err) {
@@ -2225,6 +2298,10 @@ static int ubifs_fill_super(struct super_block *sb, void *data, int silent)
 		goto out_umount;
 	}
 
+	err = ubifs_enable_quotas(c);
+	if (err)
+		goto out_umount;
+
 	sb->s_root = d_make_root(root);
 	if (!sb->s_root) {
 		err = -ENOMEM;
@@ -2432,6 +2509,10 @@ static int __init ubifs_init(void)
 
 	dbg_debugfs_init();
 
+	err = ubifs_register_quota_format();
+	if (err)
+		goto out_compressors;
+
 	err = register_filesystem(&ubifs_fs_type);
 	if (err) {
 		pr_err("UBIFS error (pid %d): cannot register file system, error %d",
@@ -2442,6 +2523,8 @@ static int __init ubifs_init(void)
 
 out_dbg:
 	dbg_debugfs_exit();
+	ubifs_unregister_quota_format();
+out_compressors:
 	ubifs_compressors_exit();
 out_shrinker:
 	unregister_shrinker(&ubifs_shrinker_info);
@@ -2467,6 +2550,7 @@ static void __exit ubifs_exit(void)
 	 */
 	rcu_barrier();
 	kmem_cache_destroy(ubifs_inode_slab);
+	ubifs_unregister_quota_format();
 	unregister_filesystem(&ubifs_fs_type);
 }
 module_exit(ubifs_exit);
diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h
index a29e5c3dd1fb..aefb30fa1d27 100644
--- a/fs/ubifs/ubifs.h
+++ b/fs/ubifs/ubifs.h
@@ -27,6 +27,7 @@
 #include <linux/security.h>
 #include <linux/xattr.h>
 #include <linux/random.h>
+#include <linux/quotaops.h>
 #include <crypto/hash_info.h>
 #include <crypto/hash.h>
 #include <crypto/algapi.h>
@@ -157,6 +158,8 @@
 
 #define UBIFS_DEF_PROJID 0
 
+#define UBIFS_MAXQUOTAS 3
+
 /*
  * Lockdep classes for UBIFS inode @ui_mutex.
  */
@@ -416,6 +419,9 @@ struct ubifs_inode {
 	loff_t synced_i_size;
 	loff_t ui_size;
 	int flags;
+#ifdef CONFIG_QUOTA
+	struct dquot *i_dquot[UBIFS_MAXQUOTAS];
+#endif
 	kprojid_t projid;
 	pgoff_t last_page_read;
 	pgoff_t read_in_a_row;
@@ -1045,6 +1051,7 @@ struct ubifs_debug_info;
  * @rw_incompat: the media is not R/W compatible
  * @assert_action: action to take when a ubifs_assert() fails
  * @authenticated: flag indigating the FS is mounted in authenticated mode
+ * @quota_enable: If true, quota is enabled on this filesystem
  *
  * @tnc_mutex: protects the Tree Node Cache (TNC), @zroot, @cnext, @enext, and
  *             @calc_idx_sz
@@ -1386,6 +1393,9 @@ struct ubifs_info {
 	struct ubi_device_info di;
 	struct ubi_volume_info vi;
 
+	unsigned int quota_enable;
+	struct rb_root dqblk_tree[UBIFS_MAXQUOTAS];
+
 	struct rb_root orph_tree;
 	struct list_head orph_list;
 	struct list_head orph_new;
@@ -2105,6 +2115,33 @@ int ubifs_decrypt(const struct inode *inode, struct ubifs_data_node *dn,
 
 extern const struct fscrypt_operations ubifs_crypt_operations;
 
+/* quota.c */
+#ifdef CONFIG_QUOTA
+int ubifs_register_quota_format(void);
+void ubifs_unregister_quota_format(void);
+void ubifs_init_quota(struct ubifs_info *c);
+int ubifs_enable_quotas(struct ubifs_info *c);
+void ubifs_disable_quotas(struct ubifs_info *c);
+#else
+static inline int ubifs_register_quota_format(void)
+{
+	return 0;
+}
+static inline void ubifs_unregister_quota_format(void)
+{
+}
+static inline void ubifs_init_quota(struct ubifs_info *c)
+{
+}
+static inline int ubifs_enable_quotas(struct ubifs_info *c)
+{
+	return 0;
+}
+static inline void ubifs_disable_quotas(struct ubifs_info *c)
+{
+}
+#endif
+
 /* Normal UBIFS messages */
 __printf(2, 3)
 void ubifs_msg(const struct ubifs_info *c, const char *fmt, ...);
diff --git a/fs/ubifs/xattr.c b/fs/ubifs/xattr.c
index a0b9b349efe6..54cfe28c25b0 100644
--- a/fs/ubifs/xattr.c
+++ b/fs/ubifs/xattr.c
@@ -64,6 +64,9 @@ enum {
 static const struct inode_operations empty_iops;
 static const struct file_operations empty_fops;
 
+struct inode *ubifs_new_xattr_inode(struct ubifs_info *c, struct inode *dir,
+			      umode_t mode);
+
 /**
  * create_xattr - create an extended attribute.
  * @c: UBIFS file-system description object
@@ -110,7 +113,7 @@ static int create_xattr(struct ubifs_info *c, struct inode *host,
 	if (err)
 		return err;
 
-	inode = ubifs_new_inode(c, host, S_IFREG | S_IRWXUGO);
+	inode = ubifs_new_xattr_inode(c, host, S_IFREG | S_IRWXUGO);
 	if (IS_ERR(inode)) {
 		err = PTR_ERR(inode);
 		goto out_budg;
diff --git a/include/uapi/linux/quota.h b/include/uapi/linux/quota.h
index e1787c0df601..7082868ca225 100644
--- a/include/uapi/linux/quota.h
+++ b/include/uapi/linux/quota.h
@@ -78,6 +78,7 @@
 #define	QFMT_VFS_V0 2
 #define QFMT_OCFS2 3
 #define	QFMT_VFS_V1 4
+#define QFMT_UBIFS 5
 
 /* Size of block in which space limits are passed through the quota
  * interface */
-- 
2.20.1


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

end of thread, other threads:[~2021-01-22 15:17 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-01-24 13:13 [PATCH v4 0/8] Add quota support to UBIFS Sascha Hauer
2020-01-24 13:13 ` [PATCH 1/8] quota: Allow to pass mount path to quotactl Sascha Hauer
2020-01-27 10:45   ` Jan Kara
2020-01-28 10:06     ` Sascha Hauer
2020-01-28 11:41       ` Jan Kara
2020-01-29  1:29       ` Al Viro
2020-01-29 16:14         ` Jan Kara
2020-02-04 10:35         ` Sascha Hauer
2020-02-18  9:22           ` Jan Kara
2020-01-24 13:13 ` [PATCH 2/8] ubifs: move checks and preparation into setflags() Sascha Hauer
2020-01-24 13:13 ` [PATCH 3/8] ubifs: Add support for FS_IOC_FS[SG]ETXATTR ioctls Sascha Hauer
2020-01-24 13:13 ` [PATCH 4/8] ubifs: do not ubifs_inode() on potentially NULL pointer Sascha Hauer
2020-01-24 13:13 ` [PATCH 5/8] ubifs: Factor out ubifs_set_feature_flag() Sascha Hauer
2020-01-24 13:13 ` [PATCH 6/8] ubifs: Add support for project id Sascha Hauer
2020-01-24 13:13 ` [PATCH 7/8] ubifs: export get_znode Sascha Hauer
2020-01-24 13:13 ` [PATCH 8/8] ubifs: Add quota support Sascha Hauer
2020-01-26 18:30   ` kbuild test robot
2020-01-26 18:30   ` [RFC PATCH] ubifs: tnc_next() can be static kbuild test robot
2021-01-22 15:15 [PATCH v5 0/8] Add quota support to UBIFS Sascha Hauer
2021-01-22 15:15 ` [PATCH 8/8] ubifs: Add quota support Sascha Hauer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).