--- /dev/null
+/*
+ * 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);
--- /dev/null
+/* 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("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. <info@clusterfs.com>");
+MODULE_DESCRIPTION("administrative quotafile test module");
+MODULE_LICENSE("GPL");
+
+module_init(quotfmt_test_init);
+module_exit(quotfmt_test_exit);