diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/quota_v2.c | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'fs/quota_v2.c')
-rw-r--r-- | fs/quota_v2.c | 693 |
1 files changed, 693 insertions, 0 deletions
diff --git a/fs/quota_v2.c b/fs/quota_v2.c new file mode 100644 index 000000000000..19bdb7b86ca7 --- /dev/null +++ b/fs/quota_v2.c @@ -0,0 +1,693 @@ +/* + * vfsv0 quota IO operations on file + */ + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/mount.h> +#include <linux/dqblk_v2.h> +#include <linux/quotaio_v2.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <asm/byteorder.h> + +MODULE_AUTHOR("Jan Kara"); +MODULE_DESCRIPTION("Quota format v2 support"); +MODULE_LICENSE("GPL"); + +#define __QUOTA_V2_PARANOIA + +typedef char *dqbuf_t; + +#define GETIDINDEX(id, depth) (((id) >> ((V2_DQTREEDEPTH-(depth)-1)*8)) & 0xff) +#define GETENTRIES(buf) ((struct v2_disk_dqblk *)(((char *)buf)+sizeof(struct v2_disk_dqdbheader))) + +/* Check whether given file is really vfsv0 quotafile */ +static int v2_check_quota_file(struct super_block *sb, int type) +{ + struct v2_disk_dqheader dqhead; + ssize_t size; + static const uint quota_magics[] = V2_INITQMAGICS; + static const uint quota_versions[] = V2_INITQVERSIONS; + + size = sb->s_op->quota_read(sb, type, (char *)&dqhead, sizeof(struct v2_disk_dqheader), 0); + if (size != sizeof(struct v2_disk_dqheader)) { + printk("failed read\n"); + 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 v2_read_file_info(struct super_block *sb, int type) +{ + struct v2_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 v2_disk_dqinfo), V2_DQINFOOFF); + if (size != sizeof(struct v2_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.v2_i.dqi_blocks = le32_to_cpu(dinfo.dqi_blocks); + info->u.v2_i.dqi_free_blk = le32_to_cpu(dinfo.dqi_free_blk); + info->u.v2_i.dqi_free_entry = le32_to_cpu(dinfo.dqi_free_entry); + return 0; +} + +/* Write information header to quota file */ +static int v2_write_file_info(struct super_block *sb, int type) +{ + struct v2_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.v2_i.dqi_blocks); + dinfo.dqi_free_blk = cpu_to_le32(info->u.v2_i.dqi_free_blk); + dinfo.dqi_free_entry = cpu_to_le32(info->u.v2_i.dqi_free_entry); + size = sb->s_op->quota_write(sb, type, (char *)&dinfo, + sizeof(struct v2_disk_dqinfo), V2_DQINFOOFF); + if (size != sizeof(struct v2_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 v2_disk_dqblk *d) +{ + m->dqb_ihardlimit = le32_to_cpu(d->dqb_ihardlimit); + m->dqb_isoftlimit = le32_to_cpu(d->dqb_isoftlimit); + m->dqb_curinodes = le32_to_cpu(d->dqb_curinodes); + m->dqb_itime = le64_to_cpu(d->dqb_itime); + m->dqb_bhardlimit = le32_to_cpu(d->dqb_bhardlimit); + m->dqb_bsoftlimit = le32_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 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); + 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_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(V2_DQBLKSIZE, GFP_NOFS); + if (!buf) + printk(KERN_WARNING "VFS: 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, V2_DQBLKSIZE); + return sb->s_op->quota_read(sb, type, (char *)buf, + V2_DQBLKSIZE, blk << V2_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, + V2_DQBLKSIZE, blk << V2_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 v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf; + int ret, blk; + + if (!buf) + return -ENOMEM; + if (info->u.v2_i.dqi_free_blk) { + blk = info->u.v2_i.dqi_free_blk; + if ((ret = read_blk(sb, type, blk, buf)) < 0) + goto out_buf; + info->u.v2_i.dqi_free_blk = le32_to_cpu(dh->dqdh_next_free); + } + else { + memset(buf, 0, V2_DQBLKSIZE); + /* Assure block allocation... */ + if ((ret = write_blk(sb, type, info->u.v2_i.dqi_blocks, buf)) < 0) + goto out_buf; + blk = info->u.v2_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 v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf; + int err; + + dh->dqdh_next_free = cpu_to_le32(info->u.v2_i.dqi_free_blk); + dh->dqdh_prev_free = cpu_to_le32(0); + dh->dqdh_entries = cpu_to_le16(0); + info->u.v2_i.dqi_free_blk = blk; + mark_info_dirty(sb, type); + /* Some strange block. We had better leave it... */ + if ((err = write_blk(sb, type, blk, buf)) < 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 v2_disk_dqdbheader *dh = (struct v2_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) { + if ((err = read_blk(sb, type, nextblk, tmpbuf)) < 0) + goto out_buf; + ((struct v2_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = dh->dqdh_prev_free; + if ((err = write_blk(sb, type, nextblk, tmpbuf)) < 0) + goto out_buf; + } + if (prevblk) { + if ((err = read_blk(sb, type, prevblk, tmpbuf)) < 0) + goto out_buf; + ((struct v2_disk_dqdbheader *)tmpbuf)->dqdh_next_free = dh->dqdh_next_free; + if ((err = write_blk(sb, type, prevblk, tmpbuf)) < 0) + goto out_buf; + } + else { + info->u.v2_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 v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf; + int err; + + if (!tmpbuf) + return -ENOMEM; + dh->dqdh_next_free = cpu_to_le32(info->u.v2_i.dqi_free_entry); + dh->dqdh_prev_free = cpu_to_le32(0); + if ((err = write_blk(sb, type, blk, buf)) < 0) + goto out_buf; + if (info->u.v2_i.dqi_free_entry) { + if ((err = read_blk(sb, type, info->u.v2_i.dqi_free_entry, tmpbuf)) < 0) + goto out_buf; + ((struct v2_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = cpu_to_le32(blk); + if ((err = write_blk(sb, type, info->u.v2_i.dqi_free_entry, tmpbuf)) < 0) + goto out_buf; + } + freedqbuf(tmpbuf); + info->u.v2_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 v2_disk_dqdbheader *dh; + struct v2_disk_dqblk *ddquot; + struct v2_disk_dqblk fakedquot; + dqbuf_t buf; + + *err = 0; + if (!(buf = getdqbuf())) { + *err = -ENOMEM; + return 0; + } + dh = (struct v2_disk_dqdbheader *)buf; + ddquot = GETENTRIES(buf); + if (info->u.v2_i.dqi_free_entry) { + blk = info->u.v2_i.dqi_free_entry; + if ((*err = read_blk(sb, dquot->dq_type, blk, buf)) < 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, V2_DQBLKSIZE); + /* This is enough as block is already zeroed and entry list is empty... */ + info->u.v2_i.dqi_free_entry = blk; + mark_info_dirty(sb, dquot->dq_type); + } + if (le16_to_cpu(dh->dqdh_entries)+1 >= V2_DQSTRINBLK) /* Block will be full? */ + if ((*err = remove_free_dqentry(sb, dquot->dq_type, buf, blk)) < 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 v2_disk_dqblk)); + /* Find free structure in block */ + for (i = 0; i < V2_DQSTRINBLK && memcmp(&fakedquot, ddquot+i, sizeof(struct v2_disk_dqblk)); i++); +#ifdef __QUOTA_V2_PARANOIA + if (i == V2_DQSTRINBLK) { + printk(KERN_ERR "VFS: find_free_dqentry(): Data block full but it shouldn't.\n"); + *err = -EIO; + goto out_buf; + } +#endif + if ((*err = write_blk(sb, dquot->dq_type, blk, buf)) < 0) { + printk(KERN_ERR "VFS: find_free_dqentry(): Can't write quota data block %u.\n", blk); + goto out_buf; + } + dquot->dq_off = (blk<<V2_DQBLKSIZE_BITS)+sizeof(struct v2_disk_dqdbheader)+i*sizeof(struct v2_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; + + if (!(buf = getdqbuf())) + return -ENOMEM; + if (!*treeblk) { + ret = get_free_dqblk(sb, dquot->dq_type); + if (ret < 0) + goto out_buf; + *treeblk = ret; + memset(buf, 0, V2_DQBLKSIZE); + newact = 1; + } + else { + if ((ret = read_blk(sb, dquot->dq_type, *treeblk, buf)) < 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 == V2_DQTREEDEPTH-1) { +#ifdef __QUOTA_V2_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 = V2_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 v2_write_dquot(struct dquot *dquot) +{ + int type = dquot->dq_type; + 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); + /* 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 */ + memset(&empty, 0, sizeof(struct v2_disk_dqblk)); + if (!memcmp(&empty, &ddquot, sizeof(struct v2_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 v2_disk_dqblk), dquot->dq_off); + if (ret != sizeof(struct v2_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 v2_disk_dqdbheader *dh; + dqbuf_t buf = getdqbuf(); + int ret = 0; + + if (!buf) + return -ENOMEM; + if (dquot->dq_off >> V2_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 >> V2_DQBLKSIZE_BITS)); + goto out_buf; + } + if ((ret = read_blk(sb, type, blk, buf)) < 0) { + printk(KERN_ERR "VFS: Can't read quota data block %u\n", blk); + goto out_buf; + } + dh = (struct v2_disk_dqdbheader *)buf; + dh->dqdh_entries = cpu_to_le16(le16_to_cpu(dh->dqdh_entries)-1); + if (!le16_to_cpu(dh->dqdh_entries)) { /* Block got free? */ + if ((ret = remove_free_dqentry(sb, type, buf, blk)) < 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 << V2_DQBLKSIZE_BITS)-1)), 0, + sizeof(struct v2_disk_dqblk)); + if (le16_to_cpu(dh->dqdh_entries) == V2_DQSTRINBLK-1) { + /* Insert will write block itself */ + if ((ret = insert_free_dqentry(sb, type, buf, blk)) < 0) { + printk(KERN_ERR "VFS: Can't insert quota data block (%u) to free entry list.\n", blk); + goto out_buf; + } + } + else + if ((ret = write_blk(sb, type, blk, buf)) < 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; + if ((ret = read_blk(sb, type, *blk, buf)) < 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 == V2_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 < V2_DQBLKSIZE && !buf[i]; i++); /* Block got empty? */ + if (i == V2_DQBLKSIZE) { + put_free_dqblk(sb, type, buf, *blk); + *blk = 0; + } + else + if ((ret = write_blk(sb, type, *blk, buf)) < 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 v2_delete_dquot(struct dquot *dquot) +{ + uint tmp = V2_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 v2_disk_dqblk *ddquot = GETENTRIES(buf); + + if (!buf) + return -ENOMEM; + if ((ret = read_blk(dquot->dq_sb, dquot->dq_type, blk, buf)) < 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 < V2_DQSTRINBLK && + le32_to_cpu(ddquot[i].dqb_id) != dquot->dq_id; i++); + else { /* ID 0 as a bit more complicated searching... */ + struct v2_disk_dqblk fakedquot; + + memset(&fakedquot, 0, sizeof(struct v2_disk_dqblk)); + for (i = 0; i < V2_DQSTRINBLK; i++) + if (!le32_to_cpu(ddquot[i].dqb_id) && + memcmp(&fakedquot, ddquot+i, sizeof(struct v2_disk_dqblk))) + break; + } + if (i == V2_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 << V2_DQBLKSIZE_BITS) + sizeof(struct + v2_disk_dqdbheader) + i * sizeof(struct v2_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; + if ((ret = read_blk(dquot->dq_sb, dquot->dq_type, blk, buf)) < 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 < V2_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, V2_DQTREEOFF, 0); +} + +static int v2_read_dquot(struct dquot *dquot) +{ + int type = dquot->dq_type; + loff_t offset; + struct v2_disk_dqblk ddquot, empty; + int ret = 0; + +#ifdef __QUOTA_V2_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) { /* Entry not present? */ + 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; + if ((ret = dquot->dq_sb->s_op->quota_read(dquot->dq_sb, type, + (char *)&ddquot, sizeof(struct v2_disk_dqblk), offset)) + != sizeof(struct v2_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 v2_disk_dqblk)); + } + else { + ret = 0; + /* We need to escape back all-zero structure */ + memset(&empty, 0, sizeof(struct v2_disk_dqblk)); + empty.dqb_itime = cpu_to_le64(1); + if (!memcmp(&empty, &ddquot, sizeof(struct v2_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 v2_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 v2_delete_dquot(dquot); + return 0; +} + +static struct quota_format_ops v2_format_ops = { + .check_quota_file = v2_check_quota_file, + .read_file_info = v2_read_file_info, + .write_file_info = v2_write_file_info, + .free_file_info = NULL, + .read_dqblk = v2_read_dquot, + .commit_dqblk = v2_write_dquot, + .release_dqblk = v2_release_dquot, +}; + +static struct quota_format_type v2_quota_format = { + .qf_fmt_id = QFMT_VFS_V0, + .qf_ops = &v2_format_ops, + .qf_owner = THIS_MODULE +}; + +static int __init init_v2_quota_format(void) +{ + return register_quota_format(&v2_quota_format); +} + +static void __exit exit_v2_quota_format(void) +{ + unregister_quota_format(&v2_quota_format); +} + +module_init(init_v2_quota_format); +module_exit(exit_v2_quota_format); |