sysfile.o               \
        uptodate.o              \
        ver.o                   \
+       quota_local.o           \
+       quota_global.o          \
        xattr.o
 
 ifeq ($(CONFIG_OCFS2_FS_POSIX_ACL),y)
 
 #define ML_QUORUM      0x0000000008000000ULL /* net connection quorum */
 #define ML_EXPORT      0x0000000010000000ULL /* ocfs2 export operations */
 #define ML_XATTR       0x0000000020000000ULL /* ocfs2 extended attributes */
+#define ML_QUOTA       0x0000000040000000ULL /* ocfs2 quota operations */
 /* bits that are infrequently given and frequently matched in the high word */
 #define ML_ERROR       0x0000000100000000ULL /* sent to KERN_ERR */
 #define ML_NOTICE      0x0000000200000000ULL /* setn to KERN_NOTICE */
 
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
 #include <linux/time.h>
+#include <linux/quotaops.h>
 
 #define MLOG_MASK_PREFIX ML_DLM_GLUE
 #include <cluster/masklog.h>
 #include "slot_map.h"
 #include "super.h"
 #include "uptodate.h"
+#include "quota.h"
 
 #include "buffer_head_io.h"
 
 static struct ocfs2_super *ocfs2_get_dentry_osb(struct ocfs2_lock_res *lockres);
 static struct ocfs2_super *ocfs2_get_inode_osb(struct ocfs2_lock_res *lockres);
 static struct ocfs2_super *ocfs2_get_file_osb(struct ocfs2_lock_res *lockres);
+static struct ocfs2_super *ocfs2_get_qinfo_osb(struct ocfs2_lock_res *lockres);
 
 /*
  * Return value from ->downconvert_worker functions.
 static void ocfs2_dentry_post_unlock(struct ocfs2_super *osb,
                                     struct ocfs2_lock_res *lockres);
 
+static void ocfs2_set_qinfo_lvb(struct ocfs2_lock_res *lockres);
 
 #define mlog_meta_lvb(__level, __lockres) ocfs2_dump_meta_lvb_info(__level, __PRETTY_FUNCTION__, __LINE__, __lockres)
 
        .flags          = 0,
 };
 
+static struct ocfs2_lock_res_ops ocfs2_qinfo_lops = {
+       .set_lvb        = ocfs2_set_qinfo_lvb,
+       .get_osb        = ocfs2_get_qinfo_osb,
+       .flags          = LOCK_TYPE_REQUIRES_REFRESH | LOCK_TYPE_USES_LVB,
+};
+
 static inline int ocfs2_is_inode_lock(struct ocfs2_lock_res *lockres)
 {
        return lockres->l_type == OCFS2_LOCK_TYPE_META ||
        return (struct ocfs2_dentry_lock *)lockres->l_priv;
 }
 
+static inline struct ocfs2_mem_dqinfo *ocfs2_lock_res_qinfo(struct ocfs2_lock_res *lockres)
+{
+       BUG_ON(lockres->l_type != OCFS2_LOCK_TYPE_QINFO);
+
+       return (struct ocfs2_mem_dqinfo *)lockres->l_priv;
+}
+
 static inline struct ocfs2_super *ocfs2_get_lockres_osb(struct ocfs2_lock_res *lockres)
 {
        if (lockres->l_ops->get_osb)
        return OCFS2_SB(inode->i_sb);
 }
 
+static struct ocfs2_super *ocfs2_get_qinfo_osb(struct ocfs2_lock_res *lockres)
+{
+       struct ocfs2_mem_dqinfo *info = lockres->l_priv;
+
+       return OCFS2_SB(info->dqi_gi.dqi_sb);
+}
+
 static struct ocfs2_super *ocfs2_get_file_osb(struct ocfs2_lock_res *lockres)
 {
        struct ocfs2_file_private *fp = lockres->l_priv;
        lockres->l_flags |= OCFS2_LOCK_NOCACHE;
 }
 
+void ocfs2_qinfo_lock_res_init(struct ocfs2_lock_res *lockres,
+                              struct ocfs2_mem_dqinfo *info)
+{
+       ocfs2_lock_res_init_once(lockres);
+       ocfs2_build_lock_name(OCFS2_LOCK_TYPE_QINFO, info->dqi_gi.dqi_type,
+                             0, lockres->l_name);
+       ocfs2_lock_res_init_common(OCFS2_SB(info->dqi_gi.dqi_sb), lockres,
+                                  OCFS2_LOCK_TYPE_QINFO, &ocfs2_qinfo_lops,
+                                  info);
+}
+
 void ocfs2_lock_res_free(struct ocfs2_lock_res *res)
 {
        mlog_entry_void();
        return UNBLOCK_CONTINUE_POST;
 }
 
+static void ocfs2_set_qinfo_lvb(struct ocfs2_lock_res *lockres)
+{
+       struct ocfs2_qinfo_lvb *lvb;
+       struct ocfs2_mem_dqinfo *oinfo = ocfs2_lock_res_qinfo(lockres);
+       struct mem_dqinfo *info = sb_dqinfo(oinfo->dqi_gi.dqi_sb,
+                                           oinfo->dqi_gi.dqi_type);
+
+       mlog_entry_void();
+
+       lvb = (struct ocfs2_qinfo_lvb *)ocfs2_dlm_lvb(&lockres->l_lksb);
+       lvb->lvb_version = OCFS2_QINFO_LVB_VERSION;
+       lvb->lvb_bgrace = cpu_to_be32(info->dqi_bgrace);
+       lvb->lvb_igrace = cpu_to_be32(info->dqi_igrace);
+       lvb->lvb_syncms = cpu_to_be32(oinfo->dqi_syncms);
+       lvb->lvb_blocks = cpu_to_be32(oinfo->dqi_gi.dqi_blocks);
+       lvb->lvb_free_blk = cpu_to_be32(oinfo->dqi_gi.dqi_free_blk);
+       lvb->lvb_free_entry = cpu_to_be32(oinfo->dqi_gi.dqi_free_entry);
+
+       mlog_exit_void();
+}
+
+void ocfs2_qinfo_unlock(struct ocfs2_mem_dqinfo *oinfo, int ex)
+{
+       struct ocfs2_lock_res *lockres = &oinfo->dqi_gqlock;
+       struct ocfs2_super *osb = OCFS2_SB(oinfo->dqi_gi.dqi_sb);
+       int level = ex ? DLM_LOCK_EX : DLM_LOCK_PR;
+
+       mlog_entry_void();
+       if (!ocfs2_is_hard_readonly(osb) && !ocfs2_mount_local(osb))
+               ocfs2_cluster_unlock(osb, lockres, level);
+       mlog_exit_void();
+}
+
+static int ocfs2_refresh_qinfo(struct ocfs2_mem_dqinfo *oinfo)
+{
+       struct mem_dqinfo *info = sb_dqinfo(oinfo->dqi_gi.dqi_sb,
+                                           oinfo->dqi_gi.dqi_type);
+       struct ocfs2_lock_res *lockres = &oinfo->dqi_gqlock;
+       struct ocfs2_qinfo_lvb *lvb = ocfs2_dlm_lvb(&lockres->l_lksb);
+       struct buffer_head *bh;
+       struct ocfs2_global_disk_dqinfo *gdinfo;
+       int status = 0;
+
+       if (lvb->lvb_version == OCFS2_QINFO_LVB_VERSION) {
+               info->dqi_bgrace = be32_to_cpu(lvb->lvb_bgrace);
+               info->dqi_igrace = be32_to_cpu(lvb->lvb_igrace);
+               oinfo->dqi_syncms = be32_to_cpu(lvb->lvb_syncms);
+               oinfo->dqi_gi.dqi_blocks = be32_to_cpu(lvb->lvb_blocks);
+               oinfo->dqi_gi.dqi_free_blk = be32_to_cpu(lvb->lvb_free_blk);
+               oinfo->dqi_gi.dqi_free_entry =
+                                       be32_to_cpu(lvb->lvb_free_entry);
+       } else {
+               bh = ocfs2_read_quota_block(oinfo->dqi_gqinode, 0, &status);
+               if (!bh) {
+                       mlog_errno(status);
+                       goto bail;
+               }
+               gdinfo = (struct ocfs2_global_disk_dqinfo *)
+                                       (bh->b_data + OCFS2_GLOBAL_INFO_OFF);
+               info->dqi_bgrace = le32_to_cpu(gdinfo->dqi_bgrace);
+               info->dqi_igrace = le32_to_cpu(gdinfo->dqi_igrace);
+               oinfo->dqi_syncms = le32_to_cpu(gdinfo->dqi_syncms);
+               oinfo->dqi_gi.dqi_blocks = le32_to_cpu(gdinfo->dqi_blocks);
+               oinfo->dqi_gi.dqi_free_blk = le32_to_cpu(gdinfo->dqi_free_blk);
+               oinfo->dqi_gi.dqi_free_entry =
+                                       le32_to_cpu(gdinfo->dqi_free_entry);
+               brelse(bh);
+               ocfs2_track_lock_refresh(lockres);
+       }
+
+bail:
+       return status;
+}
+
+/* Lock quota info, this function expects at least shared lock on the quota file
+ * so that we can safely refresh quota info from disk. */
+int ocfs2_qinfo_lock(struct ocfs2_mem_dqinfo *oinfo, int ex)
+{
+       struct ocfs2_lock_res *lockres = &oinfo->dqi_gqlock;
+       struct ocfs2_super *osb = OCFS2_SB(oinfo->dqi_gi.dqi_sb);
+       int level = ex ? DLM_LOCK_EX : DLM_LOCK_PR;
+       int status = 0;
+
+       mlog_entry_void();
+
+       /* On RO devices, locking really isn't needed... */
+       if (ocfs2_is_hard_readonly(osb)) {
+               if (ex)
+                       status = -EROFS;
+               goto bail;
+       }
+       if (ocfs2_mount_local(osb))
+               goto bail;
+
+       status = ocfs2_cluster_lock(osb, lockres, level, 0, 0);
+       if (status < 0) {
+               mlog_errno(status);
+               goto bail;
+       }
+       if (!ocfs2_should_refresh_lock_res(lockres))
+               goto bail;
+       /* OK, we have the lock but we need to refresh the quota info */
+       status = ocfs2_refresh_qinfo(oinfo);
+       if (status)
+               ocfs2_qinfo_unlock(oinfo, ex);
+       ocfs2_complete_lock_res_refresh(lockres, status);
+bail:
+       mlog_exit(status);
+       return status;
+}
+
 /*
  * This is the filesystem locking protocol.  It provides the lock handling
  * hooks for the underlying DLM.  It has a maximum version number.
 
        __be32       lvb_reserved2;
 };
 
+#define OCFS2_QINFO_LVB_VERSION 1
+
+struct ocfs2_qinfo_lvb {
+       __u8    lvb_version;
+       __u8    lvb_reserved[3];
+       __be32  lvb_bgrace;
+       __be32  lvb_igrace;
+       __be32  lvb_syncms;
+       __be32  lvb_blocks;
+       __be32  lvb_free_blk;
+       __be32  lvb_free_entry;
+};
+
 /* ocfs2_inode_lock_full() 'arg_flags' flags */
 /* don't wait on recovery. */
 #define OCFS2_META_LOCK_RECOVERY       (0x01)
 struct ocfs2_file_private;
 void ocfs2_file_lock_res_init(struct ocfs2_lock_res *lockres,
                              struct ocfs2_file_private *fp);
+struct ocfs2_mem_dqinfo;
+void ocfs2_qinfo_lock_res_init(struct ocfs2_lock_res *lockres,
+                               struct ocfs2_mem_dqinfo *info);
 void ocfs2_lock_res_free(struct ocfs2_lock_res *res);
 int ocfs2_create_new_inode_locks(struct inode *inode);
 int ocfs2_drop_inode_locks(struct inode *inode);
 void ocfs2_dentry_unlock(struct dentry *dentry, int ex);
 int ocfs2_file_lock(struct file *file, int ex, int trylock);
 void ocfs2_file_unlock(struct file *file);
+int ocfs2_qinfo_lock(struct ocfs2_mem_dqinfo *oinfo, int ex);
+void ocfs2_qinfo_unlock(struct ocfs2_mem_dqinfo *oinfo, int ex);
+
 
 void ocfs2_mark_lockres_freeing(struct ocfs2_lock_res *lockres);
 void ocfs2_simple_drop_lockres(struct ocfs2_super *osb,
 
        return status;
 }
 
-static int ocfs2_simple_size_update(struct inode *inode,
-                                   struct buffer_head *di_bh,
-                                   u64 new_i_size)
+int ocfs2_simple_size_update(struct inode *inode,
+                            struct buffer_head *di_bh,
+                            u64 new_i_size)
 {
        int ret;
        struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
 
                         struct ocfs2_alloc_context *data_ac,
                         struct ocfs2_alloc_context *meta_ac,
                         enum ocfs2_alloc_restarted *reason_ret);
+int ocfs2_simple_size_update(struct inode *inode,
+                            struct buffer_head *di_bh,
+                            u64 new_i_size);
 int ocfs2_extend_no_holes(struct inode *inode, u64 new_i_size,
                          u64 zero_to);
 int ocfs2_setattr(struct dentry *dentry, struct iattr *attr);
 
                           struct buffer_head *bh);
 int ocfs2_aio_read(struct file *file, struct kiocb *req, struct iocb *iocb);
 int ocfs2_aio_write(struct file *file, struct kiocb *req, struct iocb *iocb);
+struct buffer_head *ocfs2_bread(struct inode *inode,
+                               int block, int *err, int reada);
 
 void ocfs2_set_inode_flags(struct inode *inode);
 void ocfs2_get_inode_flags(struct ocfs2_inode_info *oi);
 
        return xe->xe_type & OCFS2_XATTR_TYPE_MASK;
 }
 
+/*
+ *  On disk structures for global quota file
+ */
+
+/* Magic numbers and known versions for global quota files */
+#define OCFS2_GLOBAL_QMAGICS {\
+       0x0cf52470, /* USRQUOTA */ \
+       0x0cf52471  /* GRPQUOTA */ \
+}
+
+#define OCFS2_GLOBAL_QVERSIONS {\
+       0, \
+       0, \
+}
+
+
+/* Each block of each quota file has a certain fixed number of bytes reserved
+ * for OCFS2 internal use at its end. OCFS2 can use it for things like
+ * checksums, etc. */
+#define OCFS2_QBLK_RESERVED_SPACE 8
+
+/* Generic header of all quota files */
+struct ocfs2_disk_dqheader {
+       __le32 dqh_magic;       /* Magic number identifying file */
+       __le32 dqh_version;     /* Quota format version */
+};
+
+#define OCFS2_GLOBAL_INFO_OFF (sizeof(struct ocfs2_disk_dqheader))
+
+/* Information header of global quota file (immediately follows the generic
+ * header) */
+struct ocfs2_global_disk_dqinfo {
+/*00*/ __le32 dqi_bgrace;      /* Grace time for space softlimit excess */
+       __le32 dqi_igrace;      /* Grace time for inode softlimit excess */
+       __le32 dqi_syncms;      /* Time after which we sync local changes to
+                                * global quota file */
+       __le32 dqi_blocks;      /* Number of blocks in quota file */
+/*10*/ __le32 dqi_free_blk;    /* First free block in quota file */
+       __le32 dqi_free_entry;  /* First block with free dquot entry in quota
+                                * file */
+};
+
+/* Structure with global user / group information. We reserve some space
+ * for future use. */
+struct ocfs2_global_disk_dqblk {
+/*00*/ __le32 dqb_id;          /* ID the structure belongs to */
+       __le32 dqb_use_count;   /* Number of nodes having reference to this structure */
+       __le64 dqb_ihardlimit;  /* absolute limit on allocated inodes */
+/*10*/ __le64 dqb_isoftlimit;  /* preferred inode limit */
+       __le64 dqb_curinodes;   /* current # allocated inodes */
+/*20*/ __le64 dqb_bhardlimit;  /* absolute limit on disk space */
+       __le64 dqb_bsoftlimit;  /* preferred limit on disk space */
+/*30*/ __le64 dqb_curspace;    /* current space occupied */
+       __le64 dqb_btime;       /* time limit for excessive disk use */
+/*40*/ __le64 dqb_itime;       /* time limit for excessive inode use */
+       __le64 dqb_pad1;
+/*50*/ __le64 dqb_pad2;
+};
+
+/*
+ *  On-disk structures for local quota file
+ */
+
+/* Magic numbers and known versions for local quota files */
+#define OCFS2_LOCAL_QMAGICS {\
+       0x0cf524c0, /* USRQUOTA */ \
+       0x0cf524c1  /* GRPQUOTA */ \
+}
+
+#define OCFS2_LOCAL_QVERSIONS {\
+       0, \
+       0, \
+}
+
+/* Quota flags in dqinfo header */
+#define OLQF_CLEAN     0x0001  /* Quota file is empty (this should be after\
+                                * quota has been cleanly turned off) */
+
+#define OCFS2_LOCAL_INFO_OFF (sizeof(struct ocfs2_disk_dqheader))
+
+/* Information header of local quota file (immediately follows the generic
+ * header) */
+struct ocfs2_local_disk_dqinfo {
+       __le32 dqi_flags;       /* Flags for quota file */
+       __le32 dqi_chunks;      /* Number of chunks of quota structures
+                                * with a bitmap */
+       __le32 dqi_blocks;      /* Number of blocks allocated for quota file */
+};
+
+/* Header of one chunk of a quota file */
+struct ocfs2_local_disk_chunk {
+       __le32 dqc_free;        /* Number of free entries in the bitmap */
+       u8 dqc_bitmap[0];       /* Bitmap of entries in the corresponding
+                                * chunk of quota file */
+};
+
+/* One entry in local quota file */
+struct ocfs2_local_disk_dqblk {
+/*00*/ __le64 dqb_id;          /* id this quota applies to */
+       __le64 dqb_spacemod;    /* Change in the amount of used space */
+/*10*/ __le64 dqb_inodemod;    /* Change in the amount of used inodes */
+};
+
 #ifdef __KERNEL__
 static inline int ocfs2_fast_symlink_chars(struct super_block *sb)
 {
 
        OCFS2_LOCK_TYPE_DENTRY,
        OCFS2_LOCK_TYPE_OPEN,
        OCFS2_LOCK_TYPE_FLOCK,
+       OCFS2_LOCK_TYPE_QINFO,
        OCFS2_NUM_LOCK_TYPES
 };
 
                case OCFS2_LOCK_TYPE_FLOCK:
                        c = 'F';
                        break;
+               case OCFS2_LOCK_TYPE_QINFO:
+                       c = 'Q';
+                       break;
                default:
                        c = '\0';
        }
        [OCFS2_LOCK_TYPE_DENTRY] = "Dentry",
        [OCFS2_LOCK_TYPE_OPEN] = "Open",
        [OCFS2_LOCK_TYPE_FLOCK] = "Flock",
+       [OCFS2_LOCK_TYPE_QINFO] = "Quota",
 };
 
 static inline const char *ocfs2_lock_type_string(enum ocfs2_lock_type type)
 
--- /dev/null
+/*
+ * quota.h for OCFS2
+ *
+ * On disk quota structures for local and global quota file, in-memory
+ * structures.
+ *
+ */
+
+#ifndef _OCFS2_QUOTA_H
+#define _OCFS2_QUOTA_H
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/quota.h>
+#include <linux/list.h>
+#include <linux/dqblk_qtree.h>
+
+#include "ocfs2.h"
+
+/* Common stuff */
+/* id number of quota format */
+#define QFMT_OCFS2 3
+
+/*
+ * In-memory structures
+ */
+struct ocfs2_dquot {
+       struct dquot dq_dquot;  /* Generic VFS dquot */
+       loff_t dq_local_off;    /* Offset in the local quota file */
+       struct ocfs2_quota_chunk *dq_chunk;     /* Chunk dquot is in */
+       unsigned int dq_use_count;      /* Number of nodes having reference to this entry in global quota file */
+       s64 dq_origspace;       /* Last globally synced space usage */
+       s64 dq_originodes;      /* Last globally synced inode usage */
+};
+
+/* In-memory structure with quota header information */
+struct ocfs2_mem_dqinfo {
+       unsigned int dqi_type;          /* Quota type this structure describes */
+       unsigned int dqi_chunks;        /* Number of chunks in local quota file */
+       unsigned int dqi_blocks;        /* Number of blocks allocated for local quota file */
+       unsigned int dqi_syncms;        /* How often should we sync with other nodes */
+       struct list_head dqi_chunk;     /* List of chunks */
+       struct inode *dqi_gqinode;      /* Global quota file inode */
+       struct ocfs2_lock_res dqi_gqlock;       /* Lock protecting quota information structure */
+       struct buffer_head *dqi_gqi_bh; /* Buffer head with global quota file inode - set only if inode lock is obtained */
+       int dqi_gqi_count;              /* Number of holders of dqi_gqi_bh */
+       struct buffer_head *dqi_lqi_bh; /* Buffer head with local quota file inode */
+       struct buffer_head *dqi_ibh;    /* Buffer with information header */
+       struct qtree_mem_dqinfo dqi_gi; /* Info about global file */
+};
+
+static inline struct ocfs2_dquot *OCFS2_DQUOT(struct dquot *dquot)
+{
+       return container_of(dquot, struct ocfs2_dquot, dq_dquot);
+}
+
+struct ocfs2_quota_chunk {
+       struct list_head qc_chunk;      /* List of quotafile chunks */
+       int qc_num;                     /* Number of quota chunk */
+       struct buffer_head *qc_headerbh;        /* Buffer head with chunk header */
+};
+
+extern struct kmem_cache *ocfs2_dquot_cachep;
+extern struct kmem_cache *ocfs2_qf_chunk_cachep;
+
+extern struct qtree_fmt_operations ocfs2_global_ops;
+
+ssize_t ocfs2_quota_read(struct super_block *sb, int type, char *data,
+                        size_t len, loff_t off);
+ssize_t ocfs2_quota_write(struct super_block *sb, int type,
+                         const char *data, size_t len, loff_t off);
+int ocfs2_global_read_info(struct super_block *sb, int type);
+int ocfs2_global_write_info(struct super_block *sb, int type);
+int ocfs2_global_read_dquot(struct dquot *dquot);
+int __ocfs2_sync_dquot(struct dquot *dquot, int freeing);
+static inline int ocfs2_sync_dquot(struct dquot *dquot)
+{
+       return __ocfs2_sync_dquot(dquot, 0);
+}
+static inline int ocfs2_global_release_dquot(struct dquot *dquot)
+{
+       return __ocfs2_sync_dquot(dquot, 1);
+}
+
+int ocfs2_lock_global_qf(struct ocfs2_mem_dqinfo *oinfo, int ex);
+void ocfs2_unlock_global_qf(struct ocfs2_mem_dqinfo *oinfo, int ex);
+struct buffer_head *ocfs2_read_quota_block(struct inode *inode,
+                                          int block, int *err);
+
+extern struct dquot_operations ocfs2_quota_operations;
+extern struct quota_format_type ocfs2_quota_format;
+
+#endif /* _OCFS2_QUOTA_H */
 
--- /dev/null
+/*
+ *  Implementation of operations over global quota file
+ */
+#include <linux/fs.h>
+#include <linux/quota.h>
+#include <linux/quotaops.h>
+#include <linux/dqblk_qtree.h>
+
+#define MLOG_MASK_PREFIX ML_QUOTA
+#include <cluster/masklog.h>
+
+#include "ocfs2_fs.h"
+#include "ocfs2.h"
+#include "alloc.h"
+#include "inode.h"
+#include "journal.h"
+#include "file.h"
+#include "sysfile.h"
+#include "dlmglue.h"
+#include "uptodate.h"
+#include "quota.h"
+
+static void ocfs2_global_disk2memdqb(struct dquot *dquot, void *dp)
+{
+       struct ocfs2_global_disk_dqblk *d = dp;
+       struct mem_dqblk *m = &dquot->dq_dqb;
+
+       /* Update from disk only entries not set by the admin */
+       if (!test_bit(DQ_LASTSET_B + QIF_ILIMITS_B, &dquot->dq_flags)) {
+               m->dqb_ihardlimit = le64_to_cpu(d->dqb_ihardlimit);
+               m->dqb_isoftlimit = le64_to_cpu(d->dqb_isoftlimit);
+       }
+       if (!test_bit(DQ_LASTSET_B + QIF_INODES_B, &dquot->dq_flags))
+               m->dqb_curinodes = le64_to_cpu(d->dqb_curinodes);
+       if (!test_bit(DQ_LASTSET_B + QIF_BLIMITS_B, &dquot->dq_flags)) {
+               m->dqb_bhardlimit = le64_to_cpu(d->dqb_bhardlimit);
+               m->dqb_bsoftlimit = le64_to_cpu(d->dqb_bsoftlimit);
+       }
+       if (!test_bit(DQ_LASTSET_B + QIF_SPACE_B, &dquot->dq_flags))
+               m->dqb_curspace = le64_to_cpu(d->dqb_curspace);
+       if (!test_bit(DQ_LASTSET_B + QIF_BTIME_B, &dquot->dq_flags))
+               m->dqb_btime = le64_to_cpu(d->dqb_btime);
+       if (!test_bit(DQ_LASTSET_B + QIF_ITIME_B, &dquot->dq_flags))
+               m->dqb_itime = le64_to_cpu(d->dqb_itime);
+       OCFS2_DQUOT(dquot)->dq_use_count = le32_to_cpu(d->dqb_use_count);
+}
+
+static void ocfs2_global_mem2diskdqb(void *dp, struct dquot *dquot)
+{
+       struct ocfs2_global_disk_dqblk *d = dp;
+       struct mem_dqblk *m = &dquot->dq_dqb;
+
+       d->dqb_id = cpu_to_le32(dquot->dq_id);
+       d->dqb_use_count = cpu_to_le32(OCFS2_DQUOT(dquot)->dq_use_count);
+       d->dqb_ihardlimit = cpu_to_le64(m->dqb_ihardlimit);
+       d->dqb_isoftlimit = cpu_to_le64(m->dqb_isoftlimit);
+       d->dqb_curinodes = cpu_to_le64(m->dqb_curinodes);
+       d->dqb_bhardlimit = cpu_to_le64(m->dqb_bhardlimit);
+       d->dqb_bsoftlimit = cpu_to_le64(m->dqb_bsoftlimit);
+       d->dqb_curspace = cpu_to_le64(m->dqb_curspace);
+       d->dqb_btime = cpu_to_le64(m->dqb_btime);
+       d->dqb_itime = cpu_to_le64(m->dqb_itime);
+}
+
+static int ocfs2_global_is_id(void *dp, struct dquot *dquot)
+{
+       struct ocfs2_global_disk_dqblk *d = dp;
+       struct ocfs2_mem_dqinfo *oinfo =
+                       sb_dqinfo(dquot->dq_sb, dquot->dq_type)->dqi_priv;
+
+       if (qtree_entry_unused(&oinfo->dqi_gi, dp))
+               return 0;
+       return le32_to_cpu(d->dqb_id) == dquot->dq_id;
+}
+
+struct qtree_fmt_operations ocfs2_global_ops = {
+       .mem2disk_dqblk = ocfs2_global_mem2diskdqb,
+       .disk2mem_dqblk = ocfs2_global_disk2memdqb,
+       .is_id = ocfs2_global_is_id,
+};
+
+
+struct buffer_head *ocfs2_read_quota_block(struct inode *inode,
+                                          int block, int *err)
+{
+       struct buffer_head *tmp = NULL;
+
+       *err = ocfs2_read_virt_blocks(inode, block, 1, &tmp, 0, NULL);
+       if (*err)
+               mlog_errno(*err);
+
+       return tmp;
+}
+
+static struct buffer_head *ocfs2_get_quota_block(struct inode *inode,
+                                                int block, int *err)
+{
+       u64 pblock, pcount;
+       struct buffer_head *bh;
+
+       down_read(&OCFS2_I(inode)->ip_alloc_sem);
+       *err = ocfs2_extent_map_get_blocks(inode, block, &pblock, &pcount,
+                                          NULL);
+       up_read(&OCFS2_I(inode)->ip_alloc_sem);
+       if (*err) {
+               mlog_errno(*err);
+               return NULL;
+       }
+       bh = sb_getblk(inode->i_sb, pblock);
+       if (!bh) {
+               *err = -EIO;
+               mlog_errno(*err);
+       }
+       return bh;
+}
+
+/* Read data from global quotafile - avoid pagecache and such because we cannot
+ * afford acquiring the locks... We use quota cluster lock to serialize
+ * operations. Caller is responsible for acquiring it. */
+ssize_t ocfs2_quota_read(struct super_block *sb, int type, char *data,
+                        size_t len, loff_t off)
+{
+       struct ocfs2_mem_dqinfo *oinfo = sb_dqinfo(sb, type)->dqi_priv;
+       struct inode *gqinode = oinfo->dqi_gqinode;
+       loff_t i_size = i_size_read(gqinode);
+       int offset = off & (sb->s_blocksize - 1);
+       sector_t blk = off >> sb->s_blocksize_bits;
+       int err = 0;
+       struct buffer_head *bh;
+       size_t toread, tocopy;
+
+       if (off > i_size)
+               return 0;
+       if (off + len > i_size)
+               len = i_size - off;
+       toread = len;
+       while (toread > 0) {
+               tocopy = min((size_t)(sb->s_blocksize - offset), toread);
+               bh = ocfs2_read_quota_block(gqinode, blk, &err);
+               if (!bh) {
+                       mlog_errno(err);
+                       return err;
+               }
+               memcpy(data, bh->b_data + offset, tocopy);
+               brelse(bh);
+               offset = 0;
+               toread -= tocopy;
+               data += tocopy;
+               blk++;
+       }
+       return len;
+}
+
+/* Write to quotafile (we know the transaction is already started and has
+ * enough credits) */
+ssize_t ocfs2_quota_write(struct super_block *sb, int type,
+                         const char *data, size_t len, loff_t off)
+{
+       struct mem_dqinfo *info = sb_dqinfo(sb, type);
+       struct ocfs2_mem_dqinfo *oinfo = info->dqi_priv;
+       struct inode *gqinode = oinfo->dqi_gqinode;
+       int offset = off & (sb->s_blocksize - 1);
+       sector_t blk = off >> sb->s_blocksize_bits;
+       int err = 0, new = 0;
+       struct buffer_head *bh;
+       handle_t *handle = journal_current_handle();
+
+       if (!handle) {
+               mlog(ML_ERROR, "Quota write (off=%llu, len=%llu) cancelled "
+                    "because transaction was not started.\n",
+                    (unsigned long long)off, (unsigned long long)len);
+               return -EIO;
+       }
+       if (len > sb->s_blocksize - OCFS2_QBLK_RESERVED_SPACE - offset) {
+               WARN_ON(1);
+               len = sb->s_blocksize - OCFS2_QBLK_RESERVED_SPACE - offset;
+       }
+
+       mutex_lock_nested(&gqinode->i_mutex, I_MUTEX_QUOTA);
+       if (gqinode->i_size < off + len) {
+               down_write(&OCFS2_I(gqinode)->ip_alloc_sem);
+               err = ocfs2_extend_no_holes(gqinode, off + len, off);
+               up_write(&OCFS2_I(gqinode)->ip_alloc_sem);
+               if (err < 0)
+                       goto out;
+               err = ocfs2_simple_size_update(gqinode,
+                                              oinfo->dqi_gqi_bh,
+                                              off + len);
+               if (err < 0)
+                       goto out;
+               new = 1;
+       }
+       /* Not rewriting whole block? */
+       if ((offset || len < sb->s_blocksize - OCFS2_QBLK_RESERVED_SPACE) &&
+           !new) {
+               bh = ocfs2_read_quota_block(gqinode, blk, &err);
+               if (!bh) {
+                       mlog_errno(err);
+                       return err;
+               }
+               err = ocfs2_journal_access(handle, gqinode, bh,
+                                               OCFS2_JOURNAL_ACCESS_WRITE);
+       } else {
+               bh = ocfs2_get_quota_block(gqinode, blk, &err);
+               if (!bh) {
+                       mlog_errno(err);
+                       return err;
+               }
+               err = ocfs2_journal_access(handle, gqinode, bh,
+                                               OCFS2_JOURNAL_ACCESS_CREATE);
+       }
+       if (err < 0) {
+               brelse(bh);
+               goto out;
+       }
+       lock_buffer(bh);
+       if (new)
+               memset(bh->b_data, 0, sb->s_blocksize);
+       memcpy(bh->b_data + offset, data, len);
+       flush_dcache_page(bh->b_page);
+       unlock_buffer(bh);
+       ocfs2_set_buffer_uptodate(gqinode, bh);
+       err = ocfs2_journal_dirty(handle, bh);
+       brelse(bh);
+       if (err < 0)
+               goto out;
+out:
+       if (err) {
+               mutex_unlock(&gqinode->i_mutex);
+               mlog_errno(err);
+               return err;
+       }
+       gqinode->i_version++;
+       ocfs2_mark_inode_dirty(handle, gqinode, oinfo->dqi_gqi_bh);
+       mutex_unlock(&gqinode->i_mutex);
+       return len;
+}
+
+int ocfs2_lock_global_qf(struct ocfs2_mem_dqinfo *oinfo, int ex)
+{
+       int status;
+       struct buffer_head *bh = NULL;
+
+       status = ocfs2_inode_lock(oinfo->dqi_gqinode, &bh, ex);
+       if (status < 0)
+               return status;
+       spin_lock(&dq_data_lock);
+       if (!oinfo->dqi_gqi_count++)
+               oinfo->dqi_gqi_bh = bh;
+       else
+               WARN_ON(bh != oinfo->dqi_gqi_bh);
+       spin_unlock(&dq_data_lock);
+       return 0;
+}
+
+void ocfs2_unlock_global_qf(struct ocfs2_mem_dqinfo *oinfo, int ex)
+{
+       ocfs2_inode_unlock(oinfo->dqi_gqinode, ex);
+       brelse(oinfo->dqi_gqi_bh);
+       spin_lock(&dq_data_lock);
+       if (!--oinfo->dqi_gqi_count)
+               oinfo->dqi_gqi_bh = NULL;
+       spin_unlock(&dq_data_lock);
+}
+
+/* Read information header from global quota file */
+int ocfs2_global_read_info(struct super_block *sb, int type)
+{
+       struct inode *gqinode = NULL;
+       unsigned int ino[MAXQUOTAS] = { USER_QUOTA_SYSTEM_INODE,
+                                       GROUP_QUOTA_SYSTEM_INODE };
+       struct ocfs2_global_disk_dqinfo dinfo;
+       struct mem_dqinfo *info = sb_dqinfo(sb, type);
+       struct ocfs2_mem_dqinfo *oinfo = info->dqi_priv;
+       int status;
+
+       mlog_entry_void();
+
+       /* Read global header */
+       gqinode = ocfs2_get_system_file_inode(OCFS2_SB(sb), ino[type],
+                       OCFS2_INVALID_SLOT);
+       if (!gqinode) {
+               mlog(ML_ERROR, "failed to get global quota inode (type=%d)\n",
+                       type);
+               status = -EINVAL;
+               goto out_err;
+       }
+       oinfo->dqi_gi.dqi_sb = sb;
+       oinfo->dqi_gi.dqi_type = type;
+       ocfs2_qinfo_lock_res_init(&oinfo->dqi_gqlock, oinfo);
+       oinfo->dqi_gi.dqi_entry_size = sizeof(struct ocfs2_global_disk_dqblk);
+       oinfo->dqi_gi.dqi_ops = &ocfs2_global_ops;
+       oinfo->dqi_gqi_bh = NULL;
+       oinfo->dqi_gqi_count = 0;
+       oinfo->dqi_gqinode = gqinode;
+       status = ocfs2_lock_global_qf(oinfo, 0);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_err;
+       }
+       status = sb->s_op->quota_read(sb, type, (char *)&dinfo,
+                                     sizeof(struct ocfs2_global_disk_dqinfo),
+                                     OCFS2_GLOBAL_INFO_OFF);
+       ocfs2_unlock_global_qf(oinfo, 0);
+       if (status != sizeof(struct ocfs2_global_disk_dqinfo)) {
+               mlog(ML_ERROR, "Cannot read global quota info (%d).\n",
+                    status);
+               if (status >= 0)
+                       status = -EIO;
+               mlog_errno(status);
+               goto out_err;
+       }
+       info->dqi_bgrace = le32_to_cpu(dinfo.dqi_bgrace);
+       info->dqi_igrace = le32_to_cpu(dinfo.dqi_igrace);
+       oinfo->dqi_syncms = le32_to_cpu(dinfo.dqi_syncms);
+       oinfo->dqi_gi.dqi_blocks = le32_to_cpu(dinfo.dqi_blocks);
+       oinfo->dqi_gi.dqi_free_blk = le32_to_cpu(dinfo.dqi_free_blk);
+       oinfo->dqi_gi.dqi_free_entry = le32_to_cpu(dinfo.dqi_free_entry);
+       oinfo->dqi_gi.dqi_blocksize_bits = sb->s_blocksize_bits;
+       oinfo->dqi_gi.dqi_usable_bs = sb->s_blocksize -
+                                               OCFS2_QBLK_RESERVED_SPACE;
+       oinfo->dqi_gi.dqi_qtree_depth = qtree_depth(&oinfo->dqi_gi);
+out_err:
+       mlog_exit(status);
+       return status;
+}
+
+/* Write information to global quota file. Expects exlusive lock on quota
+ * file inode and quota info */
+static int __ocfs2_global_write_info(struct super_block *sb, int type)
+{
+       struct mem_dqinfo *info = sb_dqinfo(sb, type);
+       struct ocfs2_mem_dqinfo *oinfo = info->dqi_priv;
+       struct ocfs2_global_disk_dqinfo dinfo;
+       ssize_t size;
+
+       spin_lock(&dq_data_lock);
+       info->dqi_flags &= ~DQF_INFO_DIRTY;
+       dinfo.dqi_bgrace = cpu_to_le32(info->dqi_bgrace);
+       dinfo.dqi_igrace = cpu_to_le32(info->dqi_igrace);
+       spin_unlock(&dq_data_lock);
+       dinfo.dqi_syncms = cpu_to_le32(oinfo->dqi_syncms);
+       dinfo.dqi_blocks = cpu_to_le32(oinfo->dqi_gi.dqi_blocks);
+       dinfo.dqi_free_blk = cpu_to_le32(oinfo->dqi_gi.dqi_free_blk);
+       dinfo.dqi_free_entry = cpu_to_le32(oinfo->dqi_gi.dqi_free_entry);
+       size = sb->s_op->quota_write(sb, type, (char *)&dinfo,
+                                    sizeof(struct ocfs2_global_disk_dqinfo),
+                                    OCFS2_GLOBAL_INFO_OFF);
+       if (size != sizeof(struct ocfs2_global_disk_dqinfo)) {
+               mlog(ML_ERROR, "Cannot write global quota info structure\n");
+               if (size >= 0)
+                       size = -EIO;
+               return size;
+       }
+       return 0;
+}
+
+int ocfs2_global_write_info(struct super_block *sb, int type)
+{
+       int err;
+       struct ocfs2_mem_dqinfo *info = sb_dqinfo(sb, type)->dqi_priv;
+
+       err = ocfs2_qinfo_lock(info, 1);
+       if (err < 0)
+               return err;
+       err = __ocfs2_global_write_info(sb, type);
+       ocfs2_qinfo_unlock(info, 1);
+       return err;
+}
+
+/* Read in information from global quota file and acquire a reference to it.
+ * dquot_acquire() has already started the transaction and locked quota file */
+int ocfs2_global_read_dquot(struct dquot *dquot)
+{
+       int err, err2, ex = 0;
+       struct ocfs2_mem_dqinfo *info =
+                       sb_dqinfo(dquot->dq_sb, dquot->dq_type)->dqi_priv;
+
+       err = ocfs2_qinfo_lock(info, 0);
+       if (err < 0)
+               goto out;
+       err = qtree_read_dquot(&info->dqi_gi, dquot);
+       if (err < 0)
+               goto out_qlock;
+       OCFS2_DQUOT(dquot)->dq_use_count++;
+       OCFS2_DQUOT(dquot)->dq_origspace = dquot->dq_dqb.dqb_curspace;
+       OCFS2_DQUOT(dquot)->dq_originodes = dquot->dq_dqb.dqb_curinodes;
+       if (!dquot->dq_off) {   /* No real quota entry? */
+               /* Upgrade to exclusive lock for allocation */
+               err = ocfs2_qinfo_lock(info, 1);
+               if (err < 0)
+                       goto out_qlock;
+               ex = 1;
+       }
+       err = qtree_write_dquot(&info->dqi_gi, dquot);
+       if (ex && info_dirty(sb_dqinfo(dquot->dq_sb, dquot->dq_type))) {
+               err2 = __ocfs2_global_write_info(dquot->dq_sb, dquot->dq_type);
+               if (!err)
+                       err = err2;
+       }
+out_qlock:
+       if (ex)
+               ocfs2_qinfo_unlock(info, 1);
+       ocfs2_qinfo_unlock(info, 0);
+out:
+       if (err < 0)
+               mlog_errno(err);
+       return err;
+}
+
+/* Sync local information about quota modifications with global quota file.
+ * Caller must have started the transaction and obtained exclusive lock for
+ * global quota file inode */
+int __ocfs2_sync_dquot(struct dquot *dquot, int freeing)
+{
+       int err, err2;
+       struct super_block *sb = dquot->dq_sb;
+       int type = dquot->dq_type;
+       struct ocfs2_mem_dqinfo *info = sb_dqinfo(sb, type)->dqi_priv;
+       struct ocfs2_global_disk_dqblk dqblk;
+       s64 spacechange, inodechange;
+       time_t olditime, oldbtime;
+
+       err = sb->s_op->quota_read(sb, type, (char *)&dqblk,
+                                  sizeof(struct ocfs2_global_disk_dqblk),
+                                  dquot->dq_off);
+       if (err != sizeof(struct ocfs2_global_disk_dqblk)) {
+               if (err >= 0) {
+                       mlog(ML_ERROR, "Short read from global quota file "
+                                      "(%u read)\n", err);
+                       err = -EIO;
+               }
+               goto out;
+       }
+
+       /* Update space and inode usage. Get also other information from
+        * global quota file so that we don't overwrite any changes there.
+        * We are */
+       spin_lock(&dq_data_lock);
+       spacechange = dquot->dq_dqb.dqb_curspace -
+                                       OCFS2_DQUOT(dquot)->dq_origspace;
+       inodechange = dquot->dq_dqb.dqb_curinodes -
+                                       OCFS2_DQUOT(dquot)->dq_originodes;
+       olditime = dquot->dq_dqb.dqb_itime;
+       oldbtime = dquot->dq_dqb.dqb_btime;
+       ocfs2_global_disk2memdqb(dquot, &dqblk);
+       mlog(0, "Syncing global dquot %d space %lld+%lld, inodes %lld+%lld\n",
+            dquot->dq_id, dquot->dq_dqb.dqb_curspace, spacechange,
+            dquot->dq_dqb.dqb_curinodes, inodechange);
+       if (!test_bit(DQ_LASTSET_B + QIF_SPACE_B, &dquot->dq_flags))
+               dquot->dq_dqb.dqb_curspace += spacechange;
+       if (!test_bit(DQ_LASTSET_B + QIF_INODES_B, &dquot->dq_flags))
+               dquot->dq_dqb.dqb_curinodes += inodechange;
+       /* Set properly space grace time... */
+       if (dquot->dq_dqb.dqb_bsoftlimit &&
+           dquot->dq_dqb.dqb_curspace > dquot->dq_dqb.dqb_bsoftlimit) {
+               if (!test_bit(DQ_LASTSET_B + QIF_BTIME_B, &dquot->dq_flags) &&
+                   oldbtime > 0) {
+                       if (dquot->dq_dqb.dqb_btime > 0)
+                               dquot->dq_dqb.dqb_btime =
+                                       min(dquot->dq_dqb.dqb_btime, oldbtime);
+                       else
+                               dquot->dq_dqb.dqb_btime = oldbtime;
+               }
+       } else {
+               dquot->dq_dqb.dqb_btime = 0;
+               clear_bit(DQ_BLKS_B, &dquot->dq_flags);
+       }
+       /* Set properly inode grace time... */
+       if (dquot->dq_dqb.dqb_isoftlimit &&
+           dquot->dq_dqb.dqb_curinodes > dquot->dq_dqb.dqb_isoftlimit) {
+               if (!test_bit(DQ_LASTSET_B + QIF_ITIME_B, &dquot->dq_flags) &&
+                   olditime > 0) {
+                       if (dquot->dq_dqb.dqb_itime > 0)
+                               dquot->dq_dqb.dqb_itime =
+                                       min(dquot->dq_dqb.dqb_itime, olditime);
+                       else
+                               dquot->dq_dqb.dqb_itime = olditime;
+               }
+       } else {
+               dquot->dq_dqb.dqb_itime = 0;
+               clear_bit(DQ_INODES_B, &dquot->dq_flags);
+       }
+       /* All information is properly updated, clear the flags */
+       __clear_bit(DQ_LASTSET_B + QIF_SPACE_B, &dquot->dq_flags);
+       __clear_bit(DQ_LASTSET_B + QIF_INODES_B, &dquot->dq_flags);
+       __clear_bit(DQ_LASTSET_B + QIF_BLIMITS_B, &dquot->dq_flags);
+       __clear_bit(DQ_LASTSET_B + QIF_ILIMITS_B, &dquot->dq_flags);
+       __clear_bit(DQ_LASTSET_B + QIF_BTIME_B, &dquot->dq_flags);
+       __clear_bit(DQ_LASTSET_B + QIF_ITIME_B, &dquot->dq_flags);
+       OCFS2_DQUOT(dquot)->dq_origspace = dquot->dq_dqb.dqb_curspace;
+       OCFS2_DQUOT(dquot)->dq_originodes = dquot->dq_dqb.dqb_curinodes;
+       spin_unlock(&dq_data_lock);
+       err = ocfs2_qinfo_lock(info, freeing);
+       if (err < 0) {
+               mlog(ML_ERROR, "Failed to lock quota info, loosing quota write"
+                              " (type=%d, id=%u)\n", dquot->dq_type,
+                              (unsigned)dquot->dq_id);
+               goto out;
+       }
+       if (freeing)
+               OCFS2_DQUOT(dquot)->dq_use_count--;
+       err = qtree_write_dquot(&info->dqi_gi, dquot);
+       if (err < 0)
+               goto out_qlock;
+       if (freeing && !OCFS2_DQUOT(dquot)->dq_use_count) {
+               err = qtree_release_dquot(&info->dqi_gi, dquot);
+               if (info_dirty(sb_dqinfo(sb, type))) {
+                       err2 = __ocfs2_global_write_info(sb, type);
+                       if (!err)
+                               err = err2;
+               }
+       }
+out_qlock:
+       ocfs2_qinfo_unlock(info, freeing);
+out:
+       if (err < 0)
+               mlog_errno(err);
+       return err;
+}
+
+/*
+ *  Wrappers for generic quota functions
+ */
+
+static int ocfs2_write_dquot(struct dquot *dquot)
+{
+       handle_t *handle;
+       struct ocfs2_super *osb = OCFS2_SB(dquot->dq_sb);
+       int status = 0;
+
+       mlog_entry("id=%u, type=%d", dquot->dq_id, dquot->dq_type);
+
+       handle = ocfs2_start_trans(osb, OCFS2_QWRITE_CREDITS);
+       if (IS_ERR(handle)) {
+               status = PTR_ERR(handle);
+               mlog_errno(status);
+               goto out;
+       }
+       status = dquot_commit(dquot);
+       ocfs2_commit_trans(osb, handle);
+out:
+       mlog_exit(status);
+       return status;
+}
+
+int ocfs2_calc_qdel_credits(struct super_block *sb, int type)
+{
+       struct ocfs2_mem_dqinfo *oinfo;
+       int features[MAXQUOTAS] = { OCFS2_FEATURE_RO_COMPAT_USRQUOTA,
+                                   OCFS2_FEATURE_RO_COMPAT_GRPQUOTA };
+
+       if (!OCFS2_HAS_RO_COMPAT_FEATURE(sb, features[type]))
+               return 0;
+
+       oinfo = sb_dqinfo(sb, type)->dqi_priv;
+       /* We modify tree, leaf block, global info, local chunk header,
+        * global and local inode */
+       return oinfo->dqi_gi.dqi_qtree_depth + 2 + 1 +
+              2 * OCFS2_INODE_UPDATE_CREDITS;
+}
+
+static int ocfs2_release_dquot(struct dquot *dquot)
+{
+       handle_t *handle;
+       struct ocfs2_mem_dqinfo *oinfo =
+                       sb_dqinfo(dquot->dq_sb, dquot->dq_type)->dqi_priv;
+       struct ocfs2_super *osb = OCFS2_SB(dquot->dq_sb);
+       int status = 0;
+
+       mlog_entry("id=%u, type=%d", dquot->dq_id, dquot->dq_type);
+
+       status = ocfs2_lock_global_qf(oinfo, 1);
+       if (status < 0)
+               goto out;
+       handle = ocfs2_start_trans(osb,
+               ocfs2_calc_qdel_credits(dquot->dq_sb, dquot->dq_type));
+       if (IS_ERR(handle)) {
+               status = PTR_ERR(handle);
+               mlog_errno(status);
+               goto out_ilock;
+       }
+       status = dquot_release(dquot);
+       ocfs2_commit_trans(osb, handle);
+out_ilock:
+       ocfs2_unlock_global_qf(oinfo, 1);
+out:
+       mlog_exit(status);
+       return status;
+}
+
+int ocfs2_calc_qinit_credits(struct super_block *sb, int type)
+{
+       struct ocfs2_mem_dqinfo *oinfo;
+       int features[MAXQUOTAS] = { OCFS2_FEATURE_RO_COMPAT_USRQUOTA,
+                                   OCFS2_FEATURE_RO_COMPAT_GRPQUOTA };
+       struct ocfs2_dinode *lfe, *gfe;
+
+       if (!OCFS2_HAS_RO_COMPAT_FEATURE(sb, features[type]))
+               return 0;
+
+       oinfo = sb_dqinfo(sb, type)->dqi_priv;
+       gfe = (struct ocfs2_dinode *)oinfo->dqi_gqi_bh->b_data;
+       lfe = (struct ocfs2_dinode *)oinfo->dqi_lqi_bh->b_data;
+       /* We can extend local file + global file. In local file we
+        * can modify info, chunk header block and dquot block. In
+        * global file we can modify info, tree and leaf block */
+       return ocfs2_calc_extend_credits(sb, &lfe->id2.i_list, 0) +
+              ocfs2_calc_extend_credits(sb, &gfe->id2.i_list, 0) +
+              3 + oinfo->dqi_gi.dqi_qtree_depth + 2;
+}
+
+static int ocfs2_acquire_dquot(struct dquot *dquot)
+{
+       handle_t *handle;
+       struct ocfs2_mem_dqinfo *oinfo =
+                       sb_dqinfo(dquot->dq_sb, dquot->dq_type)->dqi_priv;
+       struct ocfs2_super *osb = OCFS2_SB(dquot->dq_sb);
+       int status = 0;
+
+       mlog_entry("id=%u, type=%d", dquot->dq_id, dquot->dq_type);
+       /* We need an exclusive lock, because we're going to update use count
+        * and instantiate possibly new dquot structure */
+       status = ocfs2_lock_global_qf(oinfo, 1);
+       if (status < 0)
+               goto out;
+       handle = ocfs2_start_trans(osb,
+               ocfs2_calc_qinit_credits(dquot->dq_sb, dquot->dq_type));
+       if (IS_ERR(handle)) {
+               status = PTR_ERR(handle);
+               mlog_errno(status);
+               goto out_ilock;
+       }
+       status = dquot_acquire(dquot);
+       ocfs2_commit_trans(osb, handle);
+out_ilock:
+       ocfs2_unlock_global_qf(oinfo, 1);
+out:
+       mlog_exit(status);
+       return status;
+}
+
+static int ocfs2_mark_dquot_dirty(struct dquot *dquot)
+{
+       unsigned long mask = (1 << (DQ_LASTSET_B + QIF_ILIMITS_B)) |
+                            (1 << (DQ_LASTSET_B + QIF_BLIMITS_B)) |
+                            (1 << (DQ_LASTSET_B + QIF_INODES_B)) |
+                            (1 << (DQ_LASTSET_B + QIF_SPACE_B)) |
+                            (1 << (DQ_LASTSET_B + QIF_BTIME_B)) |
+                            (1 << (DQ_LASTSET_B + QIF_ITIME_B));
+       int sync = 0;
+       int status;
+       struct super_block *sb = dquot->dq_sb;
+       int type = dquot->dq_type;
+       struct ocfs2_mem_dqinfo *oinfo = sb_dqinfo(sb, type)->dqi_priv;
+       handle_t *handle;
+       struct ocfs2_super *osb = OCFS2_SB(sb);
+
+       mlog_entry("id=%u, type=%d", dquot->dq_id, type);
+       dquot_mark_dquot_dirty(dquot);
+
+       /* In case user set some limits, sync dquot immediately to global
+        * quota file so that information propagates quicker */
+       spin_lock(&dq_data_lock);
+       if (dquot->dq_flags & mask)
+               sync = 1;
+       spin_unlock(&dq_data_lock);
+       if (!sync) {
+               status = ocfs2_write_dquot(dquot);
+               goto out;
+       }
+       status = ocfs2_lock_global_qf(oinfo, 1);
+       if (status < 0)
+               goto out;
+       handle = ocfs2_start_trans(osb, OCFS2_QSYNC_CREDITS);
+       if (IS_ERR(handle)) {
+               status = PTR_ERR(handle);
+               mlog_errno(status);
+               goto out_ilock;
+       }
+       status = ocfs2_sync_dquot(dquot);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_trans;
+       }
+       /* Now write updated local dquot structure */
+       status = dquot_commit(dquot);
+out_trans:
+       ocfs2_commit_trans(osb, handle);
+out_ilock:
+       ocfs2_unlock_global_qf(oinfo, 1);
+out:
+       mlog_exit(status);
+       return status;
+}
+
+/* This should happen only after set_dqinfo(). */
+static int ocfs2_write_info(struct super_block *sb, int type)
+{
+       handle_t *handle;
+       int status = 0;
+       struct ocfs2_mem_dqinfo *oinfo = sb_dqinfo(sb, type)->dqi_priv;
+
+       mlog_entry_void();
+
+       status = ocfs2_lock_global_qf(oinfo, 1);
+       if (status < 0)
+               goto out;
+       handle = ocfs2_start_trans(OCFS2_SB(sb), OCFS2_QINFO_WRITE_CREDITS);
+       if (IS_ERR(handle)) {
+               status = PTR_ERR(handle);
+               mlog_errno(status);
+               goto out_ilock;
+       }
+       status = dquot_commit_info(sb, type);
+       ocfs2_commit_trans(OCFS2_SB(sb), handle);
+out_ilock:
+       ocfs2_unlock_global_qf(oinfo, 1);
+out:
+       mlog_exit(status);
+       return status;
+}
+
+/* This is difficult. We have to lock quota inode and start transaction
+ * in this function but we don't want to take the penalty of exlusive
+ * quota file lock when we are just going to use cached structures. So
+ * we just take read lock check whether we have dquot cached and if so,
+ * we don't have to take the write lock... */
+static int ocfs2_dquot_initialize(struct inode *inode, int type)
+{
+       handle_t *handle = NULL;
+       int status = 0;
+       struct super_block *sb = inode->i_sb;
+       struct ocfs2_mem_dqinfo *oinfo;
+       int exclusive = 0;
+       int cnt;
+       qid_t id;
+
+       mlog_entry_void();
+
+       for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
+               if (type != -1 && cnt != type)
+                       continue;
+               if (!sb_has_quota_active(sb, cnt))
+                       continue;
+               oinfo = sb_dqinfo(sb, cnt)->dqi_priv;
+               status = ocfs2_lock_global_qf(oinfo, 0);
+               if (status < 0)
+                       goto out;
+               /* This is just a performance optimization not a reliable test.
+                * Since we hold an inode lock, noone can actually release
+                * the structure until we are finished with initialization. */
+               if (inode->i_dquot[cnt] != NODQUOT) {
+                       ocfs2_unlock_global_qf(oinfo, 0);
+                       continue;
+               }
+               /* When we have inode lock, we know that no dquot_release() can
+                * run and thus we can safely check whether we need to
+                * read+modify global file to get quota information or whether
+                * our node already has it. */
+               if (cnt == USRQUOTA)
+                       id = inode->i_uid;
+               else if (cnt == GRPQUOTA)
+                       id = inode->i_gid;
+               else
+                       BUG();
+               /* Obtain exclusion from quota off... */
+               down_write(&sb_dqopt(sb)->dqptr_sem);
+               exclusive = !dquot_is_cached(sb, id, cnt);
+               up_write(&sb_dqopt(sb)->dqptr_sem);
+               if (exclusive) {
+                       status = ocfs2_lock_global_qf(oinfo, 1);
+                       if (status < 0) {
+                               exclusive = 0;
+                               mlog_errno(status);
+                               goto out_ilock;
+                       }
+                       handle = ocfs2_start_trans(OCFS2_SB(sb),
+                                       ocfs2_calc_qinit_credits(sb, cnt));
+                       if (IS_ERR(handle)) {
+                               status = PTR_ERR(handle);
+                               mlog_errno(status);
+                               goto out_ilock;
+                       }
+               }
+               dquot_initialize(inode, cnt);
+               if (exclusive) {
+                       ocfs2_commit_trans(OCFS2_SB(sb), handle);
+                       ocfs2_unlock_global_qf(oinfo, 1);
+               }
+               ocfs2_unlock_global_qf(oinfo, 0);
+       }
+       mlog_exit(0);
+       return 0;
+out_ilock:
+       if (exclusive)
+               ocfs2_unlock_global_qf(oinfo, 1);
+       ocfs2_unlock_global_qf(oinfo, 0);
+out:
+       mlog_exit(status);
+       return status;
+}
+
+static int ocfs2_dquot_drop_slow(struct inode *inode)
+{
+       int status;
+       int cnt;
+       int got_lock[MAXQUOTAS] = {0, 0};
+       handle_t *handle;
+       struct super_block *sb = inode->i_sb;
+       struct ocfs2_mem_dqinfo *oinfo;
+
+       for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
+               if (!sb_has_quota_active(sb, cnt))
+                       continue;
+               oinfo = sb_dqinfo(sb, cnt)->dqi_priv;
+               status = ocfs2_lock_global_qf(oinfo, 1);
+               if (status < 0)
+                       goto out;
+               got_lock[cnt] = 1;
+       }
+       handle = ocfs2_start_trans(OCFS2_SB(sb),
+                       ocfs2_calc_qinit_credits(sb, USRQUOTA) +
+                       ocfs2_calc_qinit_credits(sb, GRPQUOTA));
+       if (IS_ERR(handle)) {
+               status = PTR_ERR(handle);
+               mlog_errno(status);
+                               goto out;
+       }
+       dquot_drop(inode);
+       ocfs2_commit_trans(OCFS2_SB(sb), handle);
+out:
+       for (cnt = 0; cnt < MAXQUOTAS; cnt++)
+               if (got_lock[cnt]) {
+                       oinfo = sb_dqinfo(sb, cnt)->dqi_priv;
+                       ocfs2_unlock_global_qf(oinfo, 1);
+               }
+       return status;
+}
+
+/* See the comment before ocfs2_dquot_initialize. */
+static int ocfs2_dquot_drop(struct inode *inode)
+{
+       int status = 0;
+       struct super_block *sb = inode->i_sb;
+       struct ocfs2_mem_dqinfo *oinfo;
+       int exclusive = 0;
+       int cnt;
+       int got_lock[MAXQUOTAS] = {0, 0};
+
+       mlog_entry_void();
+       for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
+               if (!sb_has_quota_active(sb, cnt))
+                       continue;
+               oinfo = sb_dqinfo(sb, cnt)->dqi_priv;
+               status = ocfs2_lock_global_qf(oinfo, 0);
+               if (status < 0)
+                       goto out;
+               got_lock[cnt] = 1;
+       }
+       /* Lock against anyone releasing references so that when when we check
+        * we know we are not going to be last ones to release dquot */
+       down_write(&sb_dqopt(sb)->dqptr_sem);
+       /* Urgh, this is a terrible hack :( */
+       for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
+               if (inode->i_dquot[cnt] != NODQUOT &&
+                   atomic_read(&inode->i_dquot[cnt]->dq_count) > 1) {
+                       exclusive = 1;
+                       break;
+               }
+       }
+       if (!exclusive)
+               dquot_drop_locked(inode);
+       up_write(&sb_dqopt(sb)->dqptr_sem);
+out:
+       for (cnt = 0; cnt < MAXQUOTAS; cnt++)
+               if (got_lock[cnt]) {
+                       oinfo = sb_dqinfo(sb, cnt)->dqi_priv;
+                       ocfs2_unlock_global_qf(oinfo, 0);
+               }
+       /* In case we bailed out because we had to do expensive locking
+        * do it now... */
+       if (exclusive)
+               status = ocfs2_dquot_drop_slow(inode);
+       mlog_exit(status);
+       return status;
+}
+
+static struct dquot *ocfs2_alloc_dquot(struct super_block *sb, int type)
+{
+       struct ocfs2_dquot *dquot =
+                               kmem_cache_zalloc(ocfs2_dquot_cachep, GFP_NOFS);
+
+       if (!dquot)
+               return NULL;
+       return &dquot->dq_dquot;
+}
+
+static void ocfs2_destroy_dquot(struct dquot *dquot)
+{
+       kmem_cache_free(ocfs2_dquot_cachep, dquot);
+}
+
+struct dquot_operations ocfs2_quota_operations = {
+       .initialize     = ocfs2_dquot_initialize,
+       .drop           = ocfs2_dquot_drop,
+       .alloc_space    = dquot_alloc_space,
+       .alloc_inode    = dquot_alloc_inode,
+       .free_space     = dquot_free_space,
+       .free_inode     = dquot_free_inode,
+       .transfer       = dquot_transfer,
+       .write_dquot    = ocfs2_write_dquot,
+       .acquire_dquot  = ocfs2_acquire_dquot,
+       .release_dquot  = ocfs2_release_dquot,
+       .mark_dirty     = ocfs2_mark_dquot_dirty,
+       .write_info     = ocfs2_write_info,
+       .alloc_dquot    = ocfs2_alloc_dquot,
+       .destroy_dquot  = ocfs2_destroy_dquot,
+};
 
--- /dev/null
+/*
+ *  Implementation of operations over local quota file
+ */
+
+#include <linux/fs.h>
+#include <linux/quota.h>
+#include <linux/quotaops.h>
+#include <linux/module.h>
+
+#define MLOG_MASK_PREFIX ML_QUOTA
+#include <cluster/masklog.h>
+
+#include "ocfs2_fs.h"
+#include "ocfs2.h"
+#include "inode.h"
+#include "alloc.h"
+#include "file.h"
+#include "buffer_head_io.h"
+#include "journal.h"
+#include "sysfile.h"
+#include "dlmglue.h"
+#include "quota.h"
+
+/* Number of local quota structures per block */
+static inline unsigned int ol_quota_entries_per_block(struct super_block *sb)
+{
+       return ((sb->s_blocksize - OCFS2_QBLK_RESERVED_SPACE) /
+               sizeof(struct ocfs2_local_disk_dqblk));
+}
+
+/* Number of blocks with entries in one chunk */
+static inline unsigned int ol_chunk_blocks(struct super_block *sb)
+{
+       return ((sb->s_blocksize - sizeof(struct ocfs2_local_disk_chunk) -
+                OCFS2_QBLK_RESERVED_SPACE) << 3) /
+              ol_quota_entries_per_block(sb);
+}
+
+/* Number of entries in a chunk bitmap */
+static unsigned int ol_chunk_entries(struct super_block *sb)
+{
+       return ol_chunk_blocks(sb) * ol_quota_entries_per_block(sb);
+}
+
+/* Offset of the chunk in quota file */
+static unsigned int ol_quota_chunk_block(struct super_block *sb, int c)
+{
+       /* 1 block for local quota file info, 1 block per chunk for chunk info */
+       return 1 + (ol_chunk_blocks(sb) + 1) * c;
+}
+
+/* Offset of the dquot structure in the quota file */
+static loff_t ol_dqblk_off(struct super_block *sb, int c, int off)
+{
+       int epb = ol_quota_entries_per_block(sb);
+
+       return ((ol_quota_chunk_block(sb, c) + 1 + off / epb)
+               << sb->s_blocksize_bits) +
+               (off % epb) * sizeof(struct ocfs2_local_disk_dqblk);
+}
+
+/* Compute block number from given offset */
+static inline unsigned int ol_dqblk_file_block(struct super_block *sb, loff_t off)
+{
+       return off >> sb->s_blocksize_bits;
+}
+
+static inline unsigned int ol_dqblk_block_offset(struct super_block *sb, loff_t off)
+{
+       return off & ((1 << sb->s_blocksize_bits) - 1);
+}
+
+/* Compute offset in the chunk of a structure with the given offset */
+static int ol_dqblk_chunk_off(struct super_block *sb, int c, loff_t off)
+{
+       int epb = ol_quota_entries_per_block(sb);
+
+       return ((off >> sb->s_blocksize_bits) -
+                       ol_quota_chunk_block(sb, c) - 1) * epb
+              + ((unsigned int)(off & ((1 << sb->s_blocksize_bits) - 1))) /
+                sizeof(struct ocfs2_local_disk_dqblk);
+}
+
+/* Write bufferhead into the fs */
+static int ocfs2_modify_bh(struct inode *inode, struct buffer_head *bh,
+               void (*modify)(struct buffer_head *, void *), void *private)
+{
+       struct super_block *sb = inode->i_sb;
+       handle_t *handle;
+       int status;
+
+       handle = ocfs2_start_trans(OCFS2_SB(sb), 1);
+       if (IS_ERR(handle)) {
+               status = PTR_ERR(handle);
+               mlog_errno(status);
+               return status;
+       }
+       status = ocfs2_journal_access(handle, inode, bh,
+                                     OCFS2_JOURNAL_ACCESS_WRITE);
+       if (status < 0) {
+               mlog_errno(status);
+               ocfs2_commit_trans(OCFS2_SB(sb), handle);
+               return status;
+       }
+       lock_buffer(bh);
+       modify(bh, private);
+       unlock_buffer(bh);
+       status = ocfs2_journal_dirty(handle, bh);
+       if (status < 0) {
+               mlog_errno(status);
+               ocfs2_commit_trans(OCFS2_SB(sb), handle);
+               return status;
+       }
+       status = ocfs2_commit_trans(OCFS2_SB(sb), handle);
+       if (status < 0) {
+               mlog_errno(status);
+               return status;
+       }
+       return 0;
+}
+
+/* Check whether we understand format of quota files */
+static int ocfs2_local_check_quota_file(struct super_block *sb, int type)
+{
+       unsigned int lmagics[MAXQUOTAS] = OCFS2_LOCAL_QMAGICS;
+       unsigned int lversions[MAXQUOTAS] = OCFS2_LOCAL_QVERSIONS;
+       unsigned int gmagics[MAXQUOTAS] = OCFS2_GLOBAL_QMAGICS;
+       unsigned int gversions[MAXQUOTAS] = OCFS2_GLOBAL_QVERSIONS;
+       unsigned int ino[MAXQUOTAS] = { USER_QUOTA_SYSTEM_INODE,
+                                       GROUP_QUOTA_SYSTEM_INODE };
+       struct buffer_head *bh;
+       struct inode *linode = sb_dqopt(sb)->files[type];
+       struct inode *ginode = NULL;
+       struct ocfs2_disk_dqheader *dqhead;
+       int status, ret = 0;
+
+       /* First check whether we understand local quota file */
+       bh = ocfs2_read_quota_block(linode, 0, &status);
+       if (!bh) {
+               mlog_errno(status);
+               mlog(ML_ERROR, "failed to read quota file header (type=%d)\n",
+                       type);
+               goto out_err;
+       }
+       dqhead = (struct ocfs2_disk_dqheader *)(bh->b_data);
+       if (le32_to_cpu(dqhead->dqh_magic) != lmagics[type]) {
+               mlog(ML_ERROR, "quota file magic does not match (%u != %u),"
+                       " type=%d\n", le32_to_cpu(dqhead->dqh_magic),
+                       lmagics[type], type);
+               goto out_err;
+       }
+       if (le32_to_cpu(dqhead->dqh_version) != lversions[type]) {
+               mlog(ML_ERROR, "quota file version does not match (%u != %u),"
+                       " type=%d\n", le32_to_cpu(dqhead->dqh_version),
+                       lversions[type], type);
+               goto out_err;
+       }
+       brelse(bh);
+       bh = NULL;
+
+       /* Next check whether we understand global quota file */
+       ginode = ocfs2_get_system_file_inode(OCFS2_SB(sb), ino[type],
+                                               OCFS2_INVALID_SLOT);
+       if (!ginode) {
+               mlog(ML_ERROR, "cannot get global quota file inode "
+                               "(type=%d)\n", type);
+               goto out_err;
+       }
+       /* Since the header is read only, we don't care about locking */
+       bh = ocfs2_read_quota_block(ginode, 0, &status);
+       if (!bh) {
+               mlog_errno(status);
+               mlog(ML_ERROR, "failed to read global quota file header "
+                               "(type=%d)\n", type);
+               goto out_err;
+       }
+       dqhead = (struct ocfs2_disk_dqheader *)(bh->b_data);
+       if (le32_to_cpu(dqhead->dqh_magic) != gmagics[type]) {
+               mlog(ML_ERROR, "global quota file magic does not match "
+                       "(%u != %u), type=%d\n",
+                       le32_to_cpu(dqhead->dqh_magic), gmagics[type], type);
+               goto out_err;
+       }
+       if (le32_to_cpu(dqhead->dqh_version) != gversions[type]) {
+               mlog(ML_ERROR, "global quota file version does not match "
+                       "(%u != %u), type=%d\n",
+                       le32_to_cpu(dqhead->dqh_version), gversions[type],
+                       type);
+               goto out_err;
+       }
+
+       ret = 1;
+out_err:
+       brelse(bh);
+       iput(ginode);
+       return ret;
+}
+
+/* Release given list of quota file chunks */
+static void ocfs2_release_local_quota_bitmaps(struct list_head *head)
+{
+       struct ocfs2_quota_chunk *pos, *next;
+
+       list_for_each_entry_safe(pos, next, head, qc_chunk) {
+               list_del(&pos->qc_chunk);
+               brelse(pos->qc_headerbh);
+               kmem_cache_free(ocfs2_qf_chunk_cachep, pos);
+       }
+}
+
+/* Load quota bitmaps into memory */
+static int ocfs2_load_local_quota_bitmaps(struct inode *inode,
+                       struct ocfs2_local_disk_dqinfo *ldinfo,
+                       struct list_head *head)
+{
+       struct ocfs2_quota_chunk *newchunk;
+       int i, status;
+
+       INIT_LIST_HEAD(head);
+       for (i = 0; i < le32_to_cpu(ldinfo->dqi_chunks); i++) {
+               newchunk = kmem_cache_alloc(ocfs2_qf_chunk_cachep, GFP_NOFS);
+               if (!newchunk) {
+                       ocfs2_release_local_quota_bitmaps(head);
+                       return -ENOMEM;
+               }
+               newchunk->qc_num = i;
+               newchunk->qc_headerbh = ocfs2_read_quota_block(inode,
+                               ol_quota_chunk_block(inode->i_sb, i),
+                               &status);
+               if (!newchunk->qc_headerbh) {
+                       mlog_errno(status);
+                       kmem_cache_free(ocfs2_qf_chunk_cachep, newchunk);
+                       ocfs2_release_local_quota_bitmaps(head);
+                       return status;
+               }
+               list_add_tail(&newchunk->qc_chunk, head);
+       }
+       return 0;
+}
+
+static void olq_update_info(struct buffer_head *bh, void *private)
+{
+       struct mem_dqinfo *info = private;
+       struct ocfs2_mem_dqinfo *oinfo = info->dqi_priv;
+       struct ocfs2_local_disk_dqinfo *ldinfo;
+
+       ldinfo = (struct ocfs2_local_disk_dqinfo *)(bh->b_data +
+                                               OCFS2_LOCAL_INFO_OFF);
+       spin_lock(&dq_data_lock);
+       ldinfo->dqi_flags = cpu_to_le32(info->dqi_flags & DQF_MASK);
+       ldinfo->dqi_chunks = cpu_to_le32(oinfo->dqi_chunks);
+       ldinfo->dqi_blocks = cpu_to_le32(oinfo->dqi_blocks);
+       spin_unlock(&dq_data_lock);
+}
+
+/* Read information header from quota file */
+static int ocfs2_local_read_info(struct super_block *sb, int type)
+{
+       struct ocfs2_local_disk_dqinfo *ldinfo;
+       struct mem_dqinfo *info = sb_dqinfo(sb, type);
+       struct ocfs2_mem_dqinfo *oinfo;
+       struct inode *lqinode = sb_dqopt(sb)->files[type];
+       int status;
+       struct buffer_head *bh = NULL;
+       int locked = 0;
+
+       info->dqi_maxblimit = 0x7fffffffffffffffLL;
+       info->dqi_maxilimit = 0x7fffffffffffffffLL;
+       oinfo = kmalloc(sizeof(struct ocfs2_mem_dqinfo), GFP_NOFS);
+       if (!oinfo) {
+               mlog(ML_ERROR, "failed to allocate memory for ocfs2 quota"
+                              " info.");
+               goto out_err;
+       }
+       info->dqi_priv = oinfo;
+       oinfo->dqi_type = type;
+       INIT_LIST_HEAD(&oinfo->dqi_chunk);
+       oinfo->dqi_lqi_bh = NULL;
+       oinfo->dqi_ibh = NULL;
+
+       status = ocfs2_global_read_info(sb, type);
+       if (status < 0)
+               goto out_err;
+
+       status = ocfs2_inode_lock(lqinode, &oinfo->dqi_lqi_bh, 1);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_err;
+       }
+       locked = 1;
+
+       /* Now read local header */
+       bh = ocfs2_read_quota_block(lqinode, 0, &status);
+       if (!bh) {
+               mlog_errno(status);
+               mlog(ML_ERROR, "failed to read quota file info header "
+                       "(type=%d)\n", type);
+               goto out_err;
+       }
+       ldinfo = (struct ocfs2_local_disk_dqinfo *)(bh->b_data +
+                                               OCFS2_LOCAL_INFO_OFF);
+       info->dqi_flags = le32_to_cpu(ldinfo->dqi_flags);
+       oinfo->dqi_chunks = le32_to_cpu(ldinfo->dqi_chunks);
+       oinfo->dqi_blocks = le32_to_cpu(ldinfo->dqi_blocks);
+       oinfo->dqi_ibh = bh;
+
+       /* We crashed when using local quota file? */
+       if (!(info->dqi_flags & OLQF_CLEAN))
+               goto out_err;   /* So far we just bail out. Later we should resync here */
+
+       status = ocfs2_load_local_quota_bitmaps(sb_dqopt(sb)->files[type],
+                                               ldinfo,
+                                               &oinfo->dqi_chunk);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_err;
+       }
+
+       /* Now mark quota file as used */
+       info->dqi_flags &= ~OLQF_CLEAN;
+       status = ocfs2_modify_bh(lqinode, bh, olq_update_info, info);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_err;
+       }
+
+       return 0;
+out_err:
+       if (oinfo) {
+               iput(oinfo->dqi_gqinode);
+               ocfs2_simple_drop_lockres(OCFS2_SB(sb), &oinfo->dqi_gqlock);
+               ocfs2_lock_res_free(&oinfo->dqi_gqlock);
+               brelse(oinfo->dqi_lqi_bh);
+               if (locked)
+                       ocfs2_inode_unlock(lqinode, 1);
+               ocfs2_release_local_quota_bitmaps(&oinfo->dqi_chunk);
+               kfree(oinfo);
+       }
+       brelse(bh);
+       return -1;
+}
+
+/* Write local info to quota file */
+static int ocfs2_local_write_info(struct super_block *sb, int type)
+{
+       struct mem_dqinfo *info = sb_dqinfo(sb, type);
+       struct buffer_head *bh = ((struct ocfs2_mem_dqinfo *)info->dqi_priv)
+                                               ->dqi_ibh;
+       int status;
+
+       status = ocfs2_modify_bh(sb_dqopt(sb)->files[type], bh, olq_update_info,
+                                info);
+       if (status < 0) {
+               mlog_errno(status);
+               return -1;
+       }
+
+       return 0;
+}
+
+/* Release info from memory */
+static int ocfs2_local_free_info(struct super_block *sb, int type)
+{
+       struct mem_dqinfo *info = sb_dqinfo(sb, type);
+       struct ocfs2_mem_dqinfo *oinfo = info->dqi_priv;
+       struct ocfs2_quota_chunk *chunk;
+       struct ocfs2_local_disk_chunk *dchunk;
+       int mark_clean = 1, len;
+       int status;
+
+       iput(oinfo->dqi_gqinode);
+       ocfs2_simple_drop_lockres(OCFS2_SB(sb), &oinfo->dqi_gqlock);
+       ocfs2_lock_res_free(&oinfo->dqi_gqlock);
+       list_for_each_entry(chunk, &oinfo->dqi_chunk, qc_chunk) {
+               dchunk = (struct ocfs2_local_disk_chunk *)
+                                       (chunk->qc_headerbh->b_data);
+               if (chunk->qc_num < oinfo->dqi_chunks - 1) {
+                       len = ol_chunk_entries(sb);
+               } else {
+                       len = (oinfo->dqi_blocks -
+                              ol_quota_chunk_block(sb, chunk->qc_num) - 1)
+                             * ol_quota_entries_per_block(sb);
+               }
+               /* Not all entries free? Bug! */
+               if (le32_to_cpu(dchunk->dqc_free) != len) {
+                       mlog(ML_ERROR, "releasing quota file with used "
+                                       "entries (type=%d)\n", type);
+                       mark_clean = 0;
+               }
+       }
+       ocfs2_release_local_quota_bitmaps(&oinfo->dqi_chunk);
+
+       if (!mark_clean)
+               goto out;
+
+       /* Mark local file as clean */
+       info->dqi_flags |= OLQF_CLEAN;
+       status = ocfs2_modify_bh(sb_dqopt(sb)->files[type],
+                                oinfo->dqi_ibh,
+                                olq_update_info,
+                                info);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+
+out:
+       ocfs2_inode_unlock(sb_dqopt(sb)->files[type], 1);
+       brelse(oinfo->dqi_ibh);
+       brelse(oinfo->dqi_lqi_bh);
+       kfree(oinfo);
+       return 0;
+}
+
+static void olq_set_dquot(struct buffer_head *bh, void *private)
+{
+       struct ocfs2_dquot *od = private;
+       struct ocfs2_local_disk_dqblk *dqblk;
+       struct super_block *sb = od->dq_dquot.dq_sb;
+
+       dqblk = (struct ocfs2_local_disk_dqblk *)(bh->b_data
+               + ol_dqblk_block_offset(sb, od->dq_local_off));
+
+       dqblk->dqb_id = cpu_to_le64(od->dq_dquot.dq_id);
+       spin_lock(&dq_data_lock);
+       dqblk->dqb_spacemod = cpu_to_le64(od->dq_dquot.dq_dqb.dqb_curspace -
+                                         od->dq_origspace);
+       dqblk->dqb_inodemod = cpu_to_le64(od->dq_dquot.dq_dqb.dqb_curinodes -
+                                         od->dq_originodes);
+       spin_unlock(&dq_data_lock);
+       mlog(0, "Writing local dquot %u space %lld inodes %lld\n",
+            od->dq_dquot.dq_id, dqblk->dqb_spacemod, dqblk->dqb_inodemod);
+}
+
+/* Write dquot to local quota file */
+static int ocfs2_local_write_dquot(struct dquot *dquot)
+{
+       struct super_block *sb = dquot->dq_sb;
+       struct ocfs2_dquot *od = OCFS2_DQUOT(dquot);
+       struct buffer_head *bh;
+       int status;
+
+       bh = ocfs2_read_quota_block(sb_dqopt(sb)->files[dquot->dq_type],
+                                   ol_dqblk_file_block(sb, od->dq_local_off),
+                                   &status);
+       if (!bh) {
+               mlog_errno(status);
+               goto out;
+       }
+       status = ocfs2_modify_bh(sb_dqopt(sb)->files[dquot->dq_type], bh,
+                                olq_set_dquot, od);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+out:
+       brelse(bh);
+       return status;
+}
+
+/* Find free entry in local quota file */
+static struct ocfs2_quota_chunk *ocfs2_find_free_entry(struct super_block *sb,
+                                                      int type,
+                                                      int *offset)
+{
+       struct mem_dqinfo *info = sb_dqinfo(sb, type);
+       struct ocfs2_mem_dqinfo *oinfo = info->dqi_priv;
+       struct ocfs2_quota_chunk *chunk;
+       struct ocfs2_local_disk_chunk *dchunk;
+       int found = 0, len;
+
+       list_for_each_entry(chunk, &oinfo->dqi_chunk, qc_chunk) {
+               dchunk = (struct ocfs2_local_disk_chunk *)
+                                               chunk->qc_headerbh->b_data;
+               if (le32_to_cpu(dchunk->dqc_free) > 0) {
+                       found = 1;
+                       break;
+               }
+       }
+       if (!found)
+               return NULL;
+
+       if (chunk->qc_num < oinfo->dqi_chunks - 1) {
+               len = ol_chunk_entries(sb);
+       } else {
+               len = (oinfo->dqi_blocks -
+                      ol_quota_chunk_block(sb, chunk->qc_num) - 1)
+                     * ol_quota_entries_per_block(sb);
+       }
+
+       found = ocfs2_find_next_zero_bit(dchunk->dqc_bitmap, len, 0);
+       /* We failed? */
+       if (found == len) {
+               mlog(ML_ERROR, "Did not find empty entry in chunk %d with %u"
+                    " entries free (type=%d)\n", chunk->qc_num,
+                    le32_to_cpu(dchunk->dqc_free), type);
+               return ERR_PTR(-EIO);
+       }
+       *offset = found;
+       return chunk;
+}
+
+/* Add new chunk to the local quota file */
+static struct ocfs2_quota_chunk *ocfs2_local_quota_add_chunk(
+                                                       struct super_block *sb,
+                                                       int type,
+                                                       int *offset)
+{
+       struct mem_dqinfo *info = sb_dqinfo(sb, type);
+       struct ocfs2_mem_dqinfo *oinfo = info->dqi_priv;
+       struct inode *lqinode = sb_dqopt(sb)->files[type];
+       struct ocfs2_quota_chunk *chunk = NULL;
+       struct ocfs2_local_disk_chunk *dchunk;
+       int status;
+       handle_t *handle;
+       struct buffer_head *bh = NULL;
+       u64 p_blkno;
+
+       /* We are protected by dqio_sem so no locking needed */
+       status = ocfs2_extend_no_holes(lqinode,
+                                      lqinode->i_size + 2 * sb->s_blocksize,
+                                      lqinode->i_size);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+       status = ocfs2_simple_size_update(lqinode, oinfo->dqi_lqi_bh,
+                                         lqinode->i_size + 2 * sb->s_blocksize);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+
+       chunk = kmem_cache_alloc(ocfs2_qf_chunk_cachep, GFP_NOFS);
+       if (!chunk) {
+               status = -ENOMEM;
+               mlog_errno(status);
+               goto out;
+       }
+
+       down_read(&OCFS2_I(lqinode)->ip_alloc_sem);
+       status = ocfs2_extent_map_get_blocks(lqinode, oinfo->dqi_blocks,
+                                            &p_blkno, NULL, NULL);
+       up_read(&OCFS2_I(lqinode)->ip_alloc_sem);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+       bh = sb_getblk(sb, p_blkno);
+       if (!bh) {
+               status = -ENOMEM;
+               mlog_errno(status);
+               goto out;
+       }
+       dchunk = (struct ocfs2_local_disk_chunk *)bh->b_data;
+
+       handle = ocfs2_start_trans(OCFS2_SB(sb), 2);
+       if (IS_ERR(handle)) {
+               status = PTR_ERR(handle);
+               mlog_errno(status);
+               goto out;
+       }
+
+       status = ocfs2_journal_access(handle, lqinode, bh,
+                                     OCFS2_JOURNAL_ACCESS_WRITE);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_trans;
+       }
+       lock_buffer(bh);
+       dchunk->dqc_free = ol_quota_entries_per_block(sb);
+       memset(dchunk->dqc_bitmap, 0,
+              sb->s_blocksize - sizeof(struct ocfs2_local_disk_chunk) -
+              OCFS2_QBLK_RESERVED_SPACE);
+       set_buffer_uptodate(bh);
+       unlock_buffer(bh);
+       status = ocfs2_journal_dirty(handle, bh);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_trans;
+       }
+
+       oinfo->dqi_blocks += 2;
+       oinfo->dqi_chunks++;
+       status = ocfs2_local_write_info(sb, type);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_trans;
+       }
+       status = ocfs2_commit_trans(OCFS2_SB(sb), handle);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+
+       list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
+       chunk->qc_num = list_entry(chunk->qc_chunk.prev,
+                                  struct ocfs2_quota_chunk,
+                                  qc_chunk)->qc_num + 1;
+       chunk->qc_headerbh = bh;
+       *offset = 0;
+       return chunk;
+out_trans:
+       ocfs2_commit_trans(OCFS2_SB(sb), handle);
+out:
+       brelse(bh);
+       kmem_cache_free(ocfs2_qf_chunk_cachep, chunk);
+       return ERR_PTR(status);
+}
+
+/* Find free entry in local quota file */
+static struct ocfs2_quota_chunk *ocfs2_extend_local_quota_file(
+                                                      struct super_block *sb,
+                                                      int type,
+                                                      int *offset)
+{
+       struct mem_dqinfo *info = sb_dqinfo(sb, type);
+       struct ocfs2_mem_dqinfo *oinfo = info->dqi_priv;
+       struct ocfs2_quota_chunk *chunk;
+       struct inode *lqinode = sb_dqopt(sb)->files[type];
+       struct ocfs2_local_disk_chunk *dchunk;
+       int epb = ol_quota_entries_per_block(sb);
+       unsigned int chunk_blocks;
+       int status;
+       handle_t *handle;
+
+       if (list_empty(&oinfo->dqi_chunk))
+               return ocfs2_local_quota_add_chunk(sb, type, offset);
+       /* Is the last chunk full? */
+       chunk = list_entry(oinfo->dqi_chunk.prev,
+                       struct ocfs2_quota_chunk, qc_chunk);
+       chunk_blocks = oinfo->dqi_blocks -
+                       ol_quota_chunk_block(sb, chunk->qc_num) - 1;
+       if (ol_chunk_blocks(sb) == chunk_blocks)
+               return ocfs2_local_quota_add_chunk(sb, type, offset);
+
+       /* We are protected by dqio_sem so no locking needed */
+       status = ocfs2_extend_no_holes(lqinode,
+                                      lqinode->i_size + sb->s_blocksize,
+                                      lqinode->i_size);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+       status = ocfs2_simple_size_update(lqinode, oinfo->dqi_lqi_bh,
+                                         lqinode->i_size + sb->s_blocksize);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+       handle = ocfs2_start_trans(OCFS2_SB(sb), 2);
+       if (IS_ERR(handle)) {
+               status = PTR_ERR(handle);
+               mlog_errno(status);
+               goto out;
+       }
+       status = ocfs2_journal_access(handle, lqinode, chunk->qc_headerbh,
+                                OCFS2_JOURNAL_ACCESS_WRITE);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_trans;
+       }
+
+       dchunk = (struct ocfs2_local_disk_chunk *)chunk->qc_headerbh->b_data;
+       lock_buffer(chunk->qc_headerbh);
+       le32_add_cpu(&dchunk->dqc_free, ol_quota_entries_per_block(sb));
+       unlock_buffer(chunk->qc_headerbh);
+       status = ocfs2_journal_dirty(handle, chunk->qc_headerbh);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_trans;
+       }
+       oinfo->dqi_blocks++;
+       status = ocfs2_local_write_info(sb, type);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_trans;
+       }
+
+       status = ocfs2_commit_trans(OCFS2_SB(sb), handle);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+       *offset = chunk_blocks * epb;
+       return chunk;
+out_trans:
+       ocfs2_commit_trans(OCFS2_SB(sb), handle);
+out:
+       return ERR_PTR(status);
+}
+
+void olq_alloc_dquot(struct buffer_head *bh, void *private)
+{
+       int *offset = private;
+       struct ocfs2_local_disk_chunk *dchunk;
+
+       dchunk = (struct ocfs2_local_disk_chunk *)bh->b_data;
+       ocfs2_set_bit(*offset, dchunk->dqc_bitmap);
+       le32_add_cpu(&dchunk->dqc_free, -1);
+}
+
+/* Create dquot in the local file for given id */
+static int ocfs2_create_local_dquot(struct dquot *dquot)
+{
+       struct super_block *sb = dquot->dq_sb;
+       int type = dquot->dq_type;
+       struct inode *lqinode = sb_dqopt(sb)->files[type];
+       struct ocfs2_quota_chunk *chunk;
+       struct ocfs2_dquot *od = OCFS2_DQUOT(dquot);
+       int offset;
+       int status;
+
+       chunk = ocfs2_find_free_entry(sb, type, &offset);
+       if (!chunk) {
+               chunk = ocfs2_extend_local_quota_file(sb, type, &offset);
+               if (IS_ERR(chunk))
+                       return PTR_ERR(chunk);
+       } else if (IS_ERR(chunk)) {
+               return PTR_ERR(chunk);
+       }
+       od->dq_local_off = ol_dqblk_off(sb, chunk->qc_num, offset);
+       od->dq_chunk = chunk;
+
+       /* Initialize dquot structure on disk */
+       status = ocfs2_local_write_dquot(dquot);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+
+       /* Mark structure as allocated */
+       status = ocfs2_modify_bh(lqinode, chunk->qc_headerbh, olq_alloc_dquot,
+                                &offset);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+out:
+       return status;
+}
+
+/* Create entry in local file for dquot, load data from the global file */
+static int ocfs2_local_read_dquot(struct dquot *dquot)
+{
+       int status;
+
+       mlog_entry("id=%u, type=%d\n", dquot->dq_id, dquot->dq_type);
+
+       status = ocfs2_global_read_dquot(dquot);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_err;
+       }
+
+       /* Now create entry in the local quota file */
+       status = ocfs2_create_local_dquot(dquot);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out_err;
+       }
+       mlog_exit(0);
+       return 0;
+out_err:
+       mlog_exit(status);
+       return status;
+}
+
+/* Release dquot structure from local quota file. ocfs2_release_dquot() has
+ * already started a transaction and obtained exclusive lock for global
+ * quota file. */
+static int ocfs2_local_release_dquot(struct dquot *dquot)
+{
+       int status;
+       int type = dquot->dq_type;
+       struct ocfs2_dquot *od = OCFS2_DQUOT(dquot);
+       struct super_block *sb = dquot->dq_sb;
+       struct ocfs2_local_disk_chunk *dchunk;
+       int offset;
+       handle_t *handle = journal_current_handle();
+
+       BUG_ON(!handle);
+       /* First write all local changes to global file */
+       status = ocfs2_global_release_dquot(dquot);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+
+       status = ocfs2_journal_access(handle, sb_dqopt(sb)->files[type],
+                       od->dq_chunk->qc_headerbh, OCFS2_JOURNAL_ACCESS_WRITE);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+       offset = ol_dqblk_chunk_off(sb, od->dq_chunk->qc_num,
+                                            od->dq_local_off);
+       dchunk = (struct ocfs2_local_disk_chunk *)
+                       (od->dq_chunk->qc_headerbh->b_data);
+       /* Mark structure as freed */
+       lock_buffer(od->dq_chunk->qc_headerbh);
+       ocfs2_clear_bit(offset, dchunk->dqc_bitmap);
+       le32_add_cpu(&dchunk->dqc_free, 1);
+       unlock_buffer(od->dq_chunk->qc_headerbh);
+       status = ocfs2_journal_dirty(handle, od->dq_chunk->qc_headerbh);
+       if (status < 0) {
+               mlog_errno(status);
+               goto out;
+       }
+       status = 0;
+out:
+       /* Clear the read bit so that next time someone uses this
+        * dquot he reads fresh info from disk and allocates local
+        * dquot structure */
+       clear_bit(DQ_READ_B, &dquot->dq_flags);
+       return status;
+}
+
+static struct quota_format_ops ocfs2_format_ops = {
+       .check_quota_file       = ocfs2_local_check_quota_file,
+       .read_file_info         = ocfs2_local_read_info,
+       .write_file_info        = ocfs2_global_write_info,
+       .free_file_info         = ocfs2_local_free_info,
+       .read_dqblk             = ocfs2_local_read_dquot,
+       .commit_dqblk           = ocfs2_local_write_dquot,
+       .release_dqblk          = ocfs2_local_release_dquot,
+};
+
+struct quota_format_type ocfs2_quota_format = {
+       .qf_fmt_id = QFMT_OCFS2,
+       .qf_ops = &ocfs2_format_ops,
+       .qf_owner = THIS_MODULE
+};
 
 #include "uptodate.h"
 #include "ver.h"
 #include "xattr.h"
+#include "quota.h"
 
 #include "buffer_head_io.h"
 
 static struct kmem_cache *ocfs2_inode_cachep = NULL;
+struct kmem_cache *ocfs2_dquot_cachep;
+struct kmem_cache *ocfs2_qf_chunk_cachep;
 
 /* OCFS2 needs to schedule several differnt types of work which
  * require cluster locking, disk I/O, recovery waits, etc. Since these
        .put_super      = ocfs2_put_super,
        .remount_fs     = ocfs2_remount,
        .show_options   = ocfs2_show_options,
+       .quota_read     = ocfs2_quota_read,
+       .quota_write    = ocfs2_quota_write,
 };
 
 enum {
 
        ocfs2_set_locking_protocol();
 
+       status = register_quota_format(&ocfs2_quota_format);
 leave:
        if (status < 0) {
                ocfs2_free_mem_caches();
                destroy_workqueue(ocfs2_wq);
        }
 
+       unregister_quota_format(&ocfs2_quota_format);
+
        debugfs_remove(ocfs2_debugfs_root);
 
        ocfs2_free_mem_caches();
                                       (SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT|
                                                SLAB_MEM_SPREAD),
                                       ocfs2_inode_init_once);
-       if (!ocfs2_inode_cachep)
+       ocfs2_dquot_cachep = kmem_cache_create("ocfs2_dquot_cache",
+                                       sizeof(struct ocfs2_dquot),
+                                       0,
+                                       (SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT|
+                                               SLAB_MEM_SPREAD),
+                                       NULL);
+       ocfs2_qf_chunk_cachep = kmem_cache_create("ocfs2_qf_chunk_cache",
+                                       sizeof(struct ocfs2_quota_chunk),
+                                       0,
+                                       (SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD),
+                                       NULL);
+       if (!ocfs2_inode_cachep || !ocfs2_dquot_cachep ||
+           !ocfs2_qf_chunk_cachep) {
+               if (ocfs2_inode_cachep)
+                       kmem_cache_destroy(ocfs2_inode_cachep);
+               if (ocfs2_dquot_cachep)
+                       kmem_cache_destroy(ocfs2_dquot_cachep);
+               if (ocfs2_qf_chunk_cachep)
+                       kmem_cache_destroy(ocfs2_qf_chunk_cachep);
                return -ENOMEM;
+       }
 
        return 0;
 }
 {
        if (ocfs2_inode_cachep)
                kmem_cache_destroy(ocfs2_inode_cachep);
-
        ocfs2_inode_cachep = NULL;
+
+       if (ocfs2_dquot_cachep)
+               kmem_cache_destroy(ocfs2_dquot_cachep);
+       ocfs2_dquot_cachep = NULL;
+
+       if (ocfs2_qf_chunk_cachep)
+               kmem_cache_destroy(ocfs2_qf_chunk_cachep);
+       ocfs2_qf_chunk_cachep = NULL;
 }
 
 static int ocfs2_get_sector(struct super_block *sb,