Whamcloud - gitweb
Added forgottent files needed for quota stuff
authorgreen <green>
Mon, 4 Apr 2005 12:25:22 +0000 (12:25 +0000)
committergreen <green>
Mon, 4 Apr 2005 12:25:22 +0000 (12:25 +0000)
lustre/ldiskfs/lustre_quota_fmt.c [new file with mode: 0644]
lustre/ldiskfs/lustre_quota_fmt.h [new file with mode: 0644]
lustre/ldiskfs/quotafmt_test.c [new file with mode: 0644]

diff --git a/lustre/ldiskfs/lustre_quota_fmt.c b/lustre/ldiskfs/lustre_quota_fmt.c
new file mode 100644 (file)
index 0000000..84aa304
--- /dev/null
@@ -0,0 +1,750 @@
+/*
+ * Lustre administrative quota format.
+ *
+ *  from
+ *  linux/fs/quota_v2.c
+ */
+
+
+#ifndef EXPORT_SYMTAB
+# define EXPORT_SYMTAB
+#endif
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/quotaio_v1.h>
+
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+
+#include <linux/lustre_quota.h>
+#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<<LUSTRE_DQBLKSIZE_BITS;
+
+       memset(buf, 0, LUSTRE_DQBLKSIZE);
+       fs = get_fs();
+       set_fs(KERNEL_DS);
+       ret = filp->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<<LUSTRE_DQBLKSIZE_BITS;
+
+       fs = get_fs();
+       set_fs(KERNEL_DS);
+       ret = filp->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<<LUSTRE_DQBLKSIZE_BITS)+sizeof(struct lustre_disk_dqdbheader)+i*sizeof(struct lustre_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 lustre_dquot *dquot, uint *treeblk, int depth)
+{
+       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];
+       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 (file)
index 0000000..73d6120
--- /dev/null
@@ -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 <linux/types.h>
+#include <linux/quota.h>
+
+/*
+ * 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 (file)
index 0000000..0c33aa1
--- /dev/null
@@ -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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/random.h>
+
+#include <linux/lustre_quota.h>
+#include <linux/obd_class.h>
+
+#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(&quotfmt_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. <info@clusterfs.com>");
+MODULE_DESCRIPTION("administrative quotafile test module");
+MODULE_LICENSE("GPL");
+
+module_init(quotfmt_test_init);
+module_exit(quotfmt_test_exit);