[PATCH RFC 1/6] fs: new interface and behavior for file project id

Subsystems: capabilities, diskquota, documentation, file locking (flock() and fcntl()/lockf()), filesystems (vfs and infrastructure), proc sysctl, the rest

STALE4244d

6 messages, 1 author, 2015-02-11 · open the first message on its own page

[PATCH RFC 1/6] fs: new interface and behavior for file project id

From: Konstantin Khlebnikov <hidden>
Date: 2015-02-11 15:11:46

For now project id and quotas are implemented only in XFS.
Existing behavior isn't very useful: any unprivileged user can set any
project id for its own files and this way he can bypass project limits.

XFS interface for getting or changing file project is a very XFS-centric:
ioctl XFS_IOC_FSGET/SETXATTR with structure (struct fsxattr) as a argument
which has three unrelated fields and twelve reserved padding bytes.
Idea of keeping XFS-compatible interface seems overpriced. Old tools checks
filesystem name/magic thus without update they anyway will work only for XFS.

This patch defines common interface and new behavior.
Depending on sysctl fs.protected_projects = 0|1 projects works as:

0 = XFS-compatible projects
  - changing project id could be performed only from init user-ns
  - file owner or task with CAP_FOWNER can set any project id
  - changing user-ns project-id mapping allowed for everybody
  - cross-project hardlinks and renaming are forbidden (-EXDEV)
  - new inodes inherits project id from directory if flag
    XFS_DIFLAG_PROJINHERIT is set for directory inode

1 = Protected projects
  - changing project id requires CAP_SYS_RESOURCE in current user-ns
  - changing project id mapping require CAP_SYS_RESOURCE in parent user-ns
  - cross-project hardlinks and renaming are permitted if current task has
    CAP_SYS_RESOURCE in current user-namespace or if directory project is
    mapped to zero in current user-namespace.
  - new inodes always inherits project id from directory

Now project id is more sticky and cross-project sharing is more flexible.
User-namespace project mapping defines set of project ids which could be
used inside, if it's empty then container cannot change project id at all.

CONFIG_PROTECTED_PROJECTS_BY_DEFAULT defines default value for sysctl.

This patch adds two new fcntls:
int fcntl(fd, F_GET_PROJECT, projid_t *);
int fcntl(fd, F_SET_PROJECT, projid_t);

Permissions:
F_GET_PROJECT is permitted for everybody but if file project isn't mapped
into current user-namespace -EACCESS will be returned.

F_SET_PROJECT: depending on state of sysctl fs.protected_projects allowed
either for file owner and CAP_FOWNER or requires capability CAP_SYS_RESOURCE.

Error codes:
EINVAL    - not implemented in this kernel
EPERM     - not permitted/supported by this filesystem type
ENOTSUPP  - not supported for this filesystem instance (no feature at sb)
EACCES    - not enough permissions or project id isn't mapped

Project id is stored in fs-specific inode and exposed via couple super-block
operations: get_projid / set_projid. This have to be sb-operations because
dquot_initialize() could be called before setting inode->i_op.

Signed-off-by: Konstantin Khlebnikov <redacted>
---
 Documentation/filesystems/Locking |    4 ++
 Documentation/filesystems/vfs.txt |   10 ++++++
 fs/fcntl.c                        |   65 +++++++++++++++++++++++++++++++++++++
 fs/quota/Kconfig                  |    9 +++++
 include/linux/fs.h                |    4 ++
 include/linux/projid.h            |    4 ++
 include/uapi/linux/fcntl.h        |    6 +++
 kernel/capability.c               |   62 +++++++++++++++++++++++++++++++++++
 kernel/sysctl.c                   |    9 +++++
 kernel/user_namespace.c           |    4 +-
 10 files changed, 175 insertions(+), 2 deletions(-)
diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking
index b30753c..649e404 100644
--- a/Documentation/filesystems/Locking
+++ b/Documentation/filesystems/Locking
@@ -125,6 +125,8 @@ prototypes:
 	int (*show_options)(struct seq_file *, struct dentry *);
 	ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
 	ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
+	int (*get_projid) (struct inode *, kprojid_t *);
+	int (*set_projid) (struct inode *, kprojid_t);
 	int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
 
 locking rules:
@@ -147,6 +149,8 @@ show_options:		no		(namespace_sem)
 quota_read:		no		(see below)
 quota_write:		no		(see below)
 bdev_try_to_free_page:	no		(see below)
+get_projid		no		(maybe i_mutex)
+set_projid		no		(i_mutex)
 
 ->statfs() has s_umount (shared) when called by ustat(2) (native or
 compat), but that's an accident of bad API; s_umount is used to pin
diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt
index 43ce050..c25b3ee 100644
--- a/Documentation/filesystems/vfs.txt
+++ b/Documentation/filesystems/vfs.txt
@@ -228,6 +228,10 @@ struct super_operations {
 
         ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
         ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
+
+	int (*get_projid) (struct inode *, kprojid_t *);
+	int (*set_projid) (struct inode *, kprojid_t);
+
 	int (*nr_cached_objects)(struct super_block *);
 	void (*free_cached_objects)(struct super_block *, int);
 };
@@ -319,6 +323,12 @@ or bottom half).
 	implementations will cause holdoff problems due to large scan batch
 	sizes.
 
+  get_projid: called by the VFS and quota to get project id of a inode.
+	This method is called by fcntl() and project quota management.
+
+  set_projid: called by the VFS to set project if of a inode.
+	This method is called by fcntl() with i_mutex locked.
+
 Whoever sets up the inode is responsible for filling in the "i_op" field. This
 is a pointer to a "struct inode_operations" which describes the methods that
 can be performed on individual inodes.
diff --git a/fs/fcntl.c b/fs/fcntl.c
index ee85cd4..c89df0e 100644
--- a/fs/fcntl.c
+++ b/fs/fcntl.c
@@ -9,6 +9,7 @@
 #include <linux/mm.h>
 #include <linux/fs.h>
 #include <linux/file.h>
+#include <linux/mount.h>
 #include <linux/fdtable.h>
 #include <linux/capability.h>
 #include <linux/dnotify.h>
@@ -240,6 +241,62 @@ static int f_getowner_uids(struct file *filp, unsigned long arg)
 }
 #endif
 
+static int fcntl_get_project(struct file *file, projid_t __user *arg)
+{
+	struct inode *inode = file_inode(file);
+	struct super_block *sb = inode->i_sb;
+	kprojid_t kprojid;
+	projid_t projid;
+	int err;
+
+	if (!sb->s_op->get_projid)
+		return -EPERM;
+
+	err = sb->s_op->get_projid(inode, &kprojid);
+	if (err)
+		return err;
+
+	projid = from_kprojid(current_user_ns(), kprojid);
+	if (projid == (projid_t)-1)
+		return -EACCES;
+
+	return put_user(projid, arg);
+}
+
+static int fcntl_set_project(struct file *file, projid_t projid)
+{
+	struct user_namespace *ns = current_user_ns();
+	struct inode *inode = file_inode(file);
+	struct super_block *sb = inode->i_sb;
+	kprojid_t old_kprojid, kprojid;
+	int err;
+
+	if (!sb->s_op->get_projid || !sb->s_op->set_projid)
+		return -EPERM;
+
+	kprojid = make_kprojid(ns, projid);
+	if (!projid_valid(kprojid))
+		return -EACCES;
+
+	err = mnt_want_write_file(file);
+	if (err)
+		return err;
+
+	mutex_lock(&inode->i_mutex);
+	err = sb->s_op->get_projid(inode, &old_kprojid);
+	if (!err) {
+		if (capable_set_inode_project(inode, old_kprojid, kprojid))
+			err = sb->s_op->set_projid(inode, kprojid);
+		else
+			err = -EACCES;
+	}
+	mutex_unlock(&inode->i_mutex);
+
+	mnt_drop_write_file(file);
+
+	return err;
+}
+
 static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
 		struct file *filp)
 {
@@ -334,6 +391,12 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
 	case F_GET_SEALS:
 		err = shmem_fcntl(filp, cmd, arg);
 		break;
+	case F_GET_PROJECT:
+		err = fcntl_get_project(filp, (projid_t __user *) arg);
+		break;
+	case F_SET_PROJECT:
+		err = fcntl_set_project(filp, (projid_t) arg);
+		break;
 	default:
 		break;
 	}
@@ -348,6 +411,8 @@ static int check_fcntl_cmd(unsigned cmd)
 	case F_GETFD:
 	case F_SETFD:
 	case F_GETFL:
+	case F_GET_PROJECT:
+	case F_SET_PROJECT:
 		return 1;
 	}
 	return 0;
diff --git a/fs/quota/Kconfig b/fs/quota/Kconfig
index 4a09975..b38f881 100644
--- a/fs/quota/Kconfig
+++ b/fs/quota/Kconfig
@@ -74,3 +74,12 @@ config QUOTACTL_COMPAT
 	bool
 	depends on QUOTACTL && COMPAT_FOR_U64_ALIGNMENT
 	default y
+
+config PROTECTED_PROJECTS_ENABLED_BY_DEFAULT
+	bool "Protected projects by default"
+	default n
+	help
+	  This option defines default value for sysctl fs.protected_projects.
+	  Say N if you need XFS-compatible mode when file owner could set any
+	  project id. If you need reliable project disk quotas say Y here:
+	  in this mode changing project requires capability CAP_SYS_RESOURCE.
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f125b88..f6faf22 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -27,6 +27,7 @@
 #include <linux/shrinker.h>
 #include <linux/migrate_mode.h>
 #include <linux/uidgid.h>
+#include <linux/projid.h>
 #include <linux/lockdep.h>
 #include <linux/percpu-rwsem.h>
 #include <linux/blk_types.h>
@@ -62,6 +63,7 @@ extern struct inodes_stat_t inodes_stat;
 extern int leases_enable, lease_break_time;
 extern int sysctl_protected_symlinks;
 extern int sysctl_protected_hardlinks;
+extern int sysctl_protected_projects;
 
 struct buffer_head;
 typedef int (get_block_t)(struct inode *inode, sector_t iblock,
@@ -1636,6 +1638,8 @@ struct super_operations {
 	int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
 	long (*nr_cached_objects)(struct super_block *, int);
 	long (*free_cached_objects)(struct super_block *, long, int);
+	int (*get_projid)(struct inode *, kprojid_t *);
+	int (*set_projid)(struct inode *, kprojid_t);
 };
 
 /*
diff --git a/include/linux/projid.h b/include/linux/projid.h
index 8c1f2c5..410b509 100644
--- a/include/linux/projid.h
+++ b/include/linux/projid.h
@@ -86,4 +86,8 @@ static inline bool kprojid_has_mapping(struct user_namespace *ns, kprojid_t proj
 
 #endif /* CONFIG_USER_NS */
 
+bool capable_set_inode_project(const struct inode *inode,
+		kprojid_t old_kprojid, kprojid_t kprojid);
+bool capable_mix_inode_project(kprojid_t dir_kprojid, kprojid_t kprojid);
+
 #endif /* _LINUX_PROJID_H */
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index beed138..92791d0 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -34,6 +34,12 @@
 #define F_GET_SEALS	(F_LINUX_SPECIFIC_BASE + 10)
 
 /*
+ * Get/Set project id
+ */
+#define F_GET_PROJECT	(F_LINUX_SPECIFIC_BASE + 11)
+#define F_SET_PROJECT	(F_LINUX_SPECIFIC_BASE + 12)
+
+/*
  * Types of seals
  */
 #define F_SEAL_SEAL	0x0001	/* prevent further seals from being set */
diff --git a/kernel/capability.c b/kernel/capability.c
index 989f5bf..cd67ef4 100644
--- a/kernel/capability.c
+++ b/kernel/capability.c
@@ -444,3 +444,65 @@ bool capable_wrt_inode_uidgid(const struct inode *inode, int cap)
 		kgid_has_mapping(ns, inode->i_gid);
 }
 EXPORT_SYMBOL(capable_wrt_inode_uidgid);
+
+int sysctl_protected_projects =
+		IS_ENABLED(CONFIG_PROTECTED_PROJECTS_ENABLED_BY_DEFAULT);
+
+/**
+ * capable_set_inode_project - Check restrictions for changing project id
+ * @inode:		The inode in question
+ * @old_kprojid:	current project id
+ * @kprojid:		target project id
+ *
+ * Returns true if current task can set new project id for inode:
+ * In XFS-compatible mode (sysctl fs.protected_projects = 0) this is permitted
+ * only in init user namespace if current user owns file or task has CAP_FOWNER.
+ * If sysctl fs.protected_projects = 1 then tasks must have CAP_SYS_RESOURCE in
+ * current user-namespace and both projects must be mapped into this namespace.
+ */
+bool capable_set_inode_project(const struct inode *inode,
+			kprojid_t old_kprojid, kprojid_t kprojid)
+{
+	struct user_namespace *ns = current_user_ns();
+
+	/* In XFS-compat mode file owner can set any project id */
+	if (!sysctl_protected_projects)
+		return ns == &init_user_ns && inode_owner_or_capable(inode);
+
+	return ns_capable(ns, CAP_SYS_RESOURCE) &&
+		kprojid_has_mapping(ns, old_kprojid) &&
+		kprojid_has_mapping(ns, kprojid);
+}
+EXPORT_SYMBOL(capable_set_inode_project);
+
+/**
+ * capable_mix_inode_project - Check project id restrictions for link/rename
+ * @kprojid:     inode project id
+ * @dir_kprojid: directory project id
+ *
+ * Returns true if current task can link/rename inode into given directory:
+ * In XFS-compatible mode operation is permitted only if projects are match.
+ * If fs.protected_projects is set then it's permitted also if directory
+ * project is mapped to zero or if task has capability CAP_SYS_RESOURCE.
+ */
+bool capable_mix_inode_project(kprojid_t dir_kprojid, kprojid_t kprojid)
+{
+	struct user_namespace *ns;
+	projid_t dir_projid;
+
+	if (projid_eq(dir_kprojid, kprojid))
+		return true;
+
+	if (!sysctl_protected_projects)
+		return false;
+
+	ns = current_user_ns();
+	if (!kprojid_has_mapping(ns, kprojid))
+		return false;
+
+	dir_projid = from_kprojid(ns, dir_kprojid);
+	return dir_projid == (projid_t)0 ||
+		(dir_projid != (projid_t)-1 &&
+		 ns_capable(ns, CAP_SYS_RESOURCE));
+}
+EXPORT_SYMBOL(capable_mix_inode_project);
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 88ea2d6..cb6f9fb 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1649,6 +1649,15 @@ static struct ctl_table fs_table[] = {
 		.extra2		= &one,
 	},
 	{
+		.procname	= "protected_projects",
+		.data		= &sysctl_protected_projects,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= &zero,
+		.extra2		= &one,
+	},
+	{
 		.procname	= "suid_dumpable",
 		.data		= &suid_dumpable,
 		.maxlen		= sizeof(int),
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 4109f83..88f6619 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -807,8 +807,8 @@ ssize_t proc_projid_map_write(struct file *file, const char __user *buf,
 	if ((seq_ns != ns) && (seq_ns != ns->parent))
 		return -EPERM;
 
-	/* Anyone can set any valid project id no capability needed */
-	return map_write(file, buf, size, ppos, -1,
+	return map_write(file, buf, size, ppos,
+			 sysctl_protected_projects ? CAP_SYS_RESOURCE : -1,
 			 &ns->projid_map, &ns->parent->projid_map);
 }
 

[PATCH RFC 2/6] quota: adds generic code for enforcing project quota limits

From: Konstantin Khlebnikov <hidden>
Date: 2015-02-11 15:11:48

This patch adds support for a new quota type PRJQUOTA for project quota.
[ Based on patch by Li Xi [off-list ref] ]

Permissions:
Q_GETQUOTA: allows to query all projects present in current user-namespace
Q_SETQUOTA: requires system-wide capability CAP_SYS_ADMIN

Signed-off-by: Konstantin Khlebnikov <redacted>
Cc: Li Xi <redacted>
---
 fs/quota/dquot.c           |   37 +++++++++++++++++++++++++++++++++++--
 fs/quota/quota.c           |    8 ++++++--
 fs/quota/quotaio_v2.h      |    6 ++++--
 include/linux/quota.h      |    1 +
 include/linux/quotaops.h   |    1 +
 include/uapi/linux/quota.h |    6 ++++--
 6 files changed, 51 insertions(+), 8 deletions(-)
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index 0ccd4ba..afa5f67 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -1159,8 +1159,8 @@ static int need_print_warning(struct dquot_warn *warn)
 			return uid_eq(current_fsuid(), warn->w_dq_id.uid);
 		case GRPQUOTA:
 			return in_group_p(warn->w_dq_id.gid);
-		case PRJQUOTA:	/* Never taken... Just make gcc happy */
-			return 0;
+		case PRJQUOTA:
+			return 1;
 	}
 	return 0;
 }
@@ -1399,6 +1399,8 @@ static void __dquot_initialize(struct inode *inode, int type)
 	/* First get references to structures we might need. */
 	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 		struct kqid qid;
+		kprojid_t projid;
+
 		got[cnt] = NULL;
 		if (type != -1 && cnt != type)
 			continue;
@@ -1409,6 +1411,10 @@ static void __dquot_initialize(struct inode *inode, int type)
 		 */
 		if (i_dquot(inode)[cnt])
 			continue;
+
+		if (!sb_has_quota_active(sb, cnt))
+			continue;
+
 		init_needed = 1;
 
 		switch (cnt) {
@@ -1418,6 +1424,13 @@ static void __dquot_initialize(struct inode *inode, int type)
 		case GRPQUOTA:
 			qid = make_kqid_gid(inode->i_gid);
 			break;
+		case PRJQUOTA:
+			if (!sb->s_op->get_projid)
+				continue;
+			if (sb->s_op->get_projid(inode, &projid))
+				continue;
+			qid = make_kqid_projid(projid);
+			break;
 		}
 		got[cnt] = dqget(sb, qid);
 	}
@@ -1951,6 +1964,22 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr)
 EXPORT_SYMBOL(dquot_transfer);
 
 /*
+ * Helper function for transferring inode into another project.
+ */
+int dquot_transfer_project(struct inode *inode, kprojid_t projid)
+{
+	struct dquot *transfer_to[MAXQUOTAS] = {};
+	struct super_block *sb = inode->i_sb;
+	int ret;
+
+	transfer_to[PRJQUOTA] = dqget(sb, make_kqid_projid(projid));
+	ret = __dquot_transfer(inode, transfer_to);
+	dqput_all(transfer_to);
+	return ret;
+}
+EXPORT_SYMBOL(dquot_transfer_project);
+
+/*
  * Write info of quota file to disk
  */
 int dquot_commit_info(struct super_block *sb, int type)
@@ -2165,6 +2194,10 @@ static int vfs_load_quota_inode(struct inode *inode, int type, int format_id,
 		error = -EINVAL;
 		goto out_fmt;
 	}
+	if (type == PRJQUOTA && !sb->s_op->get_projid) {
+		error = -EINVAL;
+		goto out_fmt;
+	}
 	/* Usage always has to be set... */
 	if (!(flags & DQUOT_USAGE_ENABLED)) {
 		error = -EINVAL;
diff --git a/fs/quota/quota.c b/fs/quota/quota.c
index d14a799..0acd1bb 100644
--- a/fs/quota/quota.c
+++ b/fs/quota/quota.c
@@ -30,11 +30,15 @@ static int check_quotactl_permission(struct super_block *sb, int type, int cmd,
 	case Q_XGETQSTATV:
 	case Q_XQUOTASYNC:
 		break;
-	/* allow to query information for dquots we "own" */
+	/*
+	 * Allow to query information for user/group dquots we "own".
+	 * Allow querying project quota present in our user-namespace.
+	 */
 	case Q_GETQUOTA:
 	case Q_XGETQUOTA:
 		if ((type == USRQUOTA && uid_eq(current_euid(), make_kuid(current_user_ns(), id))) ||
-		    (type == GRPQUOTA && in_egroup_p(make_kgid(current_user_ns(), id))))
+		    (type == GRPQUOTA && in_egroup_p(make_kgid(current_user_ns(), id))) ||
+		    (type == PRJQUOTA && projid_valid(make_kprojid(current_user_ns(), id))))
 			break;
 		/*FALLTHROUGH*/
 	default:
diff --git a/fs/quota/quotaio_v2.h b/fs/quota/quotaio_v2.h
index f1966b4..4e95430 100644
--- a/fs/quota/quotaio_v2.h
+++ b/fs/quota/quotaio_v2.h
@@ -13,12 +13,14 @@
  */
 #define V2_INITQMAGICS {\
 	0xd9c01f11,	/* USRQUOTA */\
-	0xd9c01927	/* GRPQUOTA */\
+	0xd9c01927,	/* GRPQUOTA */\
+	0xd9c03f14,	/* PRJQUOTA */\
 }
 
 #define V2_INITQVERSIONS {\
 	1,		/* USRQUOTA */\
-	1		/* GRPQUOTA */\
+	1,		/* GRPQUOTA */\
+	1,		/* PRJQUOTA */\
 }
 
 /* First generic header */
diff --git a/include/linux/quota.h b/include/linux/quota.h
index d534e8e..8bad159 100644
--- a/include/linux/quota.h
+++ b/include/linux/quota.h
@@ -50,6 +50,7 @@
 
 #undef USRQUOTA
 #undef GRPQUOTA
+#undef PRJQUOTA
 enum quota_type {
 	USRQUOTA = 0,		/* element used for user quotas */
 	GRPQUOTA = 1,		/* element used for group quotas */
diff --git a/include/linux/quotaops.h b/include/linux/quotaops.h
index df73258..ba54745 100644
--- a/include/linux/quotaops.h
+++ b/include/linux/quotaops.h
@@ -104,6 +104,7 @@ int dquot_set_dqblk(struct super_block *sb, struct kqid id,
 
 int __dquot_transfer(struct inode *inode, struct dquot **transfer_to);
 int dquot_transfer(struct inode *inode, struct iattr *iattr);
+int dquot_transfer_project(struct inode *inode, kprojid_t projid);
 
 static inline struct mem_dqinfo *sb_dqinfo(struct super_block *sb, int type)
 {
diff --git a/include/uapi/linux/quota.h b/include/uapi/linux/quota.h
index 1f49b83..9c95b2c 100644
--- a/include/uapi/linux/quota.h
+++ b/include/uapi/linux/quota.h
@@ -36,11 +36,12 @@
 #include <linux/errno.h>
 #include <linux/types.h>
 
-#define __DQUOT_VERSION__	"dquot_6.5.2"
+#define __DQUOT_VERSION__	"dquot_6.6.0"
 
-#define MAXQUOTAS 2
+#define MAXQUOTAS 3
 #define USRQUOTA  0		/* element used for user quotas */
 #define GRPQUOTA  1		/* element used for group quotas */
+#define PRJQUOTA  2		/* element used for project quotas */
 
 /*
  * Definitions for the default names of the quotas files.
@@ -48,6 +49,7 @@
 #define INITQFNAMES { \
 	"user",    /* USRQUOTA */ \
 	"group",   /* GRPQUOTA */ \
+	"project", /* PRJQUOTA */ \
 	"undefined", \
 };
 

[PATCH RFC 3/6] quota: mangle statfs result according to project quota usage and limits

From: Konstantin Khlebnikov <hidden>
Date: 2015-02-11 15:11:49

This patch updates amount of available space and inodes returned by
vfs_statfs() according to project quota for this directory.
This works only if sysctl fs.protected_projects = 1.

Signed-off-by: Konstantin Khlebnikov <redacted>
---
 fs/quota/dquot.c         |   56 ++++++++++++++++++++++++++++++++++++++++++++--
 fs/statfs.c              |    5 +++-
 include/linux/quotaops.h |    5 ++++
 3 files changed, 63 insertions(+), 3 deletions(-)
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index afa5f67..e3dfac8 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -65,6 +65,7 @@
 #include <linux/stat.h>
 #include <linux/tty.h>
 #include <linux/file.h>
+#include <linux/statfs.h>
 #include <linux/slab.h>
 #include <linux/sysctl.h>
 #include <linux/init.h>
@@ -1242,15 +1243,20 @@ static void flush_warnings(struct dquot_warn *warn)
 	}
 }
 
-static int ignore_hardlimit(struct dquot *dquot)
+static bool sb_ignore_hardlimit(struct super_block *sb, int type)
 {
-	struct mem_dqinfo *info = &sb_dqopt(dquot->dq_sb)->info[dquot->dq_id.type];
+	struct mem_dqinfo *info = sb_dqinfo(sb, type);
 
 	return capable(CAP_SYS_RESOURCE) &&
 	       (info->dqi_format->qf_fmt_id != QFMT_VFS_OLD ||
 		!(info->dqi_flags & DQF_ROOT_SQUASH));
 }
 
+static bool ignore_hardlimit(struct dquot *dquot)
+{
+	return sb_ignore_hardlimit(dquot->dq_sb, dquot->dq_id.type);
+}
+
 /* needs dq_data_lock */
 static int check_idq(struct dquot *dquot, qsize_t inodes,
 		     struct dquot_warn *warn)
@@ -2022,6 +2028,52 @@ int dquot_file_open(struct inode *inode, struct file *file)
 }
 EXPORT_SYMBOL(dquot_file_open);
 
+void dquot_mangle_statfs(const struct path *path, struct kstatfs *buf)
+{
+	struct inode *inode = path->dentry->d_inode;
+	struct super_block *sb = inode->i_sb;
+	u64 blimit = 0, ilimit = 0, busage, iusage;
+	struct dquot **dquots;
+
+	if (!sysctl_protected_projects ||
+	    !sb_has_quota_limits_enabled(sb, PRJQUOTA) ||
+	    sb_ignore_hardlimit(sb, PRJQUOTA))
+		return;
+
+	dquots = i_dquot(inode);
+	if (!dquots[PRJQUOTA])
+		return;
+
+	spin_lock(&dq_data_lock);
+	if (dquots[PRJQUOTA]) {
+		struct mem_dqblk *dm = &(dquots[PRJQUOTA]->dq_dqb);
+
+		blimit = dm->dqb_bsoftlimit ?: dm->dqb_bhardlimit;
+		busage = dm->dqb_curspace + dm->dqb_rsvspace;
+		ilimit = dm->dqb_isoftlimit ?: dm->dqb_ihardlimit;
+		iusage = dm->dqb_curinodes;
+	}
+	spin_unlock(&dq_data_lock);
+
+	if (blimit) {
+		u64 bavail = (blimit <= busage) ? 0 :
+			div64_long(blimit - busage, buf->f_bsize);
+
+		if (buf->f_bavail > bavail)
+			buf->f_bavail = bavail;
+	}
+
+	if (ilimit) {
+		u64 iavail = (ilimit <= iusage) ? 0 : (ilimit - iusage);
+
+		if (buf->f_ffree > iavail) {
+			/* Cut count of "free" inodes but keep "used" */
+			buf->f_files -= buf->f_ffree - iavail;
+			buf->f_ffree = iavail;
+		}
+	}
+}
+
 /*
  * Turn quota off on a device. type == -1 ==> quotaoff for all types (umount)
  */
diff --git a/fs/statfs.c b/fs/statfs.c
index 083dc0a..3fa6192 100644
--- a/fs/statfs.c
+++ b/fs/statfs.c
@@ -7,6 +7,7 @@
 #include <linux/statfs.h>
 #include <linux/security.h>
 #include <linux/uaccess.h>
+#include <linux/quotaops.h>
 #include "internal.h"
 
 static int flags_by_mnt(int mnt_flags)
@@ -68,8 +69,10 @@ int vfs_statfs(struct path *path, struct kstatfs *buf)
 	int error;
 
 	error = statfs_by_dentry(path->dentry, buf);
-	if (!error)
+	if (!error) {
 		buf->f_flags = calculate_f_flags(path->mnt);
+		dquot_mangle_statfs(path, buf);
+	}
 	return error;
 }
 EXPORT_SYMBOL(vfs_statfs);
diff --git a/include/linux/quotaops.h b/include/linux/quotaops.h
index ba54745..42fe4d9 100644
--- a/include/linux/quotaops.h
+++ b/include/linux/quotaops.h
@@ -85,6 +85,7 @@ int dquot_commit_info(struct super_block *sb, int type);
 int dquot_mark_dquot_dirty(struct dquot *dquot);
 
 int dquot_file_open(struct inode *inode, struct file *file);
+void dquot_mangle_statfs(const struct path *path, struct kstatfs *buf);
 
 int dquot_enable(struct inode *inode, int type, int format_id,
 	unsigned int flags);
@@ -275,6 +276,10 @@ static inline int dquot_resume(struct super_block *sb, int type)
 
 #define dquot_file_open		generic_file_open
 
+static inline void dquot_mangle_statfs(struct path *path, struct kstatfs *buf)
+{
+}
+
 static inline int dquot_writeback_dquots(struct super_block *sb, int type)
 {
 	return 0;

[PATCH RFC 4/6] ext4: add project id support

From: Konstantin Khlebnikov <hidden>
Date: 2015-02-11 15:11:50

This patch adds a new internal field of ext4 inode to save project identifier.

Signed-off-by: Konstantin Khlebnikov <redacted>
---
 fs/ext4/ext4.h   |   13 ++++++++++-
 fs/ext4/ialloc.c |    6 +++++
 fs/ext4/inode.c  |   65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/ext4/namei.c  |   14 ++++++++++++
 fs/ext4/super.c  |    2 ++
 5 files changed, 99 insertions(+), 1 deletion(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index a75fba6..a3fdbb5 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -683,6 +683,7 @@ struct ext4_inode {
 	__le32  i_crtime;       /* File Creation time */
 	__le32  i_crtime_extra; /* extra FileCreationtime (nsec << 2 | epoch) */
 	__le32  i_version_hi;	/* high 32 bits for 64-bit version */
+	__le32  i_projid;	/* Project ID */
 };
 
 struct move_extent {
@@ -938,6 +939,7 @@ struct ext4_inode_info {
 
 	/* Precomputed uuid+inum+igen checksum for seeding inode checksums */
 	__u32 i_csum_seed;
+	kprojid_t i_projid;
 };
 
 /*
@@ -1522,6 +1524,7 @@ static inline void ext4_clear_state_flags(struct ext4_inode_info *ei)
  * GDT_CSUM bits are mutually exclusive.
  */
 #define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM	0x0400
+#define EXT4_FEATURE_RO_COMPAT_PROJECT		0x1000 /* Project ID */
 
 #define EXT4_FEATURE_INCOMPAT_COMPRESSION	0x0001
 #define EXT4_FEATURE_INCOMPAT_FILETYPE		0x0002
@@ -1571,7 +1574,8 @@ static inline void ext4_clear_state_flags(struct ext4_inode_info *ei)
 					 EXT4_FEATURE_RO_COMPAT_HUGE_FILE |\
 					 EXT4_FEATURE_RO_COMPAT_BIGALLOC |\
 					 EXT4_FEATURE_RO_COMPAT_METADATA_CSUM|\
-					 EXT4_FEATURE_RO_COMPAT_QUOTA)
+					 EXT4_FEATURE_RO_COMPAT_QUOTA |\
+					 EXT4_FEATURE_RO_COMPAT_PROJECT)
 
 /*
  * Default values for user and/or group using reserved blocks
@@ -1579,6 +1583,11 @@ static inline void ext4_clear_state_flags(struct ext4_inode_info *ei)
 #define	EXT4_DEF_RESUID		0
 #define	EXT4_DEF_RESGID		0
 
+/*
+ * Default project ID
+ */
+#define	EXT4_DEF_PROJID		0
+
 #define EXT4_DEF_INODE_READAHEAD_BLKS	32
 
 /*
@@ -2131,6 +2140,8 @@ extern int ext4_zero_partial_blocks(handle_t *handle, struct inode *inode,
 			     loff_t lstart, loff_t lend);
 extern int ext4_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf);
 extern qsize_t *ext4_get_reserved_space(struct inode *inode);
+extern int ext4_get_projid(struct inode *inode, kprojid_t *projid);
+extern int ext4_set_projid(struct inode *inode, kprojid_t projid);
 extern void ext4_da_update_reserve_space(struct inode *inode,
 					int used, int quota_claim);
 
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index ac644c3..d81a30d 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -756,6 +756,12 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir,
 		inode->i_gid = dir->i_gid;
 	} else
 		inode_init_owner(inode, dir, mode);
+
+	if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_PROJECT))
+		ei->i_projid = EXT4_I(dir)->i_projid;
+	else
+		ei->i_projid = KPROJIDT_INIT(EXT4_DEF_PROJID);
+
 	dquot_initialize(inode);
 
 	if (!goal)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 5653fa4..0ae2c39 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -3863,6 +3863,53 @@ static inline void ext4_iget_extra_inode(struct inode *inode,
 		EXT4_I(inode)->i_inline_off = 0;
 }
 
+int ext4_get_projid(struct inode *inode, kprojid_t *projid)
+{
+	struct super_block *sb = inode->i_sb;
+
+	if (!EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_PROJECT))
+		return -EOPNOTSUPP;
+
+	*projid = EXT4_I(inode)->i_projid;
+	return 0;
+}
+
+/* Called with inode->i_mutex locked. */
+int ext4_set_projid(struct inode *inode, kprojid_t projid)
+{
+	struct super_block *sb = inode->i_sb;
+	struct ext4_inode *raw_inode;
+	struct ext4_iloc iloc;
+	handle_t *handle;
+	int err;
+
+	if (!EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_PROJECT))
+		return -EOPNOTSUPP;
+
+	/* Sanity check */
+	if (EXT4_INODE_SIZE(sb) <= EXT4_GOOD_OLD_INODE_SIZE ||
+	    !EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), i_projid))
+		return -EOPNOTSUPP;
+
+	if (projid_eq(EXT4_I(inode)->i_projid, projid))
+		return 0;
+
+	handle = ext4_journal_start(inode, EXT4_HT_INODE, 1);
+	if (IS_ERR(handle))
+		return PTR_ERR(handle);
+	err = ext4_reserve_inode_write(handle, inode, &iloc);
+	if (!err) {
+		inode->i_ctime = ext4_current_time(inode);
+		EXT4_I(inode)->i_projid = projid;
+		err = ext4_mark_iloc_dirty(handle, inode, &iloc);
+	}
+	if (IS_SYNC(inode))
+		ext4_handle_sync(handle);
+	ext4_journal_stop(handle);
+
+	return err;
+}
+
 struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
 {
 	struct ext4_iloc iloc;
@@ -3874,6 +3921,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
 	int block;
 	uid_t i_uid;
 	gid_t i_gid;
+	projid_t i_projid;
 
 	inode = iget_locked(sb, ino);
 	if (!inode)
@@ -3923,12 +3971,20 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
 	inode->i_mode = le16_to_cpu(raw_inode->i_mode);
 	i_uid = (uid_t)le16_to_cpu(raw_inode->i_uid_low);
 	i_gid = (gid_t)le16_to_cpu(raw_inode->i_gid_low);
+
+	if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_PROJECT) &&
+	    EXT4_FITS_IN_INODE(raw_inode, ei, i_projid))
+		i_projid = (projid_t)le32_to_cpu(raw_inode->i_projid);
+	else
+		i_projid = EXT4_DEF_PROJID;
+
 	if (!(test_opt(inode->i_sb, NO_UID32))) {
 		i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
 		i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
 	}
 	i_uid_write(inode, i_uid);
 	i_gid_write(inode, i_gid);
+	ei->i_projid = KPROJIDT_INIT(i_projid);
 	set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
 
 	ext4_clear_state_flags(ei);	/* Only relevant on 32-bit archs */
@@ -4192,6 +4248,15 @@ static int ext4_do_update_inode(handle_t *handle,
 		raw_inode->i_uid_high = 0;
 		raw_inode->i_gid_high = 0;
 	}
+
+	if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_PROJECT) &&
+	    EXT4_FITS_IN_INODE(raw_inode, ei, i_projid)) {
+		projid_t i_projid;
+
+		i_projid = from_kprojid(&init_user_ns, ei->i_projid);
+		raw_inode->i_projid = cpu_to_le32(i_projid);
+	}
+
 	raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
 
 	EXT4_INODE_SET_XTIME(i_ctime, inode, raw_inode);
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 2291923..9337d81 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -2938,6 +2938,10 @@ static int ext4_link(struct dentry *old_dentry,
 	if (inode->i_nlink >= EXT4_LINK_MAX)
 		return -EMLINK;
 
+	if (!capable_mix_inode_project(EXT4_I(dir)->i_projid,
+				       EXT4_I(inode)->i_projid))
+		return -EXDEV;
+
 	dquot_initialize(dir);
 
 retry:
@@ -3217,6 +3221,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
 	int credits;
 	u8 old_file_type;
 
+	if (!capable_mix_inode_project(EXT4_I(new.dir)->i_projid,
+				       EXT4_I(old.inode)->i_projid))
+		return -EXDEV;
+
 	dquot_initialize(old.dir);
 	dquot_initialize(new.dir);
 
@@ -3395,6 +3403,12 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
 	u8 new_file_type;
 	int retval;
 
+	if (!capable_mix_inode_project(EXT4_I(new.dir)->i_projid,
+				       EXT4_I(old.inode)->i_projid) ||
+	    !capable_mix_inode_project(EXT4_I(old.dir)->i_projid,
+				       EXT4_I(new.inode)->i_projid))
+		return -EXDEV;
+
 	dquot_initialize(old.dir);
 	dquot_initialize(new.dir);
 
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index ac64edb..d656269 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1103,6 +1103,8 @@ static const struct super_operations ext4_sops = {
 	.get_dquots	= ext4_get_dquots,
 #endif
 	.bdev_try_to_free_page = bdev_try_to_free_page,
+	.get_projid	= ext4_get_projid,
+	.set_projid	= ext4_set_projid,
 };
 
 static const struct export_operations ext4_export_ops = {

[PATCH RFC 5/6] ext4: adds project quota support

From: Konstantin Khlebnikov <hidden>
Date: 2015-02-11 15:11:52

This patch adds mount options for enabling/disabling project quota
accounting and enforcement. A new specific inode is also used for
project quota accounting.

[ Mostly unchanged patch from Li Xi [off-list ref] ]

Signed-off-by: Konstantin Khlebnikov <redacted>
Cc: Li Xi <redacted>
---
 fs/ext4/ext4.h  |    8 ++++++--
 fs/ext4/inode.c |   12 +++++++++++-
 fs/ext4/super.c |   53 ++++++++++++++++++++++++++++++++++++++++++-----------
 3 files changed, 59 insertions(+), 14 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index a3fdbb5..da153c3 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -208,6 +208,7 @@ struct ext4_io_submit {
 #define EXT4_UNDEL_DIR_INO	 6	/* Undelete directory inode */
 #define EXT4_RESIZE_INO		 7	/* Reserved group descriptors inode */
 #define EXT4_JOURNAL_INO	 8	/* Journal inode */
+#define EXT4_PRJ_QUOTA_INO	 9	/* Project quota inode */
 
 /* First non-reserved inode for old ext4 filesystems */
 #define EXT4_GOOD_OLD_FIRST_INO	11
@@ -983,6 +984,7 @@ struct ext4_inode_info {
 #define EXT4_MOUNT_DIOREAD_NOLOCK	0x400000 /* Enable support for dio read nolocking */
 #define EXT4_MOUNT_JOURNAL_CHECKSUM	0x800000 /* Journal checksums */
 #define EXT4_MOUNT_JOURNAL_ASYNC_COMMIT	0x1000000 /* Journal Async Commit */
+#define EXT4_MOUNT_PRJQUOTA		0x2000000 /* Project quota support */
 #define EXT4_MOUNT_DELALLOC		0x8000000 /* Delalloc support */
 #define EXT4_MOUNT_DATA_ERR_ABORT	0x10000000 /* Abort on file data write */
 #define EXT4_MOUNT_BLOCK_VALIDITY	0x20000000 /* Block validity checking */
@@ -1158,7 +1160,8 @@ struct ext4_super_block {
 	__le32	s_grp_quota_inum;	/* inode for tracking group quota */
 	__le32	s_overhead_clusters;	/* overhead blocks/clusters in fs */
 	__le32	s_backup_bgs[2];	/* groups with sparse_super2 SBs */
-	__le32	s_reserved[106];	/* Padding to the end of the block */
+	__le32	s_prj_quota_inum;	/* inode for tracking project quota */
+	__le32	s_reserved[105];	/* Padding to the end of the block */
 	__le32	s_checksum;		/* crc32c(superblock) */
 };
 
@@ -1173,7 +1176,7 @@ struct ext4_super_block {
 #define EXT4_MF_FS_ABORTED	0x0002	/* Fatal error detected */
 
 /* Number of quota types we support */
-#define EXT4_MAXQUOTAS 2
+#define EXT4_MAXQUOTAS 3
 
 /*
  * fourth extended-fs super-block data in memory
@@ -1365,6 +1368,7 @@ static inline int ext4_valid_inum(struct super_block *sb, unsigned long ino)
 		ino == EXT4_BOOT_LOADER_INO ||
 		ino == EXT4_JOURNAL_INO ||
 		ino == EXT4_RESIZE_INO ||
+		ino == EXT4_PRJ_QUOTA_INO ||
 		(ino >= EXT4_FIRST_INO(sb) &&
 		 ino <= le32_to_cpu(EXT4_SB(sb)->s_es->s_inodes_count));
 }
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 0ae2c39..966bad1 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -3894,9 +3894,18 @@ int ext4_set_projid(struct inode *inode, kprojid_t projid)
 	if (projid_eq(EXT4_I(inode)->i_projid, projid))
 		return 0;
 
-	handle = ext4_journal_start(inode, EXT4_HT_INODE, 1);
+	dquot_initialize(inode);
+
+	handle = ext4_journal_start(inode, EXT4_HT_INODE, 1 +
+					EXT4_QUOTA_INIT_BLOCKS(sb) +
+					EXT4_QUOTA_DEL_BLOCKS(sb));
 	if (IS_ERR(handle))
 		return PTR_ERR(handle);
+
+	err = dquot_transfer_project(inode, projid);
+	if (err)
+		goto out;
+
 	err = ext4_reserve_inode_write(handle, inode, &iloc);
 	if (!err) {
 		inode->i_ctime = ext4_current_time(inode);
@@ -3905,6 +3914,7 @@ int ext4_set_projid(struct inode *inode, kprojid_t projid)
 	}
 	if (IS_SYNC(inode))
 		ext4_handle_sync(handle);
+out:
 	ext4_journal_stop(handle);
 
 	return err;
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index d656269..3637eef 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1036,8 +1036,8 @@ static int bdev_try_to_free_page(struct super_block *sb, struct page *page,
 }
 
 #ifdef CONFIG_QUOTA
-#define QTYPE2NAME(t) ((t) == USRQUOTA ? "user" : "group")
-#define QTYPE2MOPT(on, t) ((t) == USRQUOTA?((on)##USRJQUOTA):((on)##GRPJQUOTA))
+static char *quotatypes[] = INITQFNAMES;
+#define QTYPE2NAME(t) (quotatypes[t])
 
 static int ext4_write_dquot(struct dquot *dquot);
 static int ext4_acquire_dquot(struct dquot *dquot);
@@ -1123,10 +1123,11 @@ enum {
 	Opt_journal_path, Opt_journal_checksum, Opt_journal_async_commit,
 	Opt_abort, Opt_data_journal, Opt_data_ordered, Opt_data_writeback,
 	Opt_data_err_abort, Opt_data_err_ignore,
-	Opt_usrjquota, Opt_grpjquota, Opt_offusrjquota, Opt_offgrpjquota,
+	Opt_usrjquota, Opt_grpjquota, Opt_prjjquota,
+	Opt_offusrjquota, Opt_offgrpjquota, Opt_offprjjquota,
 	Opt_jqfmt_vfsold, Opt_jqfmt_vfsv0, Opt_jqfmt_vfsv1, Opt_quota,
 	Opt_noquota, Opt_barrier, Opt_nobarrier, Opt_err,
-	Opt_usrquota, Opt_grpquota, Opt_i_version,
+	Opt_usrquota, Opt_grpquota, Opt_prjquota, Opt_i_version,
 	Opt_stripe, Opt_delalloc, Opt_nodelalloc, Opt_mblk_io_submit,
 	Opt_nomblk_io_submit, Opt_block_validity, Opt_noblock_validity,
 	Opt_inode_readahead_blks, Opt_journal_ioprio,
@@ -1178,6 +1179,8 @@ static const match_table_t tokens = {
 	{Opt_usrjquota, "usrjquota=%s"},
 	{Opt_offgrpjquota, "grpjquota="},
 	{Opt_grpjquota, "grpjquota=%s"},
+	{Opt_offprjjquota, "prjjquota="},
+	{Opt_prjjquota, "prjjquota=%s"},
 	{Opt_jqfmt_vfsold, "jqfmt=vfsold"},
 	{Opt_jqfmt_vfsv0, "jqfmt=vfsv0"},
 	{Opt_jqfmt_vfsv1, "jqfmt=vfsv1"},
@@ -1185,6 +1188,7 @@ static const match_table_t tokens = {
 	{Opt_noquota, "noquota"},
 	{Opt_quota, "quota"},
 	{Opt_usrquota, "usrquota"},
+	{Opt_prjquota, "prjquota"},
 	{Opt_barrier, "barrier=%u"},
 	{Opt_barrier, "barrier"},
 	{Opt_nobarrier, "nobarrier"},
@@ -1399,12 +1403,17 @@ static const struct mount_opts {
 							MOPT_SET | MOPT_Q},
 	{Opt_grpquota, EXT4_MOUNT_QUOTA | EXT4_MOUNT_GRPQUOTA,
 							MOPT_SET | MOPT_Q},
+	{Opt_prjquota, EXT4_MOUNT_QUOTA | EXT4_MOUNT_PRJQUOTA,
+							MOPT_SET | MOPT_Q},
 	{Opt_noquota, (EXT4_MOUNT_QUOTA | EXT4_MOUNT_USRQUOTA |
-		       EXT4_MOUNT_GRPQUOTA), MOPT_CLEAR | MOPT_Q},
+		       EXT4_MOUNT_GRPQUOTA | EXT4_MOUNT_PRJQUOTA),
+							MOPT_CLEAR | MOPT_Q},
 	{Opt_usrjquota, 0, MOPT_Q},
 	{Opt_grpjquota, 0, MOPT_Q},
+	{Opt_prjjquota, 0, MOPT_Q},
 	{Opt_offusrjquota, 0, MOPT_Q},
 	{Opt_offgrpjquota, 0, MOPT_Q},
+	{Opt_offprjjquota, 0, MOPT_Q},
 	{Opt_jqfmt_vfsold, QFMT_VFS_OLD, MOPT_QFMT},
 	{Opt_jqfmt_vfsv0, QFMT_VFS_V0, MOPT_QFMT},
 	{Opt_jqfmt_vfsv1, QFMT_VFS_V1, MOPT_QFMT},
@@ -1427,10 +1436,14 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token,
 		return set_qf_name(sb, USRQUOTA, &args[0]);
 	else if (token == Opt_grpjquota)
 		return set_qf_name(sb, GRPQUOTA, &args[0]);
+	else if (token == Opt_prjjquota)
+		return set_qf_name(sb, PRJQUOTA, &args[0]);
 	else if (token == Opt_offusrjquota)
 		return clear_qf_name(sb, USRQUOTA);
 	else if (token == Opt_offgrpjquota)
 		return clear_qf_name(sb, GRPQUOTA);
+	else if (token == Opt_offprjjquota)
+		return clear_qf_name(sb, PRJQUOTA);
 #endif
 	switch (token) {
 	case Opt_noacl:
@@ -1656,19 +1669,28 @@ static int parse_options(char *options, struct super_block *sb,
 	}
 #ifdef CONFIG_QUOTA
 	if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA) &&
-	    (test_opt(sb, USRQUOTA) || test_opt(sb, GRPQUOTA))) {
+	    (test_opt(sb, USRQUOTA) ||
+	     test_opt(sb, GRPQUOTA) ||
+	     test_opt(sb, PRJQUOTA))) {
 		ext4_msg(sb, KERN_ERR, "Cannot set quota options when QUOTA "
 			 "feature is enabled");
 		return 0;
 	}
-	if (sbi->s_qf_names[USRQUOTA] || sbi->s_qf_names[GRPQUOTA]) {
+	if (sbi->s_qf_names[USRQUOTA] ||
+	    sbi->s_qf_names[GRPQUOTA] ||
+	    sbi->s_qf_names[PRJQUOTA]) {
 		if (test_opt(sb, USRQUOTA) && sbi->s_qf_names[USRQUOTA])
 			clear_opt(sb, USRQUOTA);
 
 		if (test_opt(sb, GRPQUOTA) && sbi->s_qf_names[GRPQUOTA])
 			clear_opt(sb, GRPQUOTA);
 
-		if (test_opt(sb, GRPQUOTA) || test_opt(sb, USRQUOTA)) {
+		if (test_opt(sb, PRJQUOTA) && sbi->s_qf_names[PRJQUOTA])
+			clear_opt(sb, PRJQUOTA);
+
+		if (test_opt(sb, GRPQUOTA) ||
+		    test_opt(sb, USRQUOTA) ||
+		    test_opt(sb, PRJQUOTA)) {
 			ext4_msg(sb, KERN_ERR, "old and new quota "
 					"format mixing");
 			return 0;
@@ -1728,6 +1750,9 @@ static inline void ext4_show_quota_options(struct seq_file *seq,
 
 	if (sbi->s_qf_names[GRPQUOTA])
 		seq_printf(seq, ",grpjquota=%s", sbi->s_qf_names[GRPQUOTA]);
+
+	if (sbi->s_qf_names[PRJQUOTA])
+		seq_printf(seq, ",prjjquota=%s", sbi->s_qf_names[PRJQUOTA]);
 #endif
 }
 
@@ -3928,6 +3953,8 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
 	else
 		sb->s_qcop = &ext4_qctl_operations;
 	sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
+	if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_PROJECT))
+		sb->s_quota_types = QTYPE_MASK_PRJ;
 #endif
 	memcpy(sb->s_uuid, es->s_uuid, sizeof(es->s_uuid));
 
@@ -5145,7 +5172,9 @@ static int ext4_mark_dquot_dirty(struct dquot *dquot)
 
 	/* Are we journaling quotas? */
 	if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA) ||
-	    sbi->s_qf_names[USRQUOTA] || sbi->s_qf_names[GRPQUOTA]) {
+	    sbi->s_qf_names[USRQUOTA] ||
+	    sbi->s_qf_names[GRPQUOTA] ||
+	    sbi->s_qf_names[PRJQUOTA]) {
 		dquot_mark_dquot_dirty(dquot);
 		return ext4_write_dquot(dquot);
 	} else {
@@ -5229,7 +5258,8 @@ static int ext4_quota_enable(struct super_block *sb, int type, int format_id,
 	struct inode *qf_inode;
 	unsigned long qf_inums[EXT4_MAXQUOTAS] = {
 		le32_to_cpu(EXT4_SB(sb)->s_es->s_usr_quota_inum),
-		le32_to_cpu(EXT4_SB(sb)->s_es->s_grp_quota_inum)
+		le32_to_cpu(EXT4_SB(sb)->s_es->s_grp_quota_inum),
+		le32_to_cpu(EXT4_SB(sb)->s_es->s_prj_quota_inum)
 	};
 
 	BUG_ON(!EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA));
@@ -5257,7 +5287,8 @@ static int ext4_enable_quotas(struct super_block *sb)
 	int type, err = 0;
 	unsigned long qf_inums[EXT4_MAXQUOTAS] = {
 		le32_to_cpu(EXT4_SB(sb)->s_es->s_usr_quota_inum),
-		le32_to_cpu(EXT4_SB(sb)->s_es->s_grp_quota_inum)
+		le32_to_cpu(EXT4_SB(sb)->s_es->s_grp_quota_inum),
+		le32_to_cpu(EXT4_SB(sb)->s_es->s_prj_quota_inum)
 	};
 
 	sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE;

[PATCH RFC 6/6] tools/quota/project_quota: sample tool for early adopters

From: Konstantin Khlebnikov <hidden>
Date: 2015-02-11 15:11:53

Usage: ./project_quota <command> <path> [args]...
Commands:
  init    <path>                initialize quota file
  on      <path>                turn on
  off     <path>                turn off
  info    <path>                show project, usage and limits
  project <path> [<id>]         get / set project id
  limit   <path> [<bytes>]      get / set space limit
  ilimit  <path> [<inodes>]     get / set inodes limit


How to enable feature using debugfs tool:
# debugfs
debugfs:  open -w <disk>
debugfs:  feature +FEATURE_R12
debugfs:  quit
# mount ...
# project_quota init <mountpoint>
# project_quota on <mountpoint>

Signed-off-by: Konstantin Khlebnikov <redacted>
---
 tools/quota/.gitignore      |    1 
 tools/quota/Makefile        |    6 +
 tools/quota/project_quota.c |  324 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 331 insertions(+)
 create mode 100644 tools/quota/.gitignore
 create mode 100644 tools/quota/Makefile
 create mode 100644 tools/quota/project_quota.c
diff --git a/tools/quota/.gitignore b/tools/quota/.gitignore
new file mode 100644
index 0000000..4aacefc
--- /dev/null
+++ b/tools/quota/.gitignore
@@ -0,0 +1 @@
+project_quota
diff --git a/tools/quota/Makefile b/tools/quota/Makefile
new file mode 100644
index 0000000..0c3daef
--- /dev/null
+++ b/tools/quota/Makefile
@@ -0,0 +1,6 @@
+CFLAGS=-Wall -W
+
+project_quota:
+
+clean:
+	rm project_quota
diff --git a/tools/quota/project_quota.c b/tools/quota/project_quota.c
new file mode 100644
index 0000000..ca7f49a
--- /dev/null
+++ b/tools/quota/project_quota.c
@@ -0,0 +1,324 @@
+/*
+ * project_quota: Tool for project disk quota manipulations
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should find a copy of v2 of the GNU General Public License somewhere on
+ * your Linux system; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * Copyright (C) 2015 Yandex LLC
+ *
+ * Authors: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
+ */
+
+#define _FILE_OFFSET_BITS 64
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/quota.h>
+#include <sys/quota.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE	1024
+#endif
+
+#ifndef F_GET_PROJECT
+#define F_GET_PROJECT  (F_LINUX_SPECIFIC_BASE + 11)
+#define F_SET_PROJECT  (F_LINUX_SPECIFIC_BASE + 12)
+#endif
+
+#ifndef PRJQUOTA
+#define PRJQUOTA 2
+#endif
+
+/* First generic header */
+struct v2_disk_dqheader {
+	__le32 dqh_magic;	/* Magic number identifying file */
+	__le32 dqh_version;	/* File version */
+};
+
+/* Header with type and version specific information */
+struct v2_disk_dqinfo {
+	__le32 dqi_bgrace;	/* Time before block soft limit becomes hard limit */
+	__le32 dqi_igrace;	/* Time before inode soft limit becomes hard limit */
+	__le32 dqi_flags;	/* Flags for quotafile (DQF_*) */
+	__le32 dqi_blocks;	/* Number of blocks in file */
+	__le32 dqi_free_blk;	/* Number of first free block in the list */
+	__le32 dqi_free_entry;	/* Number of block with at least one free entry */
+};
+
+#define PROJECT_QUOTA_FILE	"quota.project"
+#define PROJECT_QUOTA_MAGIC	0xd9c03f14
+
+static int find_mountpoint(const char *path, struct stat *path_st,
+		char **device, char **fstype, char **root_path)
+{
+	struct stat dev_st;
+	char *buf = NULL, *ptr, *real_device;
+	unsigned major, minor;
+	size_t len;
+	FILE *file;
+
+	if (stat(path, path_st))
+		return -1;
+
+	*root_path = malloc(PATH_MAX + 1);
+
+	/* since v2.6.26 */
+	file = fopen("/proc/self/mountinfo", "r");
+	if (!file)
+		goto parse_mounts;
+	while (getline(&buf, &len, file) > 0) {
+		sscanf(buf, "%*d %*d %u:%u %*s %s", &major, &minor, *root_path);
+		if (makedev(major, minor) != path_st->st_dev)
+			continue;
+		ptr = strstr(buf, " - ") + 3;
+		*fstype = strdup(strsep(&ptr, " "));
+		*device = strdup(strsep(&ptr, " "));
+		goto found;
+	}
+
+parse_mounts:
+	/* for older versions */
+	file = fopen("/proc/mounts", "r");
+	if (!file)
+		goto not_found;
+	while (getline(&buf, &len, file) > 0) {
+		ptr = buf;
+		strsep(&ptr, " ");
+		if (*buf != '/' || stat(buf, &dev_st) ||
+		    dev_st.st_rdev != path_st->st_dev)
+			continue;
+		strcpy(*root_path, strsep(&ptr, " "));
+		*fstype = strdup(strsep(&ptr, " "));
+		*device = strdup(buf);
+		goto found;
+	}
+not_found:
+	free(*root_path);
+	errno = ENODEV;
+	return -1;
+
+found:
+	real_device = realpath(*device, NULL);
+	if (real_device) {
+		free(*device);
+		*device = real_device;
+	}
+	return 0;
+}
+
+static int init_project_quota(const char *quota_path)
+{
+	struct {
+		struct v2_disk_dqheader header;
+		struct v2_disk_dqinfo info;
+		char zero[1024 * 2 - 8 * 4];
+	} quota_init = {
+		.header = {
+			.dqh_magic = PROJECT_QUOTA_MAGIC,
+			.dqh_version = 1,
+		},
+		.info = {
+			.dqi_bgrace = 7 * 24 * 60 * 60,
+			.dqi_igrace = 7 * 24 * 60 * 60,
+			.dqi_flags = 0,
+			.dqi_blocks = 2, /* header and root */
+			.dqi_free_blk = 0,
+			.dqi_free_entry = 0,
+		},
+		.zero = {0, },
+	};
+	int fd;
+
+	fd = open(quota_path, O_CREAT|O_RDWR|O_EXCL, 0600);
+	if (fd < 0)
+		return fd;
+	write(fd, &quota_init, sizeof(quota_init));
+	fsync(fd);
+	close(fd);
+	return 0;
+}
+
+static int get_project_id(const char *path, unsigned *project_id)
+{
+	int fd, ret;
+
+	fd = open(path, O_PATH);
+	if (fd < 0)
+		return fd;
+	ret = fcntl(fd, F_GET_PROJECT, project_id);
+	close(fd);
+	return ret;
+}
+
+static int set_project_id(const char *path, unsigned project_id)
+{
+	int fd, ret;
+
+	fd = open(path, O_PATH);
+	if (fd < 0)
+		return fd;
+	ret = fcntl(fd, F_SET_PROJECT, project_id);
+	close(fd);
+	return ret;
+}
+
+static void get_project_quota(const char *device, unsigned project_id,
+		struct if_dqblk *quota)
+{
+	if (quotactl(QCMD(Q_GETQUOTA, PRJQUOTA), device,
+				project_id, (caddr_t)quota))
+		err(2, "cannot get project quota \"%u\" at \"%s\"",
+				project_id, device);
+}
+
+static void set_project_quota(const char *device, unsigned project_id,
+		struct if_dqblk *quota)
+{
+	if (quotactl(QCMD(Q_SETQUOTA, PRJQUOTA),
+		     device, project_id, (caddr_t)quota))
+		err(2, "cannot set project quota \"%u\" at \"%s\"",
+				project_id, device);
+}
+
+int main (int argc, char **argv) {
+	char *cmd, *path, *device, *fstype, *root_path;
+	struct if_dqblk quota;
+	struct stat path_st;
+	unsigned project_id;
+
+	if (argc < 3)
+		goto usage;
+
+	cmd = argv[1];
+	path = argv[2];
+	if (find_mountpoint(path, &path_st, &device, &fstype, &root_path))
+		err(2, "cannot find mountpoint for \"%s\"", path);
+
+	if (!strcmp(cmd, "limit") || !strcmp(cmd, "ilimit") ||
+	    !strcmp(cmd, "info") || !strcmp(cmd, "parent")) {
+		if (get_project_id(path, &project_id))
+			err(2, "cannot get project id for \"%s\"", path);
+	}
+
+	if (!strcmp(cmd, "init")) {
+		if (S_ISDIR(path_st.st_mode))
+			asprintf(&path, "%s/%s", path, PROJECT_QUOTA_FILE);
+
+		if (init_project_quota(path))
+			err(2, "cannot init project quota file \"%s\"", path);
+
+	} else if (!strcmp(cmd, "on")) {
+		struct v2_disk_dqheader header;
+		int fd, format;
+
+		if (S_ISDIR(path_st.st_mode))
+			asprintf(&path, "%s/%s", path, PROJECT_QUOTA_FILE);
+
+		fd = open(path, O_RDONLY);
+		if (fd < 0)
+			err(2, "cannot open quota file \"%s\"", path);
+		if (read(fd, &header, sizeof(header)) != sizeof(header))
+			err(2, "cannot read quota file \"%s\"", path);
+		close(fd);
+
+		if (header.dqh_magic != PROJECT_QUOTA_MAGIC)
+			errx(2, "wrong quota file magic");
+
+		if (header.dqh_version == 1)
+			format = QFMT_VFS_V1;
+		else
+			errx(2, "unsupported quota file version");
+
+		if (mount(NULL, root_path, NULL, MS_REMOUNT, "prjquota"))
+			err(2, "cannot remount \"%s\"", root_path);
+
+		if (quotactl(QCMD(Q_QUOTAON, PRJQUOTA), device,
+					format, (caddr_t)path))
+			err(2, "cannot turn on project quota for %s", device);
+
+	} else if (!strcmp(cmd, "off")) {
+
+		if (quotactl(QCMD(Q_QUOTAOFF, PRJQUOTA), device, 0, NULL))
+			err(2, "cannot turn off project quota for %s", device);
+
+	} else if (!strcmp(cmd, "project")) {
+		if (argc < 4) {
+			if (get_project_id(path, &project_id))
+				err(2, "cannot get project id for \"%s\"", path);
+			printf("%u\n", project_id);
+		} else {
+			project_id = atoi(argv[3]);
+			if (set_project_id(path, project_id))
+				err(2, "cannot set project id for \"%s\"", path);
+		}
+	} else if (!strcmp(cmd, "limit")) {
+		if (argc < 4) {
+			get_project_quota(device, project_id, &quota);
+			printf("%lld\n", quota.dqb_bhardlimit * QIF_DQBLKSIZE);
+		} else {
+			quota.dqb_bhardlimit = atoll(argv[3]) / QIF_DQBLKSIZE;
+			quota.dqb_bsoftlimit = 0;
+			quota.dqb_valid = QIF_BLIMITS;
+			set_project_quota(device, project_id, &quota);
+		}
+	} else if (!strcmp(cmd, "ilimit")) {
+		if (argc < 4) {
+			get_project_quota(device, project_id, &quota);
+			printf("%lld\n", quota.dqb_ihardlimit);
+		} else {
+			quota.dqb_ihardlimit = atoll(argv[3]);
+			quota.dqb_isoftlimit = 0;
+			quota.dqb_valid = QIF_ILIMITS;
+			set_project_quota(device, project_id, &quota);
+		}
+	} else if (!strcmp(cmd, "info")) {
+		get_project_quota(device, project_id, &quota);
+		printf("project   %u\n", project_id);
+		printf("usage     %llu\n", quota.dqb_curspace);
+		printf("limit     %llu\n", quota.dqb_bhardlimit * QIF_DQBLKSIZE);
+		printf("inodes    %llu\n", quota.dqb_curinodes);
+		printf("ilimit    %llu\n", quota.dqb_ihardlimit);
+	} else {
+		warnx("Unknown command \"%s\"", cmd);
+		goto usage;
+	}
+
+	free(device);
+	free(fstype);
+	free(root_path);
+
+	return 0;
+
+usage:
+	fprintf(stderr, "Usage: %s <command> <path> [args]...\n"
+			"Commands:\n"
+			"  init    <path>                initialize quota file\n"
+			"  on      <path>                turn on\n"
+			"  off     <path>                turn off\n"
+			"  info    <path>                show project, usage and limits\n"
+			"  project <path> [<id>]         get / set project id\n"
+			"  limit   <path> [<bytes>]      get / set space limit\n"
+			"  ilimit  <path> [<inodes>]     get / set inodes limit\n",
+			argv[0]);
+	return 2;
+}
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help