| /* |
| * 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 <linux/quotaops.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))) |
| |
| #define QUOTABLOCK_BITS 10 |
| #define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS) |
| |
| static inline qsize_t v2_stoqb(qsize_t space) |
| { |
| return (space + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS; |
| } |
| |
| static inline qsize_t v2_qbtos(qsize_t blocks) |
| { |
| return blocks << QUOTABLOCK_BITS; |
| } |
| |
| /* 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("quota_v2: failed read expected=%zd got=%zd\n", |
| sizeof(struct v2_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 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; |
| } |
| /* limits are stored as unsigned 32-bit data */ |
| info->dqi_maxblimit = 0xffffffff; |
| info->dqi_maxilimit = 0xffffffff; |
| 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 = v2_qbtos(le32_to_cpu(d->dqb_bhardlimit)); |
| m->dqb_bsoftlimit = v2_qbtos(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(v2_qbtos(m->dqb_bhardlimit)); |
| d->dqb_bsoftlimit = cpu_to_le32(v2_qbtos(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; |
| } |
| le16_add_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_mutex */ |
| 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; |
| le16_add_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? */ |
| /* Don't put the root block into the free block list */ |
| if (i == V2_DQBLKSIZE && *blk != V2_DQTREEOFF) { |
| 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); |