From e3c8432637485b8721cea6567987472d8f10f8c4 Mon Sep 17 00:00:00 2001 From: green Date: Mon, 4 Apr 2005 12:25:22 +0000 Subject: [PATCH] Added forgottent files needed for quota stuff --- lustre/ldiskfs/lustre_quota_fmt.c | 750 ++++++++++++++++++++++++++++++++++++++ lustre/ldiskfs/lustre_quota_fmt.h | 83 +++++ lustre/ldiskfs/quotafmt_test.c | 460 +++++++++++++++++++++++ 3 files changed, 1293 insertions(+) create mode 100644 lustre/ldiskfs/lustre_quota_fmt.c create mode 100644 lustre/ldiskfs/lustre_quota_fmt.h create mode 100644 lustre/ldiskfs/quotafmt_test.c diff --git a/lustre/ldiskfs/lustre_quota_fmt.c b/lustre/ldiskfs/lustre_quota_fmt.c new file mode 100644 index 0000000..84aa304 --- /dev/null +++ b/lustre/ldiskfs/lustre_quota_fmt.c @@ -0,0 +1,750 @@ +/* + * Lustre administrative quota format. + * + * from + * linux/fs/quota_v2.c + */ + + +#ifndef EXPORT_SYMTAB +# define EXPORT_SYMTAB +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "lustre_quota_fmt.h" + +typedef char *dqbuf_t; + +#define GETIDINDEX(id, depth) (((id) >> ((LUSTRE_DQTREEDEPTH-(depth)-1)*8)) & 0xff) +#define GETENTRIES(buf) ((struct lustre_disk_dqblk *)(((char *)buf)+sizeof(struct lustre_disk_dqdbheader))) + +/* Check whether given file is really lustre admin quotafile */ +int lustre_check_quota_file(struct lustre_quota_info *lqi, int type) +{ + struct lustre_disk_dqheader dqhead; + struct file *f = lqi->qi_files[type]; + mm_segment_t fs; + ssize_t size; + loff_t offset = 0; + static const uint quota_magics[] = LUSTRE_INITQMAGICS; + static const uint quota_versions[] = LUSTRE_INITQVERSIONS; + + fs = get_fs(); + set_fs(KERNEL_DS); + size = f->f_op->read(f, (char *)&dqhead, sizeof(struct lustre_disk_dqheader), &offset); + set_fs(fs); + if (size != sizeof(struct lustre_disk_dqheader)) + 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 */ +int lustre_read_quota_info(struct lustre_quota_info *lqi, int type) +{ + mm_segment_t fs; + struct lustre_disk_dqinfo dinfo; + struct lustre_mem_dqinfo *info = &lqi->qi_info[type]; + struct file *f = lqi->qi_files[type]; + ssize_t size; + loff_t offset = LUSTRE_DQINFOOFF; + + fs = get_fs(); + set_fs(KERNEL_DS); + size = f->f_op->read(f, (char *)&dinfo, sizeof(struct lustre_disk_dqinfo), &offset); + set_fs(fs); + if (size != sizeof(struct lustre_disk_dqinfo)) { + printk(KERN_WARNING "Can't read info structure on device %s.\n", + f->f_vfsmnt->mnt_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->dqi_blocks = le32_to_cpu(dinfo.dqi_blocks); + info->dqi_free_blk = le32_to_cpu(dinfo.dqi_free_blk); + info->dqi_free_entry = le32_to_cpu(dinfo.dqi_free_entry); + return 0; +} + +/* Write information header to quota file */ +int lustre_write_quota_info(struct lustre_quota_info *lqi, int type) +{ + mm_segment_t fs; + struct lustre_disk_dqinfo dinfo; + struct lustre_mem_dqinfo *info = &lqi->qi_info[type]; + struct file *f = lqi->qi_files[type]; + ssize_t size; + loff_t offset = LUSTRE_DQINFOOFF; + + 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); + dinfo.dqi_blocks = cpu_to_le32(info->dqi_blocks); + dinfo.dqi_free_blk = cpu_to_le32(info->dqi_free_blk); + dinfo.dqi_free_entry = cpu_to_le32(info->dqi_free_entry); + fs = get_fs(); + set_fs(KERNEL_DS); + size = f->f_op->write(f, (char *)&dinfo, sizeof(struct lustre_disk_dqinfo), &offset); + set_fs(fs); + if (size != sizeof(struct lustre_disk_dqinfo)) { + printk(KERN_WARNING "Can't write info structure on device %s.\n", + f->f_vfsmnt->mnt_sb->s_id); + return -1; + } + return 0; +} + +static void disk2memdqb(struct mem_dqblk *m, struct lustre_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 lustre_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(LUSTRE_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 ssize_t read_blk(struct file *filp, uint blk, dqbuf_t buf) +{ + mm_segment_t fs; + ssize_t ret; + loff_t offset = blk<f_op->read(filp, (char *)buf, LUSTRE_DQBLKSIZE, &offset); + set_fs(fs); + return ret; +} + +static ssize_t write_blk(struct file *filp, uint blk, dqbuf_t buf) +{ + mm_segment_t fs; + ssize_t ret; + loff_t offset = blk<f_op->write(filp, (char *)buf, LUSTRE_DQBLKSIZE, &offset); + set_fs(fs); + return ret; + +} + +static void lustre_mark_info_dirty(struct lustre_mem_dqinfo *info) +{ + set_bit(DQF_INFO_DIRTY_B, &info->dqi_flags); +} + +#define lustre_info_dirty(info) test_bit(DQF_INFO_DIRTY_B, &(info)->dqi_flags) + +/* Remove empty block from list and return it */ +static int get_free_dqblk(struct file *filp, struct lustre_mem_dqinfo *info) +{ + dqbuf_t buf = getdqbuf(); + struct lustre_disk_dqdbheader *dh = (struct lustre_disk_dqdbheader *)buf; + int ret, blk; + + if (!buf) + return -ENOMEM; + if (info->dqi_free_blk) { + blk = info->dqi_free_blk; + if ((ret = read_blk(filp, blk, buf)) < 0) + goto out_buf; + info->dqi_free_blk = le32_to_cpu(dh->dqdh_next_free); + } + else { + memset(buf, 0, LUSTRE_DQBLKSIZE); + if ((ret = write_blk(filp, info->dqi_blocks, buf)) < 0) /* Assure block allocation... */ + goto out_buf; + blk = info->dqi_blocks++; + } + lustre_mark_info_dirty(info); + ret = blk; +out_buf: + freedqbuf(buf); + return ret; +} + +/* Insert empty block to the list */ +static int put_free_dqblk(struct file *filp, struct lustre_mem_dqinfo *info, + dqbuf_t buf, uint blk) +{ + struct lustre_disk_dqdbheader *dh = (struct lustre_disk_dqdbheader *)buf; + int err; + + dh->dqdh_next_free = cpu_to_le32(info->dqi_free_blk); + dh->dqdh_prev_free = cpu_to_le32(0); + dh->dqdh_entries = cpu_to_le16(0); + info->dqi_free_blk = blk; + lustre_mark_info_dirty(info); + if ((err = write_blk(filp, blk, buf)) < 0) /* Some strange block. We had better leave it... */ + return err; + return 0; +} + +/* Remove given block from the list of blocks with free entries */ +static int remove_free_dqentry(struct file *filp, struct lustre_mem_dqinfo *info, dqbuf_t buf, uint blk) +{ + dqbuf_t tmpbuf = getdqbuf(); + struct lustre_disk_dqdbheader *dh = (struct lustre_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(filp, nextblk, tmpbuf)) < 0) + goto out_buf; + ((struct lustre_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = dh->dqdh_prev_free; + if ((err = write_blk(filp, nextblk, tmpbuf)) < 0) + goto out_buf; + } + if (prevblk) { + if ((err = read_blk(filp, prevblk, tmpbuf)) < 0) + goto out_buf; + ((struct lustre_disk_dqdbheader *)tmpbuf)->dqdh_next_free = dh->dqdh_next_free; + if ((err = write_blk(filp, prevblk, tmpbuf)) < 0) + goto out_buf; + } + else { + info->dqi_free_entry = nextblk; + lustre_mark_info_dirty(info); + } + freedqbuf(tmpbuf); + dh->dqdh_next_free = dh->dqdh_prev_free = cpu_to_le32(0); + if (write_blk(filp, blk, buf) < 0) /* No matter whether write succeeds block is out of list */ + 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 file *filp, struct lustre_mem_dqinfo *info, dqbuf_t buf, uint blk) +{ + dqbuf_t tmpbuf = getdqbuf(); + struct lustre_disk_dqdbheader *dh = (struct lustre_disk_dqdbheader *)buf; + int err; + + if (!tmpbuf) + return -ENOMEM; + dh->dqdh_next_free = cpu_to_le32(info->dqi_free_entry); + dh->dqdh_prev_free = cpu_to_le32(0); + if ((err = write_blk(filp, blk, buf)) < 0) + goto out_buf; + if (info->dqi_free_entry) { + if ((err = read_blk(filp, info->dqi_free_entry, tmpbuf)) < 0) + goto out_buf; + ((struct lustre_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = cpu_to_le32(blk); + if ((err = write_blk(filp, info->dqi_free_entry, tmpbuf)) < 0) + goto out_buf; + } + freedqbuf(tmpbuf); + info->dqi_free_entry = blk; + lustre_mark_info_dirty(info); + return 0; +out_buf: + freedqbuf(tmpbuf); + return err; +} + +/* Find space for dquot */ +static uint find_free_dqentry(struct lustre_dquot *dquot, int *err) +{ + struct lustre_quota_info *lqi = dquot->dq_info; + struct file *filp = lqi->qi_files[dquot->dq_type]; + struct lustre_mem_dqinfo *info = &lqi->qi_info[dquot->dq_type]; + uint blk, i; + struct lustre_disk_dqdbheader *dh; + struct lustre_disk_dqblk *ddquot; + struct lustre_disk_dqblk fakedquot; + dqbuf_t buf; + + *err = 0; + if (!(buf = getdqbuf())) { + *err = -ENOMEM; + return 0; + } + dh = (struct lustre_disk_dqdbheader *)buf; + ddquot = GETENTRIES(buf); + if (info->dqi_free_entry) { + blk = info->dqi_free_entry; + if ((*err = read_blk(filp, blk, buf)) < 0) + goto out_buf; + } + else { + blk = get_free_dqblk(filp, info); + if ((int)blk < 0) { + *err = blk; + freedqbuf(buf); + return 0; + } + memset(buf, 0, LUSTRE_DQBLKSIZE); + info->dqi_free_entry = blk; /* This is enough as block is already zeroed and entry list is empty... */ + lustre_mark_info_dirty(info); + } + if (le16_to_cpu(dh->dqdh_entries)+1 >= LUSTRE_DQSTRINBLK) /* Block will be full? */ + if ((*err = remove_free_dqentry(filp, info, 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 lustre_disk_dqblk)); + /* Find free structure in block */ + for (i = 0; i < LUSTRE_DQSTRINBLK && memcmp(&fakedquot, ddquot+i, sizeof(struct lustre_disk_dqblk)); i++); + + if (i == LUSTRE_DQSTRINBLK) { + printk(KERN_ERR "VFS: find_free_dqentry(): Data block full but it shouldn't.\n"); + *err = -EIO; + goto out_buf; + } + + if ((*err = write_blk(filp, 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<dq_info; + struct file *filp = lqi->qi_files[dquot->dq_type]; + struct lustre_mem_dqinfo *info = &lqi->qi_info[dquot->dq_type]; + dqbuf_t buf; + int ret = 0, newson = 0, newact = 0; + u32 *ref; + uint newblk; + + if (!(buf = getdqbuf())) + return -ENOMEM; + if (!*treeblk) { + ret = get_free_dqblk(filp, info); + if (ret < 0) + goto out_buf; + *treeblk = ret; + memset(buf, 0, LUSTRE_DQBLKSIZE); + newact = 1; + } + else { + if ((ret = read_blk(filp, *treeblk, buf)) < 0) { + printk(KERN_ERR "VFS: Can't read tree quota block %u.\n", *treeblk); + goto out_buf; + } + } + ref = (u32 *)buf; + newblk = le32_to_cpu(ref[GETIDINDEX(dquot->dq_id, depth)]); + if (!newblk) + newson = 1; + if (depth == LUSTRE_DQTREEDEPTH-1) { + + if (newblk) { + printk(KERN_ERR "VFS: Inserting already present quota entry (block %u).\n", ref[GETIDINDEX(dquot->dq_id, depth)]); + ret = -EIO; + goto out_buf; + } + + 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(filp, *treeblk, buf); + } + else if (newact && ret < 0) + put_free_dqblk(filp, info, buf, *treeblk); +out_buf: + freedqbuf(buf); + return ret; +} + +/* Wrapper for inserting quota structure into tree */ +static inline int dq_insert_tree(struct lustre_dquot *dquot) +{ + int tmp = LUSTRE_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 lustre_write_dquot(struct lustre_dquot *dquot) +{ + int type = dquot->dq_type; + struct file *filp; + mm_segment_t fs; + loff_t offset; + ssize_t ret; + struct lustre_disk_dqblk ddquot, empty; + + 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; + } + filp = dquot->dq_info->qi_files[type]; + offset = dquot->dq_off; + 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 lustre_disk_dqblk)); + if (!memcmp(&empty, &ddquot, sizeof(struct lustre_disk_dqblk))) + ddquot.dqb_itime = cpu_to_le64(1); + fs = get_fs(); + set_fs(KERNEL_DS); + ret = filp->f_op->write(filp, (char *)&ddquot, sizeof(struct lustre_disk_dqblk), &offset); + set_fs(fs); + if (ret != sizeof(struct lustre_disk_dqblk)) { + printk(KERN_WARNING "VFS: dquota write failed on dev %s\n", filp->f_dentry->d_sb->s_id); + if (ret >= 0) + ret = -ENOSPC; + } + else + ret = 0; + + return ret; +} + +/* Free dquot entry in data block */ +static int free_dqentry(struct lustre_dquot *dquot, uint blk) +{ + struct file *filp = dquot->dq_info->qi_files[dquot->dq_type]; + struct lustre_mem_dqinfo *info = &dquot->dq_info->qi_info[dquot->dq_type]; + struct lustre_disk_dqdbheader *dh; + dqbuf_t buf = getdqbuf(); + int ret = 0; + + if (!buf) + return -ENOMEM; + if (dquot->dq_off >> LUSTRE_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 >> LUSTRE_DQBLKSIZE_BITS)); + goto out_buf; + } + if ((ret = read_blk(filp, blk, buf)) < 0) { + printk(KERN_ERR "VFS: Can't read quota data block %u\n", blk); + goto out_buf; + } + dh = (struct lustre_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(filp, info, buf, blk)) < 0 || + (ret = put_free_dqblk(filp, info, 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 << LUSTRE_DQBLKSIZE_BITS)-1)), 0, sizeof(struct lustre_disk_dqblk)); + if (le16_to_cpu(dh->dqdh_entries) == LUSTRE_DQSTRINBLK-1) { + /* Insert will write block itself */ + if ((ret = insert_free_dqentry(filp, info, 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(filp, 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 lustre_dquot *dquot, uint *blk, int depth) +{ + struct file *filp = dquot->dq_info->qi_files[dquot->dq_type]; + struct lustre_mem_dqinfo *info = &dquot->dq_info->qi_info[dquot->dq_type]; + dqbuf_t buf = getdqbuf(); + int ret = 0; + uint newblk; + u32 *ref = (u32 *)buf; + + if (!buf) + return -ENOMEM; + if ((ret = read_blk(filp, *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 == LUSTRE_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 < LUSTRE_DQBLKSIZE && !buf[i]; i++); /* Block got empty? */ + if (i == LUSTRE_DQBLKSIZE) { + put_free_dqblk(filp, info, buf, *blk); + *blk = 0; + } + else + if ((ret = write_blk(filp, *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 lustre_delete_dquot(struct lustre_dquot *dquot) +{ + uint tmp = LUSTRE_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 lustre_dquot *dquot, uint blk) +{ + struct file *filp = dquot->dq_info->qi_files[dquot->dq_type]; + dqbuf_t buf = getdqbuf(); + loff_t ret = 0; + int i; + struct lustre_disk_dqblk *ddquot = GETENTRIES(buf); + + if (!buf) + return -ENOMEM; + if ((ret = read_blk(filp, 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 < LUSTRE_DQSTRINBLK && le32_to_cpu(ddquot[i].dqb_id) != dquot->dq_id; i++); + else { /* ID 0 as a bit more complicated searching... */ + struct lustre_disk_dqblk fakedquot; + + memset(&fakedquot, 0, sizeof(struct lustre_disk_dqblk)); + for (i = 0; i < LUSTRE_DQSTRINBLK; i++) + if (!le32_to_cpu(ddquot[i].dqb_id) && memcmp(&fakedquot, ddquot+i, sizeof(struct lustre_disk_dqblk))) + break; + } + if (i == LUSTRE_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 << LUSTRE_DQBLKSIZE_BITS) + sizeof(struct lustre_disk_dqdbheader) + i * sizeof(struct lustre_disk_dqblk); +out_buf: + freedqbuf(buf); + return ret; +} + +/* Find entry for given id in the tree */ +static loff_t find_tree_dqentry(struct lustre_dquot *dquot, uint blk, int depth) +{ + struct file *filp = dquot->dq_info->qi_files[dquot->dq_type]; + dqbuf_t buf = getdqbuf(); + loff_t ret = 0; + u32 *ref = (u32 *)buf; + + if (!buf) + return -ENOMEM; + if ((ret = read_blk(filp, 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 < LUSTRE_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 lustre_dquot *dquot) +{ + return find_tree_dqentry(dquot, LUSTRE_DQTREEOFF, 0); +} + +int lustre_read_dquot(struct lustre_dquot *dquot) +{ + int type = dquot->dq_type; + struct file *filp; + mm_segment_t fs; + loff_t offset; + struct lustre_disk_dqblk ddquot, empty; + int ret = 0; + + filp = dquot->dq_info->qi_files[type]; + + if (!filp || !dquot->dq_info) { /* Invalidated quota? */ + printk(KERN_ERR "VFS: Quota invalidated while reading!\n"); + return -EIO; + } + + 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; + fs = get_fs(); + set_fs(KERNEL_DS); + if ((ret = filp->f_op->read(filp, (char *)&ddquot, sizeof(struct lustre_disk_dqblk), &offset)) != sizeof(struct lustre_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 lustre_disk_dqblk)); + } + else { + ret = 0; + /* We need to escape back all-zero structure */ + memset(&empty, 0, sizeof(struct lustre_disk_dqblk)); + empty.dqb_itime = cpu_to_le64(1); + if (!memcmp(&empty, &ddquot, sizeof(struct lustre_disk_dqblk))) + ddquot.dqb_itime = 0; + } + set_fs(fs); + disk2memdqb(&dquot->dq_dqb, &ddquot); + } + + return ret; +} + +/* Commit changes of dquot to disk - it might also mean deleting it when quota became fake */ +int lustre_commit_dquot(struct lustre_dquot *dquot) +{ + int rc = 0; + /* We clear the flag everytime so we don't loop when there was an IO error... */ + clear_bit(DQ_MOD_B, &dquot->dq_flags); + + /* The block/inode usage in admin quotafile isn't the real usage over all cluster, + * so keep the fake dquot entry on disk is meaningless, just remove it */ +#ifndef QFMT_NO_DELETE + if (test_bit(DQ_FAKE_B, &dquot->dq_flags)) + rc = lustre_delete_dquot(dquot); + } + else { + rc = lustre_write_dquot(dquot); + } +#else + rc = lustre_write_dquot(dquot); +#endif + if (rc < 0) + return rc; + + if (lustre_info_dirty(&dquot->dq_info->qi_info[dquot->dq_type])) + rc = lustre_write_quota_info(dquot->dq_info, dquot->dq_type); + + return rc; +} + +/* We need to export this function to initialize quotafile, because we haven't + * user level check utility */ +int lustre_init_quota_info(struct lustre_quota_info *lqi, int type) +{ + struct lustre_mem_dqinfo *dqinfo = &lqi->qi_info[type]; + struct lustre_disk_dqheader dqhead; + struct file *fp = lqi->qi_files[type]; + ssize_t size; + loff_t offset = 0; + int rc = 0; + static const uint quota_magics[] = LUSTRE_INITQMAGICS; + static const uint quota_versions[] = LUSTRE_INITQVERSIONS; + + /* write quotafile header */ + dqhead.dqh_magic = cpu_to_le32(quota_magics[type]); + dqhead.dqh_version = cpu_to_le32(quota_versions[type]); + size = fp->f_op->write(fp, (char *)&dqhead, + sizeof(struct lustre_disk_dqheader), &offset); + + if (size != sizeof(struct lustre_disk_dqheader)) { + printk(KERN_ERR "error writing quoafile header (rc:%d)\n", rc); + rc = size; + } + if (rc) + return rc; + + /* write init quota info */ + memset(dqinfo, 0, sizeof(*dqinfo)); + dqinfo->dqi_bgrace = MAX_DQ_TIME; + dqinfo->dqi_igrace = MAX_IQ_TIME; + dqinfo->dqi_blocks = LUSTRE_DQTREEOFF + 1; + + return lustre_write_quota_info(lqi, type); +} + +EXPORT_SYMBOL(lustre_check_quota_file); +EXPORT_SYMBOL(lustre_read_quota_info); +EXPORT_SYMBOL(lustre_write_quota_info); +EXPORT_SYMBOL(lustre_read_dquot); +EXPORT_SYMBOL(lustre_commit_dquot); +EXPORT_SYMBOL(lustre_init_quota_info); diff --git a/lustre/ldiskfs/lustre_quota_fmt.h b/lustre/ldiskfs/lustre_quota_fmt.h new file mode 100644 index 0000000..73d6120 --- /dev/null +++ b/lustre/ldiskfs/lustre_quota_fmt.h @@ -0,0 +1,83 @@ +/* + * Lustre administrative quota format + * + * from + * include/linux/quotaio_v2.h + */ +#ifndef _LUSTRE_QUOTA_FMT_H +#define _LUSTRE_QUOTA_FMT_H + +#include +#include + +/* + * Definitions of magics and versions of current quota files + * Same with quota v2's magic + */ +#define LUSTRE_INITQMAGICS {\ + 0xd9c01f11, /* USRQUOTA */\ + 0xd9c01927 /* GRPQUOTA */\ +} + +#define LUSTRE_INITQVERSIONS {\ + 0, /* USRQUOTA */\ + 0 /* 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 lustre_disk_dqblk { + __u32 dqb_id; /* id this quota applies to */ + __u32 dqb_ihardlimit; /* absolute limit on allocated inodes */ + __u32 dqb_isoftlimit; /* preferred inode limit */ + __u32 dqb_curinodes; /* current # allocated inodes */ + __u32 dqb_bhardlimit; /* absolute limit on disk space (in QUOTABLOCK_SIZE) */ + __u32 dqb_bsoftlimit; /* preferred limit on disk space (in QUOTABLOCK_SIZE) */ + __u64 dqb_curspace; /* current space occupied (in bytes) */ + __u64 dqb_btime; /* time limit for excessive disk use */ + __u64 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 lustre_disk_dqheader { + __u32 dqh_magic; /* Magic number identifying file */ + __u32 dqh_version; /* File version */ +}; + +/* Header with type and version specific information */ +struct lustre_disk_dqinfo { + __u32 dqi_bgrace; /* Time before block soft limit becomes hard limit */ + __u32 dqi_igrace; /* Time before inode soft limit becomes hard limit */ + __u32 dqi_flags; /* Flags for quotafile (DQF_*) */ + __u32 dqi_blocks; /* Number of blocks in file */ + __u32 dqi_free_blk; /* Number of first free block in the list */ + __u32 dqi_free_entry; /* Number of block with at least one 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 lustre_disk_dqdbheader { + __u32 dqdh_next_free; /* Number of next block with free entry */ + __u32 dqdh_prev_free; /* Number of previous block with free entry */ + __u16 dqdh_entries; /* Number of valid entries in block */ + __u16 dqdh_pad1; + __u32 dqdh_pad2; +}; + +#define LUSTRE_DQINFOOFF sizeof(struct lustre_disk_dqheader) /* Offset of info header in file */ +#define LUSTRE_DQBLKSIZE_BITS 10 +#define LUSTRE_DQBLKSIZE (1 << LUSTRE_DQBLKSIZE_BITS) /* Size of block with quota structures */ +#define LUSTRE_DQTREEOFF 1 /* Offset of tree in file in blocks */ +#define LUSTRE_DQTREEDEPTH 4 /* Depth of quota tree */ +#define LUSTRE_DQSTRINBLK ((LUSTRE_DQBLKSIZE - sizeof(struct lustre_disk_dqdbheader)) / sizeof(struct lustre_disk_dqblk)) /* Number of entries in one blocks */ + + +#endif /* lustre_quota_fmt.h */ diff --git a/lustre/ldiskfs/quotafmt_test.c b/lustre/ldiskfs/quotafmt_test.c new file mode 100644 index 0000000..0c33aa1 --- /dev/null +++ b/lustre/ldiskfs/quotafmt_test.c @@ -0,0 +1,460 @@ +/* Kernel module to test lustre administrative quotafile format APIs + * from the OBD setup function */ +#ifndef EXPORT_SYMTAB +# define EXPORT_SYMTAB +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lustre_quota_fmt.h" + +char *test_quotafile[2] = {"usrquota_test", "grpquota_test"}; + +static int quotfmt_initialize(struct lustre_quota_info *lqi, struct obd_device *tgt, + struct obd_run_ctxt *saved) +{ + struct lustre_disk_dqheader dqhead; + static const uint quota_magics[] = LUSTRE_INITQMAGICS; + static const uint quota_versions[] = LUSTRE_INITQVERSIONS; + struct file *fp; + struct inode *parent_inode = tgt->obd_ctxt.pwd->d_inode; + size_t size; + struct dentry *de; + int i, rc = 0; + ENTRY; + + push_ctxt(saved, &tgt->obd_ctxt, NULL); + + sema_init(&lqi->qi_sem, 1); + + for (i = 0; i < MAXQUOTAS; i++) { + loff_t offset = 0; + char *name = test_quotafile[i]; + int namelen = strlen(name); + + /* remove the stale test quotafile */ + down(&parent_inode->i_sem); + de = lookup_one_len(name, tgt->obd_ctxt.pwd, namelen); + if (!IS_ERR(de) && de->d_inode) + vfs_unlink(parent_inode, de); + if (!IS_ERR(de)) + dput(de); + up(&parent_inode->i_sem); + + /* create quota file */ + fp = filp_open(name, O_CREAT | O_EXCL, 0644); + if (IS_ERR(fp)) { + rc = PTR_ERR(fp); + CERROR("error creating test quotafile %s (rc = %d)\n", + name, rc); + break; + } + lqi->qi_files[i] = fp; + + /* write quotafile header */ + dqhead.dqh_magic = cpu_to_le32(quota_magics[i]); + dqhead.dqh_version = cpu_to_le32(quota_versions[i]); + size = fp->f_op->write(fp, (char *)&dqhead, + sizeof(struct lustre_disk_dqheader), + &offset); + if (size != sizeof(struct lustre_disk_dqheader)) { + CERROR("error writing quoafile header %s (rc = %d)\n", + name, rc); + rc = size; + break; + } + } + + RETURN(rc); +} + +static int quotfmt_finalize(struct lustre_quota_info *lqi, struct obd_device *tgt, + struct obd_run_ctxt *saved) +{ + struct dentry *de; + struct inode *parent_inode = tgt->obd_ctxt.pwd->d_inode; + int i, rc = 0; + ENTRY; + + for (i = 0; i < MAXQUOTAS; i++) { + char *name = test_quotafile[i]; + int namelen = strlen(name); + + if (lqi->qi_files[i] == NULL) + continue; + + /* close quota file */ + filp_close(lqi->qi_files[i], 0); + + /* unlink quota file */ + down(&parent_inode->i_sem); + + de = lookup_one_len(name, tgt->obd_ctxt.pwd, namelen); + if (IS_ERR(de) || de->d_inode == NULL) { + rc = IS_ERR(de) ? PTR_ERR(de) : -ENOENT; + CERROR("error lookup quotafile %s (rc = %d)\n", + name, rc); + goto dput; + } + + rc = vfs_unlink(parent_inode, de); + if (rc) + CERROR("error unlink quotafile %s (rc = %d)\n", + name, rc); +dput: + if (!IS_ERR(de)) + dput(de); + up(&parent_inode->i_sem); + } + + pop_ctxt(saved, &tgt->obd_ctxt, NULL); + RETURN(rc); +} + +static int quotfmt_test_1(struct lustre_quota_info *lqi) +{ + int i; + ENTRY; + + for (i = 0; i < MAXQUOTAS; i++) { + if (!lustre_check_quota_file(lqi, i)) + RETURN(-EINVAL); + } + RETURN(0); +} + +static void print_quota_info(struct lustre_quota_info *lqi) +{ +#if 0 + struct lustre_mem_dqinfo *dqinfo; + int i; + + for (i = 0; i < MAXQUOTAS; i++) { + dqinfo = &lqi->qi_info[i]; + printk("%s quota info:\n", i == USRQUOTA ? "user " : "group"); + printk("dqi_bgrace(%u) dqi_igrace(%u) dqi_flags(%lu) dqi_blocks(%u) " + "dqi_free_blk(%u) dqi_free_entry(%u)\n", + dqinfo->dqi_bgrace, dqinfo->dqi_igrace, dqinfo->dqi_flags, + dqinfo->dqi_blocks, dqinfo->dqi_free_blk, + dqinfo->dqi_free_entry); + } +#endif +} + +static int quotfmt_test_2(struct lustre_quota_info *lqi) +{ + int i, rc = 0; + ENTRY; + + for (i = 0; i < MAXQUOTAS; i++) { + struct lustre_mem_dqinfo dqinfo; + + rc = lustre_init_quota_info(lqi, i); + if (rc) { + CERROR("init quotainfo(%d) failed! (rc:%d)\n", i, rc); + break; + } + memcpy(&dqinfo, &lqi->qi_info[i], sizeof(dqinfo)); + + rc = lustre_read_quota_info(lqi, i); + if (rc) { + CERROR("read quotainfo(%d) failed! (rc:%d)\n", i, rc); + break; + } + + if(memcmp(&dqinfo, &lqi->qi_info[i], sizeof(dqinfo))) { + rc = -EINVAL; + break; + } + } + RETURN(rc); +} + +static struct lustre_dquot *get_rand_dquot(struct lustre_quota_info *lqi) +{ + struct lustre_dquot *dquot; + unsigned int rand; + + OBD_ALLOC(dquot, sizeof(*dquot)); + if (dquot == NULL) + return NULL; + + get_random_bytes(&rand, sizeof(rand)); + if (!rand) + rand = 1000; + + dquot->dq_info = lqi; + dquot->dq_id = rand % 1000 + 1; + dquot->dq_type = rand % MAXQUOTAS; + + dquot->dq_dqb.dqb_bhardlimit = rand; + dquot->dq_dqb.dqb_bsoftlimit = rand / 2; + dquot->dq_dqb.dqb_curspace = rand / 3; + dquot->dq_dqb.dqb_ihardlimit = rand; + dquot->dq_dqb.dqb_isoftlimit = rand / 2; + dquot->dq_dqb.dqb_curinodes = rand / 3; + dquot->dq_dqb.dqb_btime = jiffies; + dquot->dq_dqb.dqb_itime = jiffies; + + return dquot; +} + +static void put_rand_dquot(struct lustre_dquot *dquot) +{ + OBD_FREE(dquot, sizeof(*dquot)); +} + +static int write_check_dquot(struct lustre_quota_info *lqi) +{ + struct lustre_dquot *dquot; + struct mem_dqblk dqblk; + int rc = 0; + ENTRY; + + dquot = get_rand_dquot(lqi); + if (dquot == NULL) + RETURN(-ENOMEM); + + /* for already exists entry, we set the dq_off by read_dquot */ + rc = lustre_read_dquot(dquot); + if (rc) { + CERROR("read dquot failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + + /* for already exists entry, we rewrite it */ + rc = lustre_commit_dquot(dquot); + if (rc) { + CERROR("commit dquot failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + memcpy(&dqblk, &dquot->dq_dqb, sizeof(dqblk)); + memset(&dquot->dq_dqb, 0, sizeof(dqblk)); + + rc = lustre_read_dquot(dquot); + if (rc) { + CERROR("read dquot failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + + if (memcmp(&dqblk, &dquot->dq_dqb, sizeof(dqblk))) { + rc = -EINVAL; + GOTO(out, rc); + } +out: + put_rand_dquot(dquot); + RETURN(rc); +} + +static int quotfmt_test_3(struct lustre_quota_info *lqi) +{ + struct lustre_dquot *dquot; + int i = 0, rc = 0; + ENTRY; + +#ifdef QFMT_NO_DELETE + RETURN(0); +#endif + dquot = get_rand_dquot(lqi); + if (dquot == NULL) + RETURN(-ENOMEM); +repeat: + dquot->dq_flags &= ~DQ_FAKE; + /* write a new dquot */ + rc = lustre_commit_dquot(dquot); + if (rc) { + CERROR("commit dquot failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + dquot->dq_off = 0; + memset(&dquot->dq_dqb, 0, sizeof(dquot->dq_dqb)); + + /* check if this dquot is on disk now */ + rc = lustre_read_dquot(dquot); + if (rc) { + CERROR("read dquot failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + if (!dquot->dq_off || dquot->dq_flags & DQ_FAKE) { + CERROR("the dquot isn't committed\n"); + GOTO(out, rc = -EINVAL); + } + + /* remove this dquot */ + set_bit(DQ_FAKE_B, &dquot->dq_flags); + dquot->dq_dqb.dqb_curspace = 0; + dquot->dq_dqb.dqb_curinodes = 0; + rc = lustre_commit_dquot(dquot); + if (rc) { + CERROR("remove dquot failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + + /* check if the dquot is really removed */ + clear_bit(DQ_FAKE_B, &dquot->dq_flags); + dquot->dq_off = 0; + rc = lustre_read_dquot(dquot); + if (rc) { + CERROR("read dquot failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + if (!test_bit(DQ_FAKE_B, &dquot->dq_flags) || dquot->dq_off) { + CERROR("the dquot isn't removed!\n"); + GOTO(out, rc = -EINVAL); + } + + /* check if this dquot can be write again */ + if (++i < 2) + goto repeat; + + print_quota_info(lqi); + +out: + put_rand_dquot(dquot); + RETURN(rc); +} + +static int quotfmt_test_4(struct lustre_quota_info *lqi) +{ + int i, rc = 0; + ENTRY; + + for (i = 0; i < 30000; i++) { + rc = write_check_dquot(lqi); + if (rc) { + CERROR("write/check dquot failed at %d! (rc:%d)\n", + i, rc); + break; + } + } + print_quota_info(lqi); + RETURN(rc); +} + +static int quotfmt_run_tests(struct obd_device *obd, struct obd_device *tgt) +{ + struct obd_run_ctxt saved; + struct lustre_quota_info *lqi = NULL; + int rc = 0; + ENTRY; + + OBD_ALLOC(lqi, sizeof(*lqi)); + if (lqi == NULL) { + CERROR("not enough memory\n"); + RETURN(-ENOMEM); + } + + CWARN("=== Initialize quotafile test\n"); + rc = quotfmt_initialize(lqi, tgt, &saved); + if (rc) + GOTO(out, rc); + + CWARN("=== test 1: check quota header\n"); + rc = quotfmt_test_1(lqi); + if (rc) { + CERROR("check quota header failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + + CWARN("=== test 2: write/read quota info\n"); + rc = quotfmt_test_2(lqi); + if (rc) { + CERROR("write/read quota info failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + + CWARN("=== test 3: write/remove dquot\n"); + rc = quotfmt_test_3(lqi); + if (rc) { + CERROR("write/remove dquot failed! (rc:%d)\n", rc); + GOTO(out, rc); + } + + CWARN("=== test 4: write/read 30000 dquot\n"); + rc = quotfmt_test_4(lqi); + if (rc) { + CERROR("write/read 30000 dquot failed\n"); + GOTO(out, rc); + } +out: + CWARN("=== Finalize quotafile test\n"); + rc = quotfmt_finalize(lqi, tgt, &saved); + OBD_FREE(lqi, sizeof(*lqi)); + RETURN(rc); +} + +static int quotfmt_test_cleanup(struct obd_device *obd) +{ + ENTRY; + lprocfs_obd_cleanup(obd); + RETURN(0); +} + +static int quotfmt_test_setup(struct obd_device *obd, obd_count len, void *buf) +{ + struct lprocfs_static_vars lvars; + struct lustre_cfg *lcfg = buf; + struct obd_device *tgt; + int rc; + ENTRY; + + if (lcfg->lcfg_bufcount < 1) { + CERROR("requires a mds OBD name\n"); + RETURN(-EINVAL); + } + + tgt = class_name2obd(lustre_cfg_string(lcfg, 1)); + if (!tgt || !tgt->obd_attached || !tgt->obd_set_up) { + CERROR("target device not attached or not set up (%s)\n", + lustre_cfg_string(lcfg, 1)); + RETURN(-EINVAL); + } + + rc = quotfmt_run_tests(obd, tgt); + if (rc) + quotfmt_test_cleanup(obd); + + lprocfs_init_vars(quotfmt_test, &lvars); + lprocfs_obd_setup(obd, lvars.obd_vars); + + RETURN(rc); +} + +static struct obd_ops quotfmt_obd_ops = { + .o_owner = THIS_MODULE, + .o_setup = quotfmt_test_setup, + .o_cleanup = quotfmt_test_cleanup, +}; + +static struct lprocfs_vars lprocfs_obd_vars[] = { {0} }; +static struct lprocfs_vars lprocfs_module_vars[] = { {0} }; +LPROCFS_INIT_VARS(quotfmt_test, lprocfs_module_vars, lprocfs_obd_vars) + +static int __init quotfmt_test_init(void) +{ + struct lprocfs_static_vars lvars; + + lprocfs_init_vars(quotfmt_test, &lvars); + return class_register_type("fmt_obd_ops, lvars.module_vars, + "quotfmt_test"); +} + +static void __exit quotfmt_test_exit(void) +{ + class_unregister_type("quotfmt_test"); +} + +MODULE_AUTHOR("Cluster File Systems, Inc. "); +MODULE_DESCRIPTION("administrative quotafile test module"); +MODULE_LICENSE("GPL"); + +module_init(quotfmt_test_init); +module_exit(quotfmt_test_exit); -- 1.8.3.1