linux-fsdevel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH RFC 1/6] fs: new interface and behavior for file project id
@ 2015-02-11 15:11 Konstantin Khlebnikov
  2015-02-11 15:11 ` [PATCH RFC 2/6] quota: adds generic code for enforcing project quota limits Konstantin Khlebnikov
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Konstantin Khlebnikov @ 2015-02-11 15:11 UTC (permalink / raw)
  To: Linux FS Devel, linux-ext4, linux-kernel
  Cc: Jan Kara, Linux API, containers, Dave Chinner, Andy Lutomirski,
	Christoph Hellwig, Dmitry Monakhov, Eric W. Biederman, Li Xi,
	Theodore Ts'o, Al Viro

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 <khlebnikov@yandex-team.ru>
---
 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);
 }
 

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

* [PATCH RFC 2/6] quota: adds generic code for enforcing project quota limits
  2015-02-11 15:11 [PATCH RFC 1/6] fs: new interface and behavior for file project id Konstantin Khlebnikov
@ 2015-02-11 15:11 ` Konstantin Khlebnikov
  2015-02-11 15:11 ` [PATCH RFC 3/6] quota: mangle statfs result according to project quota usage and limits Konstantin Khlebnikov
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Khlebnikov @ 2015-02-11 15:11 UTC (permalink / raw)
  To: Linux FS Devel, linux-ext4, linux-kernel
  Cc: Jan Kara, Linux API, containers, Dave Chinner, Andy Lutomirski,
	Christoph Hellwig, Dmitry Monakhov, Eric W. Biederman, Li Xi,
	Theodore Ts'o, Al Viro

This patch adds support for a new quota type PRJQUOTA for project quota.
[ Based on patch by Li Xi <lixi@ddn.com> ]

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 <khlebnikov@yandex-team.ru>
Cc: Li Xi <lixi@ddn.com>
---
 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", \
 };
 

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

* [PATCH RFC 3/6] quota: mangle statfs result according to project quota usage and limits
  2015-02-11 15:11 [PATCH RFC 1/6] fs: new interface and behavior for file project id Konstantin Khlebnikov
  2015-02-11 15:11 ` [PATCH RFC 2/6] quota: adds generic code for enforcing project quota limits Konstantin Khlebnikov
@ 2015-02-11 15:11 ` Konstantin Khlebnikov
  2015-02-11 15:11 ` [PATCH RFC 4/6] ext4: add project id support Konstantin Khlebnikov
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Khlebnikov @ 2015-02-11 15:11 UTC (permalink / raw)
  To: Linux FS Devel, linux-ext4, linux-kernel
  Cc: Jan Kara, Linux API, containers, Dave Chinner, Andy Lutomirski,
	Christoph Hellwig, Dmitry Monakhov, Eric W. Biederman, Li Xi,
	Theodore Ts'o, Al Viro

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 <khlebnikov@yandex-team.ru>
---
 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;

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

* [PATCH RFC 4/6] ext4: add project id support
  2015-02-11 15:11 [PATCH RFC 1/6] fs: new interface and behavior for file project id Konstantin Khlebnikov
  2015-02-11 15:11 ` [PATCH RFC 2/6] quota: adds generic code for enforcing project quota limits Konstantin Khlebnikov
  2015-02-11 15:11 ` [PATCH RFC 3/6] quota: mangle statfs result according to project quota usage and limits Konstantin Khlebnikov
@ 2015-02-11 15:11 ` Konstantin Khlebnikov
  2015-02-11 15:11 ` [PATCH RFC 5/6] ext4: adds project quota support Konstantin Khlebnikov
  2015-02-11 15:11 ` [PATCH RFC 6/6] tools/quota/project_quota: sample tool for early adopters Konstantin Khlebnikov
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Khlebnikov @ 2015-02-11 15:11 UTC (permalink / raw)
  To: Linux FS Devel, linux-ext4, linux-kernel
  Cc: Jan Kara, Linux API, containers, Dave Chinner, Andy Lutomirski,
	Christoph Hellwig, Dmitry Monakhov, Eric W. Biederman, Li Xi,
	Theodore Ts'o, Al Viro

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

Signed-off-by: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
---
 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 = {

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

* [PATCH RFC 5/6] ext4: adds project quota support
  2015-02-11 15:11 [PATCH RFC 1/6] fs: new interface and behavior for file project id Konstantin Khlebnikov
                   ` (2 preceding siblings ...)
  2015-02-11 15:11 ` [PATCH RFC 4/6] ext4: add project id support Konstantin Khlebnikov
@ 2015-02-11 15:11 ` Konstantin Khlebnikov
  2015-02-11 15:11 ` [PATCH RFC 6/6] tools/quota/project_quota: sample tool for early adopters Konstantin Khlebnikov
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Khlebnikov @ 2015-02-11 15:11 UTC (permalink / raw)
  To: Linux FS Devel, linux-ext4, linux-kernel
  Cc: Jan Kara, Linux API, containers, Dave Chinner, Andy Lutomirski,
	Christoph Hellwig, Dmitry Monakhov, Eric W. Biederman, Li Xi,
	Theodore Ts'o, Al Viro

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 <lixi@ddn.com> ]

Signed-off-by: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
Cc: Li Xi <lixi@ddn.com>
---
 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;

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

* [PATCH RFC 6/6] tools/quota/project_quota: sample tool for early adopters
  2015-02-11 15:11 [PATCH RFC 1/6] fs: new interface and behavior for file project id Konstantin Khlebnikov
                   ` (3 preceding siblings ...)
  2015-02-11 15:11 ` [PATCH RFC 5/6] ext4: adds project quota support Konstantin Khlebnikov
@ 2015-02-11 15:11 ` Konstantin Khlebnikov
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Khlebnikov @ 2015-02-11 15:11 UTC (permalink / raw)
  To: Linux FS Devel, linux-ext4, linux-kernel
  Cc: Jan Kara, Linux API, containers, Dave Chinner, Andy Lutomirski,
	Christoph Hellwig, Dmitry Monakhov, Eric W. Biederman, Li Xi,
	Theodore Ts'o, Al Viro

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 <khlebnikov@yandex-team.ru>
---
 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;
+}

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

end of thread, other threads:[~2015-02-11 15:11 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-02-11 15:11 [PATCH RFC 1/6] fs: new interface and behavior for file project id Konstantin Khlebnikov
2015-02-11 15:11 ` [PATCH RFC 2/6] quota: adds generic code for enforcing project quota limits Konstantin Khlebnikov
2015-02-11 15:11 ` [PATCH RFC 3/6] quota: mangle statfs result according to project quota usage and limits Konstantin Khlebnikov
2015-02-11 15:11 ` [PATCH RFC 4/6] ext4: add project id support Konstantin Khlebnikov
2015-02-11 15:11 ` [PATCH RFC 5/6] ext4: adds project quota support Konstantin Khlebnikov
2015-02-11 15:11 ` [PATCH RFC 6/6] tools/quota/project_quota: sample tool for early adopters Konstantin Khlebnikov

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).