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