[Ocfs2-devel] [PATCH 25/29] ocfs2: Implementation of local and global quota file handling

Joel Becker Joel.Becker at oracle.com
Tue Oct 28 12:07:08 PDT 2008


On Sat, Oct 25, 2008 at 12:08:18AM +0200, Jan Kara wrote:
> For each quota type each node has local quota file. In this file it stores
> changes users have made to disk usage via this node. Once in a while this
> information is synced to global file (and thus with other nodes) so that
> limits enforcement at least aproximately works.
> 
> Global quota files contain all the information about usage and limits. It's
> mostly handled by the generic VFS code (which implements a trie of structures
> inside a quota file). We only have to provide functions to convert structures
> from on-disk format to in-memory one. We also have to provide wrappers for
> various quota functions starting transactions and acquiring necessary cluster
> locks before the actual IO is really started.

	Thanks for the description, it makes it a lot more
understandable.

Joel

> Signed-off-by: Jan Kara <jack at suse.cz>
> ---
>  fs/ocfs2/Makefile          |    2 +
>  fs/ocfs2/cluster/masklog.h |    1 +
>  fs/ocfs2/dir.c             |    4 +-
>  fs/ocfs2/dlmglue.c         |  137 +++++++
>  fs/ocfs2/dlmglue.h         |   17 +
>  fs/ocfs2/file.c            |    6 +-
>  fs/ocfs2/file.h            |    3 +
>  fs/ocfs2/inode.h           |    2 +
>  fs/ocfs2/ocfs2_fs.h        |   95 +++++
>  fs/ocfs2/ocfs2_lockid.h    |    5 +
>  fs/ocfs2/quota.h           |   97 +++++
>  fs/ocfs2/quota_global.c    |  863 ++++++++++++++++++++++++++++++++++++++++++++
>  fs/ocfs2/quota_local.c     |  833 ++++++++++++++++++++++++++++++++++++++++++
>  fs/ocfs2/super.c           |   38 ++-
>  14 files changed, 2096 insertions(+), 7 deletions(-)
>  create mode 100644 fs/ocfs2/quota.h
>  create mode 100644 fs/ocfs2/quota_global.c
>  create mode 100644 fs/ocfs2/quota_local.c
> 
> diff --git a/fs/ocfs2/Makefile b/fs/ocfs2/Makefile
> index 589dcdf..11f14e1 100644
> --- a/fs/ocfs2/Makefile
> +++ b/fs/ocfs2/Makefile
> @@ -35,6 +35,8 @@ ocfs2-objs := \
>  	sysfile.o 		\
>  	uptodate.o		\
>  	ver.o			\
> +	quota_local.o		\
> +	quota_global.o		\
>  	xattr.o
>  
>  ocfs2_stackglue-objs := stackglue.o
> diff --git a/fs/ocfs2/cluster/masklog.h b/fs/ocfs2/cluster/masklog.h
> index 57670c6..7e72a81 100644
> --- a/fs/ocfs2/cluster/masklog.h
> +++ b/fs/ocfs2/cluster/masklog.h
> @@ -113,6 +113,7 @@
>  #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 */
> diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
> index 026e6eb..084ba9c 100644
> --- a/fs/ocfs2/dir.c
> +++ b/fs/ocfs2/dir.c
> @@ -82,8 +82,8 @@ static int ocfs2_do_extend_dir(struct super_block *sb,
>  			       struct ocfs2_alloc_context *meta_ac,
>  			       struct buffer_head **new_bh);
>  
> -static struct buffer_head *ocfs2_bread(struct inode *inode,
> -				       int block, int *err, int reada)
> +struct buffer_head *ocfs2_bread(struct inode *inode,
> +				int block, int *err, int reada)
>  {
>  	struct buffer_head *bh = NULL;
>  	int tmperr;
> diff --git a/fs/ocfs2/dlmglue.c b/fs/ocfs2/dlmglue.c
> index ec68442..eab90e8 100644
> --- a/fs/ocfs2/dlmglue.c
> +++ b/fs/ocfs2/dlmglue.c
> @@ -32,6 +32,7 @@
>  #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>
> @@ -51,6 +52,7 @@
>  #include "slot_map.h"
>  #include "super.h"
>  #include "uptodate.h"
> +#include "quota.h"
>  
>  #include "buffer_head_io.h"
>  
> @@ -68,6 +70,7 @@ struct ocfs2_mask_waiter {
>  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.
> @@ -102,6 +105,7 @@ static int ocfs2_dentry_convert_worker(struct ocfs2_lock_res *lockres,
>  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)
>  
> @@ -258,6 +262,12 @@ static struct ocfs2_lock_res_ops ocfs2_flock_lops = {
>  	.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 ||
> @@ -279,6 +289,13 @@ static inline struct ocfs2_dentry_lock *ocfs2_lock_res_dl(struct ocfs2_lock_res
>  	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)
> @@ -507,6 +524,13 @@ static struct ocfs2_super *ocfs2_get_inode_osb(struct ocfs2_lock_res *lockres)
>  	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;
> @@ -609,6 +633,17 @@ void ocfs2_file_lock_res_init(struct ocfs2_lock_res *lockres,
>  	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();
> @@ -3450,6 +3485,108 @@ static int ocfs2_dentry_convert_worker(struct ocfs2_lock_res *lockres,
>  	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_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();
> +}
> +
> +/* 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 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_super *osb = OCFS2_SB(oinfo->dqi_gi.dqi_sb);
> +	struct ocfs2_qinfo_lvb *lvb;
> +	int level = ex ? DLM_LOCK_EX : DLM_LOCK_PR;
> +	int status = 0;
> +	struct buffer_head *bh;
> +	struct ocfs2_global_disk_dqinfo *gdinfo;
> +
> +	mlog_entry_void();
> +
> +	/* We'll allow faking a readonly metadata lock for
> +	 * rodevices. */
> +	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 */
> +	lvb = ocfs2_dlm_lvb(&lockres->l_lksb);
> +	if (lvb->lvb_version == OCFS2_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) {
> +			ocfs2_qinfo_unlock(oinfo, ex);
> +			mlog_errno(status);
> +			goto bail_refresh;
> +		}
> +		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_refresh:
> +	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.
> diff --git a/fs/ocfs2/dlmglue.h b/fs/ocfs2/dlmglue.h
> index 2bb01f0..6a34048 100644
> --- a/fs/ocfs2/dlmglue.h
> +++ b/fs/ocfs2/dlmglue.h
> @@ -49,6 +49,17 @@ struct ocfs2_meta_lvb {
>  	__be32       lvb_reserved2;
>  };
>  
> +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)
> @@ -69,6 +80,9 @@ void ocfs2_dentry_lock_res_init(struct ocfs2_dentry_lock *dl,
>  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);
> @@ -103,6 +117,9 @@ int ocfs2_dentry_lock(struct dentry *dentry, int ex);
>  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,
> diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c
> index e135da1..9af16e0 100644
> --- a/fs/ocfs2/file.c
> +++ b/fs/ocfs2/file.c
> @@ -303,9 +303,9 @@ bail:
>  	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);
> diff --git a/fs/ocfs2/file.h b/fs/ocfs2/file.h
> index e92382c..172f9fb 100644
> --- a/fs/ocfs2/file.h
> +++ b/fs/ocfs2/file.h
> @@ -51,6 +51,9 @@ int ocfs2_add_inode_data(struct ocfs2_super *osb,
>  			 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);
> diff --git a/fs/ocfs2/inode.h b/fs/ocfs2/inode.h
> index 2f37af9..e90a159 100644
> --- a/fs/ocfs2/inode.h
> +++ b/fs/ocfs2/inode.h
> @@ -142,6 +142,8 @@ int ocfs2_mark_inode_dirty(handle_t *handle,
>  			   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);
> diff --git a/fs/ocfs2/ocfs2_fs.h b/fs/ocfs2/ocfs2_fs.h
> index dd17137..04bebd2 100644
> --- a/fs/ocfs2/ocfs2_fs.h
> +++ b/fs/ocfs2/ocfs2_fs.h
> @@ -878,6 +878,101 @@ static inline int ocfs2_xattr_get_type(struct ocfs2_xattr_entry *xe)
>  	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, \
> +}
> +
> +/* 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;
> +	__le32 dqi_igrace;
> +	__le32 dqi_syncms;
> +	__le32 dqi_blocks;
> +/*10*/	__le32 dqi_free_blk;
> +	__le32 dqi_free_entry;
> +};
> +
> +/* 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)
>  {
> diff --git a/fs/ocfs2/ocfs2_lockid.h b/fs/ocfs2/ocfs2_lockid.h
> index 82c200f..eb6f50c 100644
> --- a/fs/ocfs2/ocfs2_lockid.h
> +++ b/fs/ocfs2/ocfs2_lockid.h
> @@ -46,6 +46,7 @@ enum ocfs2_lock_type {
>  	OCFS2_LOCK_TYPE_DENTRY,
>  	OCFS2_LOCK_TYPE_OPEN,
>  	OCFS2_LOCK_TYPE_FLOCK,
> +	OCFS2_LOCK_TYPE_QINFO,
>  	OCFS2_NUM_LOCK_TYPES
>  };
>  
> @@ -77,6 +78,9 @@ static inline char ocfs2_lock_type_char(enum ocfs2_lock_type type)
>  		case OCFS2_LOCK_TYPE_FLOCK:
>  			c = 'F';
>  			break;
> +		case OCFS2_LOCK_TYPE_QINFO:
> +			c = 'Q';
> +			break;
>  		default:
>  			c = '\0';
>  	}
> @@ -95,6 +99,7 @@ static char *ocfs2_lock_type_strings[] = {
>  	[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)
> diff --git a/fs/ocfs2/quota.h b/fs/ocfs2/quota.h
> new file mode 100644
> index 0000000..87545ca
> --- /dev/null
> +++ b/fs/ocfs2/quota.h
> @@ -0,0 +1,97 @@
> +/*
> + * 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
> +
> +/* How many bytes to we reserve in each quota file block for our internal
> + * purposes? E.g. checksums... */
> +#define OCFS2_QBLK_RESERVED_SPACE 8
> +
> +/*
> + * 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 */
> diff --git a/fs/ocfs2/quota_global.c b/fs/ocfs2/quota_global.c
> new file mode 100644
> index 0000000..b937f07
> --- /dev/null
> +++ b/fs/ocfs2/quota_global.c
> @@ -0,0 +1,863 @@
> +/*
> + *  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 "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);
> +	d->dqb_pad1 = d->dqb_pad2 = 0;
> +}
> +
> +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)
> +{
> +	return ocfs2_bread(inode, block, err, 0);
> +}
> +
> +/* 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;
> +	struct buffer_head *bh;
> +	handle_t *handle = journal_current_handle();
> +	size_t tocopy, towrite = len;
> +
> +	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;
> +	}
> +	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;
> +	}
> +	WARN_ON(off >> sb->s_blocksize_bits != \
> +		(off + len) >> sb->s_blocksize_bits);
> +	WARN_ON(((off + len) & ((1 << sb->s_blocksize_bits) - 1)) >
> +		sb->s_blocksize - OCFS2_QBLK_RESERVED_SPACE);
> +	for (towrite = len; towrite > 0; towrite -= tocopy) {
> +		tocopy = min(towrite, (size_t)(sb->s_blocksize - offset));
> +		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);
> +		if (err < 0) {
> +			brelse(bh);
> +			goto out;
> +		}
> +		lock_buffer(bh);
> +		memcpy(bh->b_data + offset, data, tocopy);
> +		flush_dcache_page(bh->b_page);
> +		unlock_buffer(bh);
> +		err = ocfs2_journal_dirty(handle, bh);
> +		brelse(bh);
> +		if (err < 0)
> +			goto out;
> +		offset = 0;
> +		data += tocopy;
> +		blk++;
> +	}
> +out:
> +	/* Nothing written? */
> +	if (len == towrite) {
> +		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 - towrite;
> +}
> +
> +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;
> +	/* Now merge grace time changes... */
> +	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;
> +	}
> +	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;
> +	}
> +	/* 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;
> +		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++) {
> +		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++) {
> +		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,
> +};
> diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c
> new file mode 100644
> index 0000000..55c3f2f
> --- /dev/null
> +++ b/fs/ocfs2/quota_local.c
> @@ -0,0 +1,833 @@
> +/*
> + *  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
> +};
> diff --git a/fs/ocfs2/super.c b/fs/ocfs2/super.c
> index 4712bc7..92539fe 100644
> --- a/fs/ocfs2/super.c
> +++ b/fs/ocfs2/super.c
> @@ -65,10 +65,13 @@
>  #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
> @@ -137,6 +140,8 @@ static const struct super_operations ocfs2_sops = {
>  	.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 {
> @@ -1071,6 +1076,7 @@ static int __init ocfs2_init(void)
>  
>  	ocfs2_set_locking_protocol();
>  
> +	status = register_quota_format(&ocfs2_quota_format);
>  leave:
>  	if (status < 0) {
>  		ocfs2_free_mem_caches();
> @@ -1094,6 +1100,8 @@ static void __exit ocfs2_exit(void)
>  		destroy_workqueue(ocfs2_wq);
>  	}
>  
> +	unregister_quota_format(&ocfs2_quota_format);
> +
>  	debugfs_remove(ocfs2_debugfs_root);
>  
>  	ocfs2_free_mem_caches();
> @@ -1209,8 +1217,27 @@ static int ocfs2_initialize_mem_caches(void)
>  				       (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;
>  }
> @@ -1219,8 +1246,15 @@ static void ocfs2_free_mem_caches(void)
>  {
>  	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,
> -- 
> 1.5.2.4
> 
> 
> _______________________________________________
> Ocfs2-devel mailing list
> Ocfs2-devel at oss.oracle.com
> http://oss.oracle.com/mailman/listinfo/ocfs2-devel

-- 

"Born under a bad sign.
 I been down since I began to crawl.
 If it wasn't for bad luck,
 I wouldn't have no luck at all."

Joel Becker
Principal Software Developer
Oracle
E-mail: joel.becker at oracle.com
Phone: (650) 506-8127



More information about the Ocfs2-devel mailing list