Re: [RFC] quota: 64-bit limits with vfs

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

 



  Hello,

  Sorry for not responding for a few days. I was busy with other things.

On Thu 06-03-08 16:41:11, Andrew Perepechko wrote:
> 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?
  Just from a quick look. There seem to be actually two separate changes:
1) Change current formats so that they refuse to set quota above treshold they
are able to handle. That's fine a we should do that (in a separate patch,
please).

2) Implement new format able to handle more that 4TB limits. In principle,
that is fine but vfsv0 format has actually been designed so that similar
changes can go mostly invisible for userspace (modulo some tools updates
etc.). Given that the format itself doesn't change that much, we definitely
do not need to introduce completely new quota format. I'd just increase the
version number. Also I'd like to avoid unnecessary code duplication. The
only thing that is really different are just the conversion routines from
disk to memory. So I'd just modify the code in fs/quota_v2.c so that it
supports both versions of the quota format - you need to parametrize macros
like GETENTRIES(), V2_DQSTRINBLK, ... (actually make inline functions of
them when we are changing it), probably make union of struct v2_disk_dqblk
including structures for both versions and change sizeof(struct
v2_disk_dqblk) to some function. But all this shouldn't be that hard to do
in a nice way...

									Honza


> ---
> 
>  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>
-- 
Jan Kara <jack@xxxxxxx>
SUSE Labs, CR
--
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