[RFC] quota: 64-bit limits with vfs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello!

We are in need of large (above 4 TB) block quota limits, but it seems like XFS filesystem 
(having its own quota implementation) is the only available fs that supports them. Currently
ext3 supports up to 8 TB of data and forthcoming ext4 will support even more.

Linux kernel has two implementations of quota format modules:
quota_v1 (with QFMT_VFS_OLD id)
quota_v2 (with QFMT_VFS_V0 id)
Either uses 32-bit data types to store quota limits on disk
(see struct v1_disk_dqblk and struct v2_disk_dqblk). Block quota limits 
are stored in 1kb units (QUOTABLOCK_SIZE constant) which gives
the largest possible quota limit of (2^32-1)*2^10 bytes ~ 4 TB.

In-memory quota entries representation suffers from the same 4 TB 
limitation (see struct mem_dqblk).

The patch below adds a separate quota_v3 module which deals with 64-bit data  to solve the problem
(another possible approach is to merge the code into quota_v2 module to reuse some amount of the code - 
this won't reuse a lot because there're too many references to disk_dqblk structures and dependent constants).

Could you comment on the patch and the idea behind it in general?

Thank you.
Andrew.

---

 fs/Kconfig                 |    7
 fs/Makefile                |    1
 fs/ext3/super.c            |   12
 fs/quota_v1.c              |   50 ++-
 fs/quota_v2.c              |   45 +-
 fs/quota_v3.c              |  739 +++++++++++++++++++++++++++++++++++++++++++++
 fs/reiserfs/super.c        |    2
 include/linux/dqblk_v3.h   |   26 +
 include/linux/quota.h      |   23 -
 include/linux/quotaio_v3.h |   81 ++++
 10 files changed, 952 insertions(+), 34 deletions(-)

---

diff -rNpu quota.orig/fs/Kconfig quota/fs/Kconfig
--- quota.orig/fs/Kconfig	2008-01-24 14:33:56.000000000 +0300
+++ quota/fs/Kconfig	2008-02-27 16:49:56.108413855 +0300
@@ -488,6 +488,13 @@ config QFMT_V2
 	  This quota format allows using quotas with 32-bit UIDs/GIDs. If you
 	  need this functionality say Y here.
 
+config QFMT_V3
+	tristate "Quota format v3 support"
+	depends on QUOTA
+	help
+	  This quota format allows using quotas with 32-bit UIDs/GIDs and 64-bit
+	  limits. If you need this functionality say Y here.
+
 config QUOTACTL
 	bool
 	depends on XFS_QUOTA || QUOTA
diff -rNpu quota.orig/fs/Makefile quota/fs/Makefile
--- quota.orig/fs/Makefile	2008-01-24 14:33:54.000000000 +0300
+++ quota/fs/Makefile	2008-02-27 16:50:13.436477156 +0300
@@ -40,6 +40,7 @@ obj-$(CONFIG_GENERIC_ACL)	+= generic_acl
 obj-$(CONFIG_QUOTA)		+= dquot.o
 obj-$(CONFIG_QFMT_V1)		+= quota_v1.o
 obj-$(CONFIG_QFMT_V2)		+= quota_v2.o
+obj-$(CONFIG_QFMT_V3)		+= quota_v3.o
 obj-$(CONFIG_QUOTACTL)		+= quota.o
 
 obj-$(CONFIG_DMAPI)		+= dmapi/
diff -rNpu quota.orig/fs/quota_v1.c quota/fs/quota_v1.c
--- quota.orig/fs/quota_v1.c	2006-03-20 08:53:29.000000000 +0300
+++ quota/fs/quota_v1.c	2008-02-29 19:15:23.325159161 +0300
@@ -25,8 +25,16 @@ static void v1_disk2mem_dqblk(struct mem
 	m->dqb_btime = d->dqb_btime;
 }
 
-static void v1_mem2disk_dqblk(struct v1_disk_dqblk *d, struct mem_dqblk *m)
+static int v1_mem2disk_dqblk(struct v1_disk_dqblk *d, struct mem_dqblk *m)
 {
+	__u32 typelimit = ~((__u32)0);
+
+	if (m->dqb_ihardlimit > typelimit ||
+	    m->dqb_isoftlimit > typelimit ||
+	    m->dqb_bhardlimit > typelimit ||
+	    m->dqb_bsoftlimit > typelimit)
+		return -EINVAL;
+
 	d->dqb_ihardlimit = m->dqb_ihardlimit;
 	d->dqb_isoftlimit = m->dqb_isoftlimit;
 	d->dqb_curinodes = m->dqb_curinodes;
@@ -35,6 +43,8 @@ static void v1_mem2disk_dqblk(struct v1_
 	d->dqb_curblocks = toqb(m->dqb_curspace);
 	d->dqb_itime = m->dqb_itime;
 	d->dqb_btime = m->dqb_btime;
+
+	return 0;
 }
 
 static int v1_read_dqblk(struct dquot *dquot)
@@ -64,7 +74,10 @@ static int v1_commit_dqblk(struct dquot
 	ssize_t ret;
 	struct v1_disk_dqblk dqblk;
 
-	v1_mem2disk_dqblk(&dqblk, &dquot->dq_dqb);
+	ret = v1_mem2disk_dqblk(&dqblk, &dquot->dq_dqb);
+	if (ret < 0)
+		return ret;
+
 	if (dquot->dq_id == 0) {
 		dqblk.dqb_btime = sb_dqopt(dquot->dq_sb)->info[type].dqi_bgrace;
 		dqblk.dqb_itime = sb_dqopt(dquot->dq_sb)->info[type].dqi_igrace;
@@ -88,7 +101,7 @@ out:
 	return ret;
 }
 
-/* Magics of new quota format */
+/* Magics of vfsv0 quota format */
 #define V2_INITQMAGICS {\
 	0xd9c01f11,     /* USRQUOTA */\
 	0xd9c01927      /* GRPQUOTA */\
@@ -100,15 +113,29 @@ struct v2_disk_dqheader {
 	__le32 dqh_version;      /* File version */
 };
 
+/* Magics of vfsv1 quota format */
+#define V3_INITQMAGICS {\
+	0xd9c01f11,     /* USRQUOTA */\
+	0xd9c01927      /* GRPQUOTA */\
+}
+
+/* Header of new quota format */
+struct v3_disk_dqheader {
+	__le32 dqh_magic;        /* Magic number identifying file */
+	__le32 dqh_version;      /* File version */
+};
+
 static int v1_check_quota_file(struct super_block *sb, int type)
 {
 	struct inode *inode = sb_dqopt(sb)->files[type];
 	ulong blocks;
 	size_t off; 
-	struct v2_disk_dqheader dqhead;
-	ssize_t size;
+	struct v2_disk_dqheader dqhead_v2;
+	struct v3_disk_dqheader dqhead_v3;
+	ssize_t size_v2, size_v3;
 	loff_t isize;
-	static const uint quota_magics[] = V2_INITQMAGICS;
+	static const uint quota_magics_v2[] = V2_INITQMAGICS,
+			  quota_magics_v3[] = V3_INITQMAGICS;
 
 	isize = i_size_read(inode);
 	if (!isize)
@@ -118,10 +145,15 @@ static int v1_check_quota_file(struct su
 	if ((blocks % sizeof(struct v1_disk_dqblk) * BLOCK_SIZE + off) % sizeof(struct v1_disk_dqblk))
 		return 0;
 	/* Doublecheck whether we didn't get file with new format - with old quotactl() this could happen */
-	size = sb->s_op->quota_read(sb, type, (char *)&dqhead, sizeof(struct v2_disk_dqheader), 0);
-	if (size != sizeof(struct v2_disk_dqheader))
+	size_v2 = sb->s_op->quota_read(sb, type, (char *)&dqhead_v2,
+				       sizeof(struct v2_disk_dqheader), 0);
+	size_v3 = sb->s_op->quota_read(sb, type, (char *)&dqhead_v3,
+				       sizeof(struct v3_disk_dqheader), 0);
+	if (size_v2 != sizeof(struct v2_disk_dqheader) &&
+	    size_v3 != sizeof(struct v3_disk_dqheader))
 		return 1;	/* Probably not new format */
-	if (le32_to_cpu(dqhead.dqh_magic) != quota_magics[type])
+	if (le32_to_cpu(dqhead_v2.dqh_magic) != quota_magics_v2[type] &&
+	    le32_to_cpu(dqhead_v3.dqh_magic) != quota_magics_v3[type])
 		return 1;	/* Definitely not new format */
 	printk(KERN_INFO "VFS: %s: Refusing to turn on old quota format on given file. It probably contains newer quota format.\n", sb->s_id);
         return 0;		/* Seems like a new format file -> refuse it */
diff -rNpu quota.orig/fs/quota_v2.c quota/fs/quota_v2.c
--- quota.orig/fs/quota_v2.c	2006-03-20 08:53:29.000000000 +0300
+++ quota/fs/quota_v2.c	2008-02-28 10:37:31.067602129 +0300
@@ -106,17 +106,27 @@ static void disk2memdqb(struct mem_dqblk
 	m->dqb_btime = le64_to_cpu(d->dqb_btime);
 }
 
-static void mem2diskdqb(struct v2_disk_dqblk *d, struct mem_dqblk *m, qid_t id)
+static int mem2diskdqb(struct v2_disk_dqblk *d, struct mem_dqblk *m, qid_t id)
 {
-	d->dqb_ihardlimit = cpu_to_le32(m->dqb_ihardlimit);
-	d->dqb_isoftlimit = cpu_to_le32(m->dqb_isoftlimit);
-	d->dqb_curinodes = cpu_to_le32(m->dqb_curinodes);
+	__u32 typelimit = ~((__u32)0);
+
+	if (m->dqb_ihardlimit > typelimit ||
+	    m->dqb_isoftlimit > typelimit ||
+	    m->dqb_bhardlimit > typelimit ||
+	    m->dqb_bsoftlimit > typelimit)
+		return -EINVAL;
+
+	d->dqb_ihardlimit = cpu_to_le32((__u32)m->dqb_ihardlimit);
+	d->dqb_isoftlimit = cpu_to_le32((__u32)m->dqb_isoftlimit);
+	d->dqb_curinodes = cpu_to_le32((__u32)m->dqb_curinodes);
 	d->dqb_itime = cpu_to_le64(m->dqb_itime);
-	d->dqb_bhardlimit = cpu_to_le32(m->dqb_bhardlimit);
-	d->dqb_bsoftlimit = cpu_to_le32(m->dqb_bsoftlimit);
+	d->dqb_bhardlimit = cpu_to_le32((__u32)m->dqb_bhardlimit);
+	d->dqb_bsoftlimit = cpu_to_le32((__u32)m->dqb_bsoftlimit);
 	d->dqb_curspace = cpu_to_le64(m->dqb_curspace);
 	d->dqb_btime = cpu_to_le64(m->dqb_btime);
 	d->dqb_id = cpu_to_le32(id);
+
+	return 0;
 }
 
 static dqbuf_t getdqbuf(void)
@@ -394,14 +404,12 @@ static int v2_write_dquot(struct dquot *
 	ssize_t ret;
 	struct v2_disk_dqblk ddquot, empty;
 
-	/* dq_off is guarded by dqio_sem */
-	if (!dquot->dq_off)
-		if ((ret = dq_insert_tree(dquot)) < 0) {
-			printk(KERN_ERR "VFS: Error %zd occurred while creating quota.\n", ret);
-			return ret;
-		}
 	spin_lock(&dq_data_lock);
-	mem2diskdqb(&ddquot, &dquot->dq_dqb, dquot->dq_id);
+	ret = mem2diskdqb(&ddquot, &dquot->dq_dqb, dquot->dq_id);
+	if (ret < 0) {
+		spin_unlock(&dq_data_lock);
+		return ret;
+	}
 	/* Argh... We may need to write structure full of zeroes but that would be
 	 * treated as an empty place by the rest of the code. Format change would
 	 * be definitely cleaner but the problems probably are not worth it */
@@ -409,6 +417,17 @@ static int v2_write_dquot(struct dquot *
 	if (!memcmp(&empty, &ddquot, sizeof(struct v2_disk_dqblk)))
 		ddquot.dqb_itime = cpu_to_le64(1);
 	spin_unlock(&dq_data_lock);
+
+	/* dq_off is guarded by dqio_sem */
+	if (!dquot->dq_off) {
+		ret = dq_insert_tree(dquot);
+		if (ret < 0) {
+			printk(KERN_ERR "VFS: Error %zd occurred "
+			       "while creating quota.\n", ret);
+			return ret;
+		}
+	}
+
 	ret = dquot->dq_sb->s_op->quota_write(dquot->dq_sb, type,
 	      (char *)&ddquot, sizeof(struct v2_disk_dqblk), dquot->dq_off);
 	if (ret != sizeof(struct v2_disk_dqblk)) {
diff -rNpu quota.orig/fs/quota_v3.c quota/fs/quota_v3.c
--- quota.orig/fs/quota_v3.c	1970-01-01 03:00:00.000000000 +0300
+++ quota/fs/quota_v3.c	2008-02-28 10:55:05.981528558 +0300
@@ -0,0 +1,739 @@
+/*
+ *	vfsv1 quota IO operations on file
+ *
+ *	adds support for quota limits above 4 TB
+ *
+ *	based on quota_v3.c by Jan Kara
+ */
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/dqblk_v3.h>
+#include <linux/quotaio_v3.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <asm/byteorder.h>
+
+MODULE_DESCRIPTION("Quota format v3 support");
+MODULE_LICENSE("GPL");
+
+#define __QUOTA_V3_PARANOIA
+
+typedef char *dqbuf_t;
+
+#define GETIDINDEX(id, depth) (((id) >> ((V3_DQTREEDEPTH-(depth)-1)*8)) & 0xff)
+#define GETENTRIES(buf) ((struct v3_disk_dqblk *) \
+			  (((char *)buf)+sizeof(struct v3_disk_dqdbheader)))
+
+/* Check whether given file is really vfsv1 quotafile */
+static int v3_check_quota_file(struct super_block *sb, int type)
+{
+	struct v3_disk_dqheader dqhead;
+	ssize_t size;
+	static const uint quota_magics[] = V3_INITQMAGICS;
+	static const uint quota_versions[] = V3_INITQVERSIONS;
+
+	size = sb->s_op->quota_read(sb, type, (char *)&dqhead,
+				    sizeof(struct v3_disk_dqheader), 0);
+	if (size != sizeof(struct v3_disk_dqheader)) {
+		printk(KERN_WARNING "quota_v3: failed read expected=%zd, "
+		       "got=%zd\n", sizeof(struct v3_disk_dqheader), size);
+		return 0;
+	}
+	if (le32_to_cpu(dqhead.dqh_magic) != quota_magics[type] ||
+	    le32_to_cpu(dqhead.dqh_version) != quota_versions[type])
+		return 0;
+	return 1;
+}
+
+/* Read information header from quota file */
+static int v3_read_file_info(struct super_block *sb, int type)
+{
+	struct v3_disk_dqinfo dinfo;
+	struct mem_dqinfo *info = sb_dqopt(sb)->info+type;
+	ssize_t size;
+
+	size = sb->s_op->quota_read(sb, type, (char *)&dinfo,
+	       sizeof(struct v3_disk_dqinfo), V3_DQINFOOFF);
+	if (size != sizeof(struct v3_disk_dqinfo)) {
+		printk(KERN_WARNING "Can't read info structure on device %s.\n",
+			sb->s_id);
+		return -1;
+	}
+	info->dqi_bgrace = le32_to_cpu(dinfo.dqi_bgrace);
+	info->dqi_igrace = le32_to_cpu(dinfo.dqi_igrace);
+	info->dqi_flags = le32_to_cpu(dinfo.dqi_flags);
+	info->u.v3_i.dqi_blocks = le32_to_cpu(dinfo.dqi_blocks);
+	info->u.v3_i.dqi_free_blk = le32_to_cpu(dinfo.dqi_free_blk);
+	info->u.v3_i.dqi_free_entry = le32_to_cpu(dinfo.dqi_free_entry);
+	return 0;
+}
+
+/* Write information header to quota file */
+static int v3_write_file_info(struct super_block *sb, int type)
+{
+	struct v3_disk_dqinfo dinfo;
+	struct mem_dqinfo *info = sb_dqopt(sb)->info+type;
+	ssize_t size;
+
+	spin_lock(&dq_data_lock);
+	info->dqi_flags &= ~DQF_INFO_DIRTY;
+	dinfo.dqi_bgrace = cpu_to_le32(info->dqi_bgrace);
+	dinfo.dqi_igrace = cpu_to_le32(info->dqi_igrace);
+	dinfo.dqi_flags = cpu_to_le32(info->dqi_flags & DQF_MASK);
+	spin_unlock(&dq_data_lock);
+	dinfo.dqi_blocks = cpu_to_le32(info->u.v3_i.dqi_blocks);
+	dinfo.dqi_free_blk = cpu_to_le32(info->u.v3_i.dqi_free_blk);
+	dinfo.dqi_free_entry = cpu_to_le32(info->u.v3_i.dqi_free_entry);
+	size = sb->s_op->quota_write(sb, type, (char *)&dinfo,
+	       sizeof(struct v3_disk_dqinfo), V3_DQINFOOFF);
+	if (size != sizeof(struct v3_disk_dqinfo)) {
+		printk(KERN_WARNING "Can't write info structure "
+			"on device %s.\n", sb->s_id);
+		return -1;
+	}
+	return 0;
+}
+
+static void disk2memdqb(struct mem_dqblk *m, struct v3_disk_dqblk *d)
+{
+	m->dqb_ihardlimit = le64_to_cpu(d->dqb_ihardlimit);
+	m->dqb_isoftlimit = le64_to_cpu(d->dqb_isoftlimit);
+	m->dqb_curinodes = le64_to_cpu(d->dqb_curinodes);
+	m->dqb_itime = le64_to_cpu(d->dqb_itime);
+	m->dqb_bhardlimit = le64_to_cpu(d->dqb_bhardlimit);
+	m->dqb_bsoftlimit = le64_to_cpu(d->dqb_bsoftlimit);
+	m->dqb_curspace = le64_to_cpu(d->dqb_curspace);
+	m->dqb_btime = le64_to_cpu(d->dqb_btime);
+}
+
+static void mem2diskdqb(struct v3_disk_dqblk *d, struct mem_dqblk *m, qid_t id)
+{
+	d->dqb_ihardlimit = cpu_to_le64(m->dqb_ihardlimit);
+	d->dqb_isoftlimit = cpu_to_le64(m->dqb_isoftlimit);
+	d->dqb_curinodes = cpu_to_le64(m->dqb_curinodes);
+	d->dqb_itime = cpu_to_le64(m->dqb_itime);
+	d->dqb_bhardlimit = cpu_to_le64(m->dqb_bhardlimit);
+	d->dqb_bsoftlimit = cpu_to_le64(m->dqb_bsoftlimit);
+	d->dqb_curspace = cpu_to_le64(m->dqb_curspace);
+	d->dqb_btime = cpu_to_le64(m->dqb_btime);
+	d->dqb_id = cpu_to_le32(id);
+}
+
+static dqbuf_t getdqbuf(void)
+{
+	dqbuf_t buf = kmalloc(V3_DQBLKSIZE, GFP_NOFS);
+	if (!buf)
+		printk(KERN_WARNING "Not enough memory for quota buffers.\n");
+	return buf;
+}
+
+static inline void freedqbuf(dqbuf_t buf)
+{
+	kfree(buf);
+}
+
+static inline ssize_t read_blk(struct super_block *sb, int type,
+				uint blk, dqbuf_t buf)
+{
+	memset(buf, 0, V3_DQBLKSIZE);
+	return sb->s_op->quota_read(sb, type, (char *)buf,
+	       V3_DQBLKSIZE, blk << V3_DQBLKSIZE_BITS);
+}
+
+static inline ssize_t write_blk(struct super_block *sb, int type,
+				 uint blk, dqbuf_t buf)
+{
+	return sb->s_op->quota_write(sb, type, (char *)buf,
+	       V3_DQBLKSIZE, blk << V3_DQBLKSIZE_BITS);
+}
+
+/* Remove empty block from list and return it */
+static int get_free_dqblk(struct super_block *sb, int type)
+{
+	dqbuf_t buf = getdqbuf();
+	struct mem_dqinfo *info = sb_dqinfo(sb, type);
+	struct v3_disk_dqdbheader *dh = (struct v3_disk_dqdbheader *)buf;
+	int ret, blk;
+
+	if (!buf)
+		return -ENOMEM;
+	if (info->u.v3_i.dqi_free_blk) {
+		blk = info->u.v3_i.dqi_free_blk;
+		ret = read_blk(sb, type, blk, buf);
+		if (ret < 0)
+			goto out_buf;
+		info->u.v3_i.dqi_free_blk = le32_to_cpu(dh->dqdh_next_free);
+	} else {
+		memset(buf, 0, V3_DQBLKSIZE);
+		/* Assure block allocation... */
+		ret = write_blk(sb, type, info->u.v3_i.dqi_blocks, buf);
+		if (ret < 0)
+			goto out_buf;
+		blk = info->u.v3_i.dqi_blocks++;
+	}
+	mark_info_dirty(sb, type);
+	ret = blk;
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Insert empty block to the list */
+static int put_free_dqblk(struct super_block *sb, int type,
+			   dqbuf_t buf, uint blk)
+{
+	struct mem_dqinfo *info = sb_dqinfo(sb, type);
+	struct v3_disk_dqdbheader *dh = (struct v3_disk_dqdbheader *)buf;
+	int err;
+
+	dh->dqdh_next_free = cpu_to_le32(info->u.v3_i.dqi_free_blk);
+	dh->dqdh_prev_free = cpu_to_le32(0);
+	dh->dqdh_entries = cpu_to_le16(0);
+	info->u.v3_i.dqi_free_blk = blk;
+	mark_info_dirty(sb, type);
+	/* Some strange block. We had better leave it... */
+	err = write_blk(sb, type, blk, buf);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/* Remove given block from the list of blocks with free entries */
+static int remove_free_dqentry(struct super_block *sb,
+				int type, dqbuf_t buf, uint blk)
+{
+	dqbuf_t tmpbuf = getdqbuf();
+	struct mem_dqinfo *info = sb_dqinfo(sb, type);
+	struct v3_disk_dqdbheader *dh = (struct v3_disk_dqdbheader *)buf;
+	uint nextblk = le32_to_cpu(dh->dqdh_next_free),
+	     prevblk = le32_to_cpu(dh->dqdh_prev_free);
+	int err;
+
+	if (!tmpbuf)
+		return -ENOMEM;
+	if (nextblk) {
+		err = read_blk(sb, type, nextblk, tmpbuf);
+		if (err < 0)
+			goto out_buf;
+		((struct v3_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = dh->dqdh_prev_free;
+		err = write_blk(sb, type, nextblk, tmpbuf);
+		if (err < 0)
+			goto out_buf;
+	}
+	if (prevblk) {
+		err = read_blk(sb, type, prevblk, tmpbuf);
+		if (err < 0)
+			goto out_buf;
+		((struct v3_disk_dqdbheader *)tmpbuf)->dqdh_next_free = dh->dqdh_next_free;
+		err = write_blk(sb, type, prevblk, tmpbuf);
+		if (err < 0)
+			goto out_buf;
+	} else {
+		info->u.v3_i.dqi_free_entry = nextblk;
+		mark_info_dirty(sb, type);
+	}
+	freedqbuf(tmpbuf);
+	dh->dqdh_next_free = dh->dqdh_prev_free = cpu_to_le32(0);
+	/* No matter whether write succeeds block is out of list */
+	if (write_blk(sb, type, blk, buf) < 0)
+		printk(KERN_ERR "VFS: Can't write block (%u) "
+			"with free entries.\n", blk);
+	return 0;
+out_buf:
+	freedqbuf(tmpbuf);
+	return err;
+}
+
+/* Insert given block to the beginning of list with free entries */
+static int insert_free_dqentry(struct super_block *sb,
+				int type, dqbuf_t buf, uint blk)
+{
+	dqbuf_t tmpbuf = getdqbuf();
+	struct mem_dqinfo *info = sb_dqinfo(sb, type);
+	struct v3_disk_dqdbheader *dh = (struct v3_disk_dqdbheader *)buf;
+	int err;
+
+	if (!tmpbuf)
+		return -ENOMEM;
+	dh->dqdh_next_free = cpu_to_le32(info->u.v3_i.dqi_free_entry);
+	dh->dqdh_prev_free = cpu_to_le32(0);
+	err = write_blk(sb, type, blk, buf);
+	if (err < 0)
+		goto out_buf;
+	if (info->u.v3_i.dqi_free_entry) {
+		err = read_blk(sb, type, info->u.v3_i.dqi_free_entry, tmpbuf);
+		if (err < 0)
+			goto out_buf;
+		((struct v3_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = cpu_to_le32(blk);
+		err = write_blk(sb, type, info->u.v3_i.dqi_free_entry, tmpbuf);
+		if (err < 0)
+			goto out_buf;
+	}
+	freedqbuf(tmpbuf);
+	info->u.v3_i.dqi_free_entry = blk;
+	mark_info_dirty(sb, type);
+	return 0;
+out_buf:
+	freedqbuf(tmpbuf);
+	return err;
+}
+
+/* Find space for dquot */
+static uint find_free_dqentry(struct dquot *dquot, int *err)
+{
+	struct super_block *sb = dquot->dq_sb;
+	struct mem_dqinfo *info = sb_dqopt(sb)->info+dquot->dq_type;
+	uint blk, i;
+	struct v3_disk_dqdbheader *dh;
+	struct v3_disk_dqblk *ddquot;
+	struct v3_disk_dqblk fakedquot;
+	dqbuf_t buf;
+
+	*err = 0;
+	buf = getdqbuf();
+	if (!buf) {
+		*err = -ENOMEM;
+		return 0;
+	}
+	dh = (struct v3_disk_dqdbheader *)buf;
+	ddquot = GETENTRIES(buf);
+	if (info->u.v3_i.dqi_free_entry) {
+		blk = info->u.v3_i.dqi_free_entry;
+		*err = read_blk(sb, dquot->dq_type, blk, buf);
+		if (*err < 0)
+			goto out_buf;
+	} else {
+		blk = get_free_dqblk(sb, dquot->dq_type);
+		if ((int)blk < 0) {
+			*err = blk;
+			freedqbuf(buf);
+			return 0;
+		}
+		memset(buf, 0, V3_DQBLKSIZE);
+		/* This is enough as block is already zeroed */
+		/* and entry list is empty...                */
+		info->u.v3_i.dqi_free_entry = blk;
+		mark_info_dirty(sb, dquot->dq_type);
+	}
+	if (le16_to_cpu(dh->dqdh_entries)+1 >= V3_DQSTRINBLK) {
+		*err = remove_free_dqentry(sb, dquot->dq_type, buf, blk);
+		if (*err < 0) {
+			printk(KERN_ERR "VFS: find_free_dqentry(): Can't "
+			       "remove block (%u) from entry free list.\n",
+			       blk);
+			goto out_buf;
+		}
+	}
+	dh->dqdh_entries = cpu_to_le16(le16_to_cpu(dh->dqdh_entries)+1);
+	memset(&fakedquot, 0, sizeof(struct v3_disk_dqblk));
+	/* Find free structure in block */
+	for (i = 0; i < V3_DQSTRINBLK && memcmp(&fakedquot, ddquot+i,
+		    sizeof(struct v3_disk_dqblk)); i++);
+#ifdef __QUOTA_V3_PARANOIA
+	if (i == V3_DQSTRINBLK) {
+		printk(KERN_ERR "VFS: find_free_dqentry(): "
+		       "Data block full but it shouldn't.\n");
+		*err = -EIO;
+		goto out_buf;
+	}
+#endif
+	*err = write_blk(sb, dquot->dq_type, blk, buf);
+	if (*err < 0) {
+		printk(KERN_ERR "VFS: find_free_dqentry(): "
+		       "Can't write quota data block %u.\n", blk);
+		goto out_buf;
+	}
+	dquot->dq_off = (blk<<V3_DQBLKSIZE_BITS)+
+			sizeof(struct v3_disk_dqdbheader)+
+			i*sizeof(struct v3_disk_dqblk);
+	freedqbuf(buf);
+	return blk;
+out_buf:
+	freedqbuf(buf);
+	return 0;
+}
+
+/* Insert reference to structure into the trie */
+static int do_insert_tree(struct dquot *dquot, uint *treeblk, int depth)
+{
+	struct super_block *sb = dquot->dq_sb;
+	dqbuf_t buf;
+	int ret = 0, newson = 0, newact = 0;
+	__le32 *ref;
+	uint newblk;
+
+	buf = getdqbuf();
+	if (!buf)
+		return -ENOMEM;
+	if (!*treeblk) {
+		ret = get_free_dqblk(sb, dquot->dq_type);
+		if (ret < 0)
+			goto out_buf;
+		*treeblk = ret;
+		memset(buf, 0, V3_DQBLKSIZE);
+		newact = 1;
+	} else {
+		ret = read_blk(sb, dquot->dq_type, *treeblk, buf);
+		if (ret < 0) {
+			printk(KERN_ERR "VFS: Can't read tree quota "
+				"block %u.\n", *treeblk);
+			goto out_buf;
+		}
+	}
+	ref = (__le32 *)buf;
+	newblk = le32_to_cpu(ref[GETIDINDEX(dquot->dq_id, depth)]);
+	if (!newblk)
+		newson = 1;
+	if (depth == V3_DQTREEDEPTH-1) {
+#ifdef __QUOTA_V3_PARANOIA
+		if (newblk) {
+			printk(KERN_ERR "VFS: Inserting already present "
+				"quota entry (block %u).\n",
+			le32_to_cpu(ref[GETIDINDEX(dquot->dq_id, depth)]));
+			ret = -EIO;
+			goto out_buf;
+		}
+#endif
+		newblk = find_free_dqentry(dquot, &ret);
+	} else
+		ret = do_insert_tree(dquot, &newblk, depth+1);
+	if (newson && ret >= 0) {
+		ref[GETIDINDEX(dquot->dq_id, depth)] = cpu_to_le32(newblk);
+		ret = write_blk(sb, dquot->dq_type, *treeblk, buf);
+	} else if (newact && ret < 0)
+		put_free_dqblk(sb, dquot->dq_type, buf, *treeblk);
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Wrapper for inserting quota structure into tree */
+static inline int dq_insert_tree(struct dquot *dquot)
+{
+	int tmp = V3_DQTREEOFF;
+	return do_insert_tree(dquot, &tmp, 0);
+}
+
+/*
+ *	We don't have to be afraid of deadlocks
+ *	as we never have quotas on quota files...
+ */
+static int v3_write_dquot(struct dquot *dquot)
+{
+	int type = dquot->dq_type;
+	ssize_t ret;
+	struct v3_disk_dqblk ddquot, empty;
+
+	/* dq_off is guarded by dqio_sem */
+	if (!dquot->dq_off) {
+		ret = dq_insert_tree(dquot);
+		if (ret < 0) {
+			printk(KERN_ERR "VFS: Error %zd occurred "
+				"while creating quota.\n", ret);
+			return ret;
+		}
+	}
+	spin_lock(&dq_data_lock);
+	mem2diskdqb(&ddquot, &dquot->dq_dqb, dquot->dq_id);
+	/* Argh... We may need to write structure full of zeroes but that would
+	 * be treated as an empty place by the rest of the code. Format change
+	 * would be definitely cleaner but the problems are not worth it */
+	memset(&empty, 0, sizeof(struct v3_disk_dqblk));
+	if (!memcmp(&empty, &ddquot, sizeof(struct v3_disk_dqblk)))
+		ddquot.dqb_itime = cpu_to_le64(1);
+	spin_unlock(&dq_data_lock);
+	ret = dquot->dq_sb->s_op->quota_write(dquot->dq_sb, type,
+	      (char *)&ddquot, sizeof(struct v3_disk_dqblk), dquot->dq_off);
+	if (ret != sizeof(struct v3_disk_dqblk)) {
+		printk(KERN_WARNING "VFS: dquota write failed on dev %s\n",
+			dquot->dq_sb->s_id);
+		if (ret >= 0)
+			ret = -ENOSPC;
+	} else
+		ret = 0;
+	dqstats.writes++;
+
+	return ret;
+}
+
+/* Free dquot entry in data block */
+static int free_dqentry(struct dquot *dquot, uint blk)
+{
+	struct super_block *sb = dquot->dq_sb;
+	int type = dquot->dq_type;
+	struct v3_disk_dqdbheader *dh;
+	dqbuf_t buf = getdqbuf();
+	int ret = 0;
+
+	if (!buf)
+		return -ENOMEM;
+	if (dquot->dq_off >> V3_DQBLKSIZE_BITS != blk) {
+		printk(KERN_ERR "VFS: Quota structure has offset to other "
+		  "block (%u) than it should (%u).\n", blk,
+		  (uint)(dquot->dq_off >> V3_DQBLKSIZE_BITS));
+		goto out_buf;
+	}
+	ret = read_blk(sb, type, blk, buf);
+	if (ret < 0) {
+		printk(KERN_ERR "VFS: Can't read quota data block %u\n", blk);
+		goto out_buf;
+	}
+	dh = (struct v3_disk_dqdbheader *)buf;
+	dh->dqdh_entries = cpu_to_le16(le16_to_cpu(dh->dqdh_entries)-1);
+	if (!le16_to_cpu(dh->dqdh_entries)) {
+		ret = remove_free_dqentry(sb, type, buf, blk);
+		if (ret < 0 ||
+		    (ret = put_free_dqblk(sb, type, buf, blk)) < 0) {
+			printk(KERN_ERR "VFS: Can't move quota data block (%u) "
+			  "to free list.\n", blk);
+			goto out_buf;
+		}
+	} else {
+		memset(buf+(dquot->dq_off & ((1 << V3_DQBLKSIZE_BITS)-1)), 0,
+		  sizeof(struct v3_disk_dqblk));
+		if (le16_to_cpu(dh->dqdh_entries) == V3_DQSTRINBLK-1) {
+			/* Insert will write block itself */
+			ret = insert_free_dqentry(sb, type, buf, blk);
+			if (ret < 0) {
+				printk(KERN_ERR "VFS: Can't insert quota data "
+				       "block (%u) to free entry list.\n", blk);
+				goto out_buf;
+			}
+		} else
+			ret = write_blk(sb, type, blk, buf);
+			if (ret < 0) {
+				printk(KERN_ERR "VFS: Can't write quota data "
+				  "block %u\n", blk);
+				goto out_buf;
+			}
+	}
+	dquot->dq_off = 0;	/* Quota is now unattached */
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Remove reference to dquot from tree */
+static int remove_tree(struct dquot *dquot, uint *blk, int depth)
+{
+	struct super_block *sb = dquot->dq_sb;
+	int type = dquot->dq_type;
+	dqbuf_t buf = getdqbuf();
+	int ret = 0;
+	uint newblk;
+	__le32 *ref = (__le32 *)buf;
+
+	if (!buf)
+		return -ENOMEM;
+	ret = read_blk(sb, type, *blk, buf);
+	if (ret < 0) {
+		printk(KERN_ERR "VFS: Can't read quota data block %u\n", *blk);
+		goto out_buf;
+	}
+	newblk = le32_to_cpu(ref[GETIDINDEX(dquot->dq_id, depth)]);
+	if (depth == V3_DQTREEDEPTH-1) {
+		ret = free_dqentry(dquot, newblk);
+		newblk = 0;
+	} else
+		ret = remove_tree(dquot, &newblk, depth+1);
+	if (ret >= 0 && !newblk) {
+		int i;
+		ref[GETIDINDEX(dquot->dq_id, depth)] = cpu_to_le32(0);
+		for (i = 0; i < V3_DQBLKSIZE && !buf[i]; i++);
+		/* Don't put the root block into the free block list */
+		if (i == V3_DQBLKSIZE && *blk != V3_DQTREEOFF) {
+			put_free_dqblk(sb, type, buf, *blk);
+			*blk = 0;
+		} else {
+			ret = write_blk(sb, type, *blk, buf);
+			if (ret < 0)
+				printk(KERN_ERR "VFS: Can't write quota tree "
+				  "block %u.\n", *blk);
+		}
+	}
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Delete dquot from tree */
+static int v3_delete_dquot(struct dquot *dquot)
+{
+	uint tmp = V3_DQTREEOFF;
+
+	if (!dquot->dq_off)	/* Even not allocated? */
+		return 0;
+	return remove_tree(dquot, &tmp, 0);
+}
+
+/* Find entry in block */
+static loff_t find_block_dqentry(struct dquot *dquot, uint blk)
+{
+	dqbuf_t buf = getdqbuf();
+	loff_t ret = 0;
+	int i;
+	struct v3_disk_dqblk *ddquot = GETENTRIES(buf);
+
+	if (!buf)
+		return -ENOMEM;
+	ret = read_blk(dquot->dq_sb, dquot->dq_type, blk, buf);
+	if (ret < 0) {
+		printk(KERN_ERR "VFS: Can't read quota tree block %u.\n", blk);
+		goto out_buf;
+	}
+	if (dquot->dq_id)
+		for (i = 0; i < V3_DQSTRINBLK &&
+		     le32_to_cpu(ddquot[i].dqb_id) != dquot->dq_id; i++);
+	else {	/* ID 0 as a bit more complicated searching... */
+		struct v3_disk_dqblk fakedquot;
+
+		memset(&fakedquot, 0, sizeof(struct v3_disk_dqblk));
+		for (i = 0; i < V3_DQSTRINBLK; i++)
+			if (!le32_to_cpu(ddquot[i].dqb_id) &&
+			    memcmp(&fakedquot, ddquot+i,
+				   sizeof(struct v3_disk_dqblk)))
+				break;
+	}
+	if (i == V3_DQSTRINBLK) {
+		printk(KERN_ERR "VFS: Quota for id %u referenced "
+		  "but not present.\n", dquot->dq_id);
+		ret = -EIO;
+		goto out_buf;
+	} else
+		ret = (blk << V3_DQBLKSIZE_BITS) + sizeof(struct
+		  v3_disk_dqdbheader) + i * sizeof(struct v3_disk_dqblk);
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Find entry for given id in the tree */
+static loff_t find_tree_dqentry(struct dquot *dquot, uint blk, int depth)
+{
+	dqbuf_t buf = getdqbuf();
+	loff_t ret = 0;
+	__le32 *ref = (__le32 *)buf;
+
+	if (!buf)
+		return -ENOMEM;
+	ret = read_blk(dquot->dq_sb, dquot->dq_type, blk, buf);
+	if (ret < 0) {
+		printk(KERN_ERR "VFS: Can't read quota tree block %u.\n", blk);
+		goto out_buf;
+	}
+	ret = 0;
+	blk = le32_to_cpu(ref[GETIDINDEX(dquot->dq_id, depth)]);
+	if (!blk)	/* No reference? */
+		goto out_buf;
+	if (depth < V3_DQTREEDEPTH-1)
+		ret = find_tree_dqentry(dquot, blk, depth+1);
+	else
+		ret = find_block_dqentry(dquot, blk);
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Find entry for given id in the tree - wrapper function */
+static inline loff_t find_dqentry(struct dquot *dquot)
+{
+	return find_tree_dqentry(dquot, V3_DQTREEOFF, 0);
+}
+
+static int v3_read_dquot(struct dquot *dquot)
+{
+	int type = dquot->dq_type;
+	loff_t offset;
+	struct v3_disk_dqblk ddquot, empty;
+	int ret = 0;
+
+#ifdef __QUOTA_V3_PARANOIA
+	/* Invalidated quota? */
+	if (!dquot->dq_sb || !sb_dqopt(dquot->dq_sb)->files[type]) {
+		printk(KERN_ERR "VFS: Quota invalidated while reading!\n");
+		return -EIO;
+	}
+#endif
+	offset = find_dqentry(dquot);
+	if (offset <= 0) {
+		if (offset < 0)
+			printk(KERN_ERR "VFS: Can't read quota "
+			  "structure for id %u.\n", dquot->dq_id);
+		dquot->dq_off = 0;
+		set_bit(DQ_FAKE_B, &dquot->dq_flags);
+		memset(&dquot->dq_dqb, 0, sizeof(struct mem_dqblk));
+		ret = offset;
+	} else {
+		dquot->dq_off = offset;
+		ret = dquot->dq_sb->s_op->quota_read(dquot->dq_sb, type,
+		      (char *)&ddquot, sizeof(struct v3_disk_dqblk), offset);
+		if (ret != sizeof(struct v3_disk_dqblk)) {
+			if (ret >= 0)
+				ret = -EIO;
+			printk(KERN_ERR "VFS: Error while reading quota "
+			  "structure for id %u.\n", dquot->dq_id);
+			memset(&ddquot, 0, sizeof(struct v3_disk_dqblk));
+		} else {
+			ret = 0;
+			/* We need to escape back all-zero structure */
+			memset(&empty, 0, sizeof(struct v3_disk_dqblk));
+			empty.dqb_itime = cpu_to_le64(1);
+			if (!memcmp(&empty, &ddquot,
+			    sizeof(struct v3_disk_dqblk)))
+				ddquot.dqb_itime = 0;
+		}
+		disk2memdqb(&dquot->dq_dqb, &ddquot);
+		if (!dquot->dq_dqb.dqb_bhardlimit &&
+			!dquot->dq_dqb.dqb_bsoftlimit &&
+			!dquot->dq_dqb.dqb_ihardlimit &&
+			!dquot->dq_dqb.dqb_isoftlimit)
+			set_bit(DQ_FAKE_B, &dquot->dq_flags);
+	}
+	dqstats.reads++;
+
+	return ret;
+}
+
+/* Check whether dquot should not be deleted. We know we are
+ * the only one operating on dquot (thanks to dq_lock) */
+static int v3_release_dquot(struct dquot *dquot)
+{
+	if (test_bit(DQ_FAKE_B, &dquot->dq_flags) &&
+	    !(dquot->dq_dqb.dqb_curinodes | dquot->dq_dqb.dqb_curspace))
+		return v3_delete_dquot(dquot);
+	return 0;
+}
+
+static struct quota_format_ops v3_format_ops = {
+	.check_quota_file	= v3_check_quota_file,
+	.read_file_info		= v3_read_file_info,
+	.write_file_info	= v3_write_file_info,
+	.free_file_info		= NULL,
+	.read_dqblk		= v3_read_dquot,
+	.commit_dqblk		= v3_write_dquot,
+	.release_dqblk		= v3_release_dquot,
+};
+
+static struct quota_format_type v3_quota_format = {
+	.qf_fmt_id	= QFMT_VFS_V1,
+	.qf_ops		= &v3_format_ops,
+	.qf_owner	= THIS_MODULE
+};
+
+static int __init init_v3_quota_format(void)
+{
+	return register_quota_format(&v3_quota_format);
+}
+
+static void __exit exit_v3_quota_format(void)
+{
+	unregister_quota_format(&v3_quota_format);
+}
+
+module_init(init_v3_quota_format);
+module_exit(exit_v3_quota_format);
diff -rNpu quota.orig/include/linux/dqblk_v3.h quota/include/linux/dqblk_v3.h
--- quota.orig/include/linux/dqblk_v3.h	1970-01-01 03:00:00.000000000 +0300
+++ quota/include/linux/dqblk_v3.h	2008-02-27 16:31:45.964283988 +0300
@@ -0,0 +1,26 @@
+/*
+ *	Definitions of structures for vfsv1 quota format
+ */
+
+#ifndef _LINUX_DQBLK_V3_H
+#define _LINUX_DQBLK_V3_H
+
+#include <linux/types.h>
+
+/* id numbers of quota format */
+#define QFMT_VFS_V1 3
+
+/* Numbers of blocks needed for updates */
+#define V3_INIT_ALLOC 4
+#define V3_INIT_REWRITE 2
+#define V3_DEL_ALLOC 0
+#define V3_DEL_REWRITE 6
+
+/* Inmemory copy of version specific information */
+struct v3_mem_dqinfo {
+	unsigned int dqi_blocks;
+	unsigned int dqi_free_blk;
+	unsigned int dqi_free_entry;
+};
+
+#endif /* _LINUX_DQBLK_V3_H */
diff -rNpu quota.orig/include/linux/quota.h quota/include/linux/quota.h
--- quota.orig/include/linux/quota.h	2006-03-20 08:53:29.000000000 +0300
+++ quota/include/linux/quota.h	2008-02-27 16:30:15.306620367 +0300
@@ -136,24 +136,27 @@ struct if_dqinfo {
 #include <linux/dqblk_xfs.h>
 #include <linux/dqblk_v1.h>
 #include <linux/dqblk_v2.h>
+#include <linux/dqblk_v3.h>
 
 /* Maximal numbers of writes for quota operation (insert/delete/update)
  * (over VFS all formats) */
-#define DQUOT_INIT_ALLOC max(V1_INIT_ALLOC, V2_INIT_ALLOC)
-#define DQUOT_INIT_REWRITE max(V1_INIT_REWRITE, V2_INIT_REWRITE)
-#define DQUOT_DEL_ALLOC max(V1_DEL_ALLOC, V2_DEL_ALLOC)
-#define DQUOT_DEL_REWRITE max(V1_DEL_REWRITE, V2_DEL_REWRITE)
+#define DQUOT_INIT_ALLOC max(V1_INIT_ALLOC, max(V2_INIT_ALLOC, V3_INIT_ALLOC))
+#define DQUOT_INIT_REWRITE max(max(V2_INIT_REWRITE, V3_INIT_REWRITE),\
+				V1_INIT_REWRITE)
+#define DQUOT_DEL_ALLOC max(V1_DEL_ALLOC, max(V2_DEL_ALLOC, V3_DEL_ALLOC))
+#define DQUOT_DEL_REWRITE max(max(V2_DEL_REWRITE, V3_DEL_REWRITE),\
+				V1_DEL_REWRITE)
 
 /*
  * Data for one user/group kept in memory
  */
 struct mem_dqblk {
-	__u32 dqb_bhardlimit;	/* absolute limit on disk blks alloc */
-	__u32 dqb_bsoftlimit;	/* preferred limit on disk blks */
+	qsize_t dqb_bhardlimit;	/* absolute limit on disk blks alloc */
+	qsize_t dqb_bsoftlimit;	/* preferred limit on disk blks */
 	qsize_t dqb_curspace;	/* current used space */
-	__u32 dqb_ihardlimit;	/* absolute limit on allocated inodes */
-	__u32 dqb_isoftlimit;	/* preferred inode limit */
-	__u32 dqb_curinodes;	/* current # allocated inodes */
+	qsize_t dqb_ihardlimit;	/* absolute limit on allocated inodes */
+	qsize_t dqb_isoftlimit;	/* preferred inode limit */
+	qsize_t dqb_curinodes;	/* current # allocated inodes */
 	time_t dqb_btime;	/* time limit for excessive disk use */
 	time_t dqb_itime;	/* time limit for excessive inode use */
 };
@@ -172,6 +173,7 @@ struct mem_dqinfo {
 	union {
 		struct v1_mem_dqinfo v1_i;
 		struct v2_mem_dqinfo v2_i;
+		struct v3_mem_dqinfo v3_i;
 	} u;
 };
 
@@ -315,6 +317,7 @@ struct quota_module_name {
 #define INIT_QUOTA_MODULE_NAMES {\
 	{QFMT_VFS_OLD, "quota_v1"},\
 	{QFMT_VFS_V0, "quota_v2"},\
+	{QFMT_VFS_V1, "quota_v3"},\
 	{0, NULL}}
 
 #else
diff -rNpu quota.orig/include/linux/quotaio_v3.h quota/include/linux/quotaio_v3.h
--- quota.orig/include/linux/quotaio_v3.h	1970-01-01 03:00:00.000000000 +0300
+++ quota/include/linux/quotaio_v3.h	2008-02-29 19:16:26.281092724 +0300
@@ -0,0 +1,81 @@
+/*
+ *	Definitions of structures for vfsv1quota format
+ */
+
+#ifndef _LINUX_QUOTAIO_V3_H
+#define _LINUX_QUOTAIO_V3_H
+
+#include <linux/types.h>
+#include <linux/quota.h>
+
+/*
+ * Definitions of magics and versions of current quota files
+ */
+#define V3_INITQMAGICS {\
+	0xd9c01f11,	/* USRQUOTA */\
+	0xd9c01927	/* GRPQUOTA */\
+}
+
+#define V3_INITQVERSIONS {\
+	1,		/* USRQUOTA */\
+	1		/* GRPQUOTA */\
+}
+
+/*
+ * The following structure defines the format of the disk quota file
+ * (as it appears on disk) - the file is a radix tree whose leaves point
+ * to blocks of these structures.
+ */
+struct v3_disk_dqblk {
+	__le32 dqb_id;		/* id this quota applies to */
+	__le32 dqb_padding;	/* padding field */
+	__le64 dqb_ihardlimit;	/* absolute limit on allocated inodes */
+	__le64 dqb_isoftlimit;	/* preferred inode limit */
+	__le64 dqb_curinodes;	/* current # allocated inodes */
+	__le64 dqb_bhardlimit;	/* absolute limit on disk space (in blocks) */
+	__le64 dqb_bsoftlimit;	/* preferred limit on disk space (in blocks) */
+	__le64 dqb_curspace;	/* current space occupied (in bytes) */
+	__le64 dqb_btime;	/* time limit for excessive disk use */
+	__le64 dqb_itime;	/* time limit for excessive inode use */
+};
+
+/*
+ * Here are header structures as written on disk and their in-memory copies
+ */
+/* First generic header */
+struct v3_disk_dqheader {
+	__le32 dqh_magic;	/* Magic number identifying file */
+	__le32 dqh_version;	/* File version */
+};
+
+/* Header with type and version specific information */
+struct v3_disk_dqinfo {
+	__le32 dqi_bgrace;	/* Time before block soft limit becomes hard */
+	__le32 dqi_igrace;	/* Time before inode soft limit becomes hard */
+	__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 a free entry */
+};
+
+/*
+ *  Structure of header of block with quota structures. It is padded to
+ *  16 bytes so there will be space for exactly 21 quota-entries in a block
+ */
+struct v3_disk_dqdbheader {
+	__le32 dqdh_next_free;	/* Number of next block with free entry */
+	__le32 dqdh_prev_free;	/* Number of previous block with free entry */
+	__le16 dqdh_entries;	/* Number of valid entries in block */
+	__le16 dqdh_pad1;
+	__le32 dqdh_pad2;
+};
+
+#define V3_DQINFOOFF	sizeof(struct v3_disk_dqheader)
+#define V3_DQBLKSIZE_BITS	10
+#define V3_DQBLKSIZE	(1 << V3_DQBLKSIZE_BITS)
+#define V3_DQTREEOFF	1		/* Offset of tree in file in blocks */
+#define V3_DQTREEDEPTH	4		/* Depth of quota tree */
+#define V3_DQSTRINBLK	((V3_DQBLKSIZE - sizeof(struct v3_disk_dqdbheader)) / \
+			sizeof(struct v3_disk_dqblk))
+
+#endif /* _LINUX_QUOTAIO_V3_H */
diff -rNpu quota.orig/fs/ext3/super.c quota/fs/ext3/super.c
--- quota.orig/fs/ext3/super.c	2008-01-24 14:33:52.000000000 +0300
+++ quota/fs/ext3/super.c	2008-03-01 17:50:26.000000000 +0300
@@ -522,7 +522,8 @@ static inline void ext3_show_quota_optio
 
 	if (sbi->s_jquota_fmt)
 		seq_printf(seq, ",jqfmt=%s",
-		(sbi->s_jquota_fmt == QFMT_VFS_OLD) ? "vfsold": "vfsv0");
+		(sbi->s_jquota_fmt == QFMT_VFS_OLD) ? "vfsold":
+		((sbi->s_jquota_fmt == QFMT_VFS_V0) ? "vfsv0" : "vfsv1"));
 
 	if (sbi->s_qf_names[USRQUOTA])
 		seq_printf(seq, ",usrjquota=%s", sbi->s_qf_names[USRQUOTA]);
@@ -673,7 +674,7 @@ enum {
 	Opt_commit, Opt_journal_update, Opt_journal_inum, Opt_journal_dev,
 	Opt_abort, Opt_data_journal, Opt_data_ordered, Opt_data_writeback,
 	Opt_usrjquota, Opt_grpjquota, Opt_offusrjquota, Opt_offgrpjquota,
-	Opt_jqfmt_vfsold, Opt_jqfmt_vfsv0, Opt_quota, Opt_noquota,
+	Opt_jqfmt_vfsold, Opt_jqfmt_vfsv0, Opt_jqfmt_vfsv1, Opt_quota,
-	Opt_ignore, Opt_barrier, Opt_err, Opt_resize, Opt_usrquota,
+	Opt_ignore, Opt_barrier, Opt_err, Opt_resize, Opt_usrquota, Opt_noquota,
 	Opt_grpquota
 };
@@ -719,6 +720,7 @@ static match_table_t tokens = {
 	{Opt_grpjquota, "grpjquota=%s"},
 	{Opt_jqfmt_vfsold, "jqfmt=vfsold"},
 	{Opt_jqfmt_vfsv0, "jqfmt=vfsv0"},
+	{Opt_jqfmt_vfsv1, "jqfmt=vfsv1"},
 	{Opt_grpquota, "grpquota"},
 	{Opt_noquota, "noquota"},
 	{Opt_quota, "quota"},
@@ -990,6 +992,9 @@ clear_qf_name:
 		case Opt_jqfmt_vfsv0:
 			sbi->s_jquota_fmt = QFMT_VFS_V0;
 			break;
+		case Opt_jqfmt_vfsv1:
+			sbi->s_jquota_fmt = QFMT_VFS_V1;
+			break;
 		case Opt_quota:
 		case Opt_usrquota:
 			set_opt(sbi->s_mount_opt, QUOTA);
@@ -1019,6 +1024,7 @@ clear_qf_name:
 		case Opt_offgrpjquota:
 		case Opt_jqfmt_vfsold:
 		case Opt_jqfmt_vfsv0:
+		case Opt_jqfmt_vfsv1:
 			printk(KERN_ERR
 				"EXT3-fs: journalled quota options not "
 				"supported.\n");
diff -rNpu quota.orig/fs/reiserfs/super.c quota/fs/reiserfs/super.c
--- quota.orig/fs/reiserfs/super.c	2008-01-24 14:33:52.000000000 +0300
+++ quota/fs/reiserfs/super.c	2008-03-01 17:51:12.000000000 +0300
@@ -1021,6 +1021,8 @@ static int reiserfs_parse_options(struct
 				REISERFS_SB(s)->s_jquota_fmt = QFMT_VFS_OLD;
 			else if (!strcmp(arg, "vfsv0"))
 				REISERFS_SB(s)->s_jquota_fmt = QFMT_VFS_V0;
+			else if (!strcmp(arg, "vfsv1"))
+				REISERFS_SB(s)->s_jquota_fmt = QFMT_VFS_V1;
 			else {
 				reiserfs_warning(s,
 						 "reiserfs_parse_options: unknown quota format specified.");

Signed-off-by: Andrew Perepechko <andrew.perepechko@xxxxxxx>
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux