Commit ed678f13 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] Quota locking fixes

From: Jan Kara <jack@ucw.cz>

Change locking rules in quota code to fix lock ordering especially wrt
journal lock.  Also some unnecessary spinlocking is removed.  The locking
changes are mainly: dqptr_sem, dqio_sem are acquired only when transaction is
already started, dqonoff_sem before a transaction is started.  This change
requires some callbacks to ext3 (also implemented in this patch) to start
transaction before the locks are acquired.
parent a97de48b
...@@ -417,7 +417,7 @@ config QFMT_V1 ...@@ -417,7 +417,7 @@ config QFMT_V1
tristate "Old quota format support" tristate "Old quota format support"
depends on QUOTA depends on QUOTA
help help
This quota format was (is) used by kernels earlier than 2.4.??. If This quota format was (is) used by kernels earlier than 2.4.22. If
you have quota working and you don't want to convert to new quota you have quota working and you don't want to convert to new quota
format say Y here. format say Y here.
...@@ -426,8 +426,8 @@ config QFMT_V2 ...@@ -426,8 +426,8 @@ config QFMT_V2
depends on QUOTA depends on QUOTA
help help
This quota format allows using quotas with 32-bit UIDs/GIDs. If you This quota format allows using quotas with 32-bit UIDs/GIDs. If you
need this functionality say Y here. Note that you will need latest need this functionality say Y here. Note that you will need recent
quota utilities for new quota format with this kernel. quota utilities (>= 3.01) for new quota format with this kernel.
config QUOTACTL config QUOTACTL
bool bool
......
...@@ -85,12 +85,31 @@ ...@@ -85,12 +85,31 @@
* and quota formats and also dqstats structure containing statistics about the * and quota formats and also dqstats structure containing statistics about the
* lists. dq_data_lock protects data from dq_dqb and also mem_dqinfo structures * lists. dq_data_lock protects data from dq_dqb and also mem_dqinfo structures
* and also guards consistency of dquot->dq_dqb with inode->i_blocks, i_bytes. * and also guards consistency of dquot->dq_dqb with inode->i_blocks, i_bytes.
* Note that we don't have to do the locking of i_blocks and i_bytes when the * i_blocks and i_bytes updates itself are guarded by i_lock acquired directly
* quota is disabled - i_sem should serialize the access. dq_data_lock should * in inode_add_bytes() and inode_sub_bytes().
* be always grabbed before dq_list_lock. *
* The spinlock ordering is hence: dq_data_lock > dq_list_lock > i_lock
* *
* Note that some things (eg. sb pointer, type, id) doesn't change during * Note that some things (eg. sb pointer, type, id) doesn't change during
* the life of the dquot structure and so needn't to be protected by a lock * the life of the dquot structure and so needn't to be protected by a lock
*
* Any operation working on dquots via inode pointers must hold dqptr_sem. If
* operation is just reading pointers from inode (or not using them at all) the
* read lock is enough. If pointers are altered function must hold write lock.
* If operation is holding reference to dquot in other way (e.g. quotactl ops)
* it must be guarded by dqonoff_sem.
* This locking assures that:
* a) update/access to dquot pointers in inode is serialized
* b) everyone is guarded against invalidate_dquots()
*
* Each dquot has its dq_lock semaphore. Locked dquots might not be referenced
* from inodes (dquot_alloc_space() and such don't check the dq_lock).
* Currently dquot is locked only when it is being read to memory on the first
* dqget(). Write operations on dquots don't hold dq_lock as they copy data
* under dq_data_lock spinlock to internal buffers before writing.
*
* Lock ordering (including journal_lock) is following:
* dqonoff_sem > journal_lock > dqptr_sem > dquot->dq_lock > dqio_sem
*/ */
spinlock_t dq_list_lock = SPIN_LOCK_UNLOCKED; spinlock_t dq_list_lock = SPIN_LOCK_UNLOCKED;
spinlock_t dq_data_lock = SPIN_LOCK_UNLOCKED; spinlock_t dq_data_lock = SPIN_LOCK_UNLOCKED;
...@@ -169,23 +188,6 @@ static void put_quota_format(struct quota_format_type *fmt) ...@@ -169,23 +188,6 @@ static void put_quota_format(struct quota_format_type *fmt)
* mechanism to locate a specific dquot. * mechanism to locate a specific dquot.
*/ */
/*
* Note that any operation which operates on dquot data (ie. dq_dqb) must
* hold dq_data_lock.
*
* Any operation working with dquots must hold dqptr_sem. If operation is
* just reading pointers from inodes than read lock is enough. If pointers
* are altered function must hold write lock.
*
* Locked dquots might not be referenced in inodes. Currently dquot it locked
* only once in its existence - when it's being read to memory on first dqget()
* and at that time it can't be referenced from inode. Write operations on
* dquots don't hold dquot lock as they copy data to internal buffers before
* writing anyway and copying as well as any data update should be atomic. Also
* nobody can change used entries in dquot structure as this is done only when
* quota is destroyed and invalidate_dquots() is called only when dq_count == 0.
*/
static LIST_HEAD(inuse_list); static LIST_HEAD(inuse_list);
static LIST_HEAD(free_dquots); static LIST_HEAD(free_dquots);
static struct list_head dquot_hash[NR_DQHASH]; static struct list_head dquot_hash[NR_DQHASH];
...@@ -286,9 +288,9 @@ static int commit_dqblk(struct dquot *dquot) ...@@ -286,9 +288,9 @@ static int commit_dqblk(struct dquot *dquot)
} }
/* Invalidate all dquots on the list. Note that this function is called after /* Invalidate all dquots on the list. Note that this function is called after
* quota is disabled so no new quota might be created. Because we hold dqptr_sem * quota is disabled so no new quota might be created. Because we hold
* for writing and pointers were already removed from inodes we actually know that * dqonoff_sem and pointers were already removed from inodes we actually know
* no quota for this sb+type should be held. */ * that no quota for this sb+type should be held. */
static void invalidate_dquots(struct super_block *sb, int type) static void invalidate_dquots(struct super_block *sb, int type)
{ {
struct dquot *dquot; struct dquot *dquot;
...@@ -303,11 +305,10 @@ static void invalidate_dquots(struct super_block *sb, int type) ...@@ -303,11 +305,10 @@ static void invalidate_dquots(struct super_block *sb, int type)
if (dquot->dq_type != type) if (dquot->dq_type != type)
continue; continue;
#ifdef __DQUOT_PARANOIA #ifdef __DQUOT_PARANOIA
/* There should be no users of quota - we hold dqptr_sem for writing */
if (atomic_read(&dquot->dq_count)) if (atomic_read(&dquot->dq_count))
BUG(); BUG();
#endif #endif
/* Quota now have no users and it has been written on last dqput() */ /* Quota now has no users and it has been written on last dqput() */
remove_dquot_hash(dquot); remove_dquot_hash(dquot);
remove_free_dquot(dquot); remove_free_dquot(dquot);
remove_inuse(dquot); remove_inuse(dquot);
...@@ -323,7 +324,7 @@ static int vfs_quota_sync(struct super_block *sb, int type) ...@@ -323,7 +324,7 @@ static int vfs_quota_sync(struct super_block *sb, int type)
struct quota_info *dqopt = sb_dqopt(sb); struct quota_info *dqopt = sb_dqopt(sb);
int cnt; int cnt;
down_read(&dqopt->dqptr_sem); down(&dqopt->dqonoff_sem);
restart: restart:
/* At this point any dirty dquot will definitely be written so we can clear /* At this point any dirty dquot will definitely be written so we can clear
dirty flag from info */ dirty flag from info */
...@@ -359,7 +360,7 @@ static int vfs_quota_sync(struct super_block *sb, int type) ...@@ -359,7 +360,7 @@ static int vfs_quota_sync(struct super_block *sb, int type)
spin_lock(&dq_list_lock); spin_lock(&dq_list_lock);
dqstats.syncs++; dqstats.syncs++;
spin_unlock(&dq_list_lock); spin_unlock(&dq_list_lock);
up_read(&dqopt->dqptr_sem); up(&dqopt->dqonoff_sem);
return 0; return 0;
} }
...@@ -402,7 +403,7 @@ static int shrink_dqcache_memory(int nr, unsigned int gfp_mask) ...@@ -402,7 +403,7 @@ static int shrink_dqcache_memory(int nr, unsigned int gfp_mask)
/* /*
* Put reference to dquot * Put reference to dquot
* NOTE: If you change this function please check whether dqput_blocks() works right... * NOTE: If you change this function please check whether dqput_blocks() works right...
* MUST be called with dqptr_sem held * MUST be called with either dqptr_sem or dqonoff_sem held
*/ */
static void dqput(struct dquot *dquot) static void dqput(struct dquot *dquot)
{ {
...@@ -467,7 +468,7 @@ static struct dquot *get_empty_dquot(struct super_block *sb, int type) ...@@ -467,7 +468,7 @@ static struct dquot *get_empty_dquot(struct super_block *sb, int type)
/* /*
* Get reference to dquot * Get reference to dquot
* MUST be called with dqptr_sem held * MUST be called with either dqptr_sem or dqonoff_sem held
*/ */
static struct dquot *dqget(struct super_block *sb, unsigned int id, int type) static struct dquot *dqget(struct super_block *sb, unsigned int id, int type)
{ {
...@@ -528,7 +529,7 @@ static int dqinit_needed(struct inode *inode, int type) ...@@ -528,7 +529,7 @@ static int dqinit_needed(struct inode *inode, int type)
return 0; return 0;
} }
/* This routine is guarded by dqptr_sem semaphore */ /* This routine is guarded by dqonoff_sem semaphore */
static void add_dquot_ref(struct super_block *sb, int type) static void add_dquot_ref(struct super_block *sb, int type)
{ {
struct list_head *p; struct list_head *p;
...@@ -594,7 +595,7 @@ int remove_inode_dquot_ref(struct inode *inode, int type, struct list_head *tofr ...@@ -594,7 +595,7 @@ int remove_inode_dquot_ref(struct inode *inode, int type, struct list_head *tofr
/* Free list of dquots - called from inode.c */ /* Free list of dquots - called from inode.c */
/* dquots are removed from inodes, no new references can be got so we are the only ones holding reference */ /* dquots are removed from inodes, no new references can be got so we are the only ones holding reference */
void put_dquot_list(struct list_head *tofree_head) static void put_dquot_list(struct list_head *tofree_head)
{ {
struct list_head *act_head; struct list_head *act_head;
struct dquot *dquot; struct dquot *dquot;
...@@ -609,6 +610,20 @@ void put_dquot_list(struct list_head *tofree_head) ...@@ -609,6 +610,20 @@ void put_dquot_list(struct list_head *tofree_head)
} }
} }
/* Function in inode.c - remove pointers to dquots in icache */
extern void remove_dquot_ref(struct super_block *, int, struct list_head *);
/* Gather all references from inodes and drop them */
static void drop_dquot_ref(struct super_block *sb, int type)
{
LIST_HEAD(tofree_head);
down_write(&sb_dqopt(sb)->dqptr_sem);
remove_dquot_ref(sb, type, &tofree_head);
up_write(&sb_dqopt(sb)->dqptr_sem);
put_dquot_list(&tofree_head);
}
static inline void dquot_incr_inodes(struct dquot *dquot, unsigned long number) static inline void dquot_incr_inodes(struct dquot *dquot, unsigned long number)
{ {
dquot->dq_dqb.dqb_curinodes += number; dquot->dq_dqb.dqb_curinodes += number;
...@@ -804,6 +819,9 @@ void dquot_initialize(struct inode *inode, int type) ...@@ -804,6 +819,9 @@ void dquot_initialize(struct inode *inode, int type)
unsigned int id = 0; unsigned int id = 0;
int cnt; int cnt;
/* Solve deadlock when we recurse when holding dqptr_sem... */
if (IS_NOQUOTA(inode))
return;
down_write(&sb_dqopt(inode->i_sb)->dqptr_sem); down_write(&sb_dqopt(inode->i_sb)->dqptr_sem);
/* Having dqptr_sem we know NOQUOTA flags can't be altered... */ /* Having dqptr_sem we know NOQUOTA flags can't be altered... */
if (IS_NOQUOTA(inode)) { if (IS_NOQUOTA(inode)) {
...@@ -832,49 +850,22 @@ void dquot_initialize(struct inode *inode, int type) ...@@ -832,49 +850,22 @@ void dquot_initialize(struct inode *inode, int type)
} }
/* /*
* Remove references to quota from inode * Release all quotas referenced by inode
* This function needs dqptr_sem for writing * Transaction must be started at an entry
*/ */
static void dquot_drop_iupdate(struct inode *inode, struct dquot **to_drop) void dquot_drop(struct inode *inode)
{ {
int cnt; int cnt;
down_write(&sb_dqopt(inode->i_sb)->dqptr_sem);
inode->i_flags &= ~S_QUOTA; inode->i_flags &= ~S_QUOTA;
for (cnt = 0; cnt < MAXQUOTAS; cnt++) { for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
to_drop[cnt] = inode->i_dquot[cnt]; if (inode->i_dquot[cnt] != NODQUOT) {
dqput(inode->i_dquot[cnt]);
inode->i_dquot[cnt] = NODQUOT; inode->i_dquot[cnt] = NODQUOT;
} }
} }
/*
* Release all quotas referenced by inode
*/
void dquot_drop(struct inode *inode)
{
struct dquot *to_drop[MAXQUOTAS];
int cnt;
down_write(&sb_dqopt(inode->i_sb)->dqptr_sem);
dquot_drop_iupdate(inode, to_drop);
up_write(&sb_dqopt(inode->i_sb)->dqptr_sem); up_write(&sb_dqopt(inode->i_sb)->dqptr_sem);
for (cnt = 0; cnt < MAXQUOTAS; cnt++)
if (to_drop[cnt] != NODQUOT)
dqput(to_drop[cnt]);
}
/*
* Release all quotas referenced by inode.
* This function assumes dqptr_sem for writing
*/
void dquot_drop_nolock(struct inode *inode)
{
struct dquot *to_drop[MAXQUOTAS];
int cnt;
dquot_drop_iupdate(inode, to_drop);
for (cnt = 0; cnt < MAXQUOTAS; cnt++)
if (to_drop[cnt] != NODQUOT)
dqput(to_drop[cnt]);
} }
/* /*
...@@ -885,11 +876,17 @@ int dquot_alloc_space(struct inode *inode, qsize_t number, int warn) ...@@ -885,11 +876,17 @@ int dquot_alloc_space(struct inode *inode, qsize_t number, int warn)
int cnt, ret = NO_QUOTA; int cnt, ret = NO_QUOTA;
char warntype[MAXQUOTAS]; char warntype[MAXQUOTAS];
/* Solve deadlock when we recurse when holding dqptr_sem... */
if (IS_NOQUOTA(inode)) {
inode_add_bytes(inode, number);
return QUOTA_OK;
}
for (cnt = 0; cnt < MAXQUOTAS; cnt++) for (cnt = 0; cnt < MAXQUOTAS; cnt++)
warntype[cnt] = NOWARN; warntype[cnt] = NOWARN;
down_read(&sb_dqopt(inode->i_sb)->dqptr_sem); down_read(&sb_dqopt(inode->i_sb)->dqptr_sem);
spin_lock(&dq_data_lock); spin_lock(&dq_data_lock);
/* Now recheck reliably when holding dqptr_sem */
if (IS_NOQUOTA(inode)) if (IS_NOQUOTA(inode))
goto add_bytes; goto add_bytes;
for (cnt = 0; cnt < MAXQUOTAS; cnt++) { for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
...@@ -921,9 +918,13 @@ int dquot_alloc_inode(const struct inode *inode, unsigned long number) ...@@ -921,9 +918,13 @@ int dquot_alloc_inode(const struct inode *inode, unsigned long number)
int cnt, ret = NO_QUOTA; int cnt, ret = NO_QUOTA;
char warntype[MAXQUOTAS]; char warntype[MAXQUOTAS];
/* Solve deadlock when we recurse when holding dqptr_sem... */
if (IS_NOQUOTA(inode))
return QUOTA_OK;
for (cnt = 0; cnt < MAXQUOTAS; cnt++) for (cnt = 0; cnt < MAXQUOTAS; cnt++)
warntype[cnt] = NOWARN; warntype[cnt] = NOWARN;
down_read(&sb_dqopt(inode->i_sb)->dqptr_sem); down_read(&sb_dqopt(inode->i_sb)->dqptr_sem);
/* Now recheck reliably when holding dqptr_sem */
if (IS_NOQUOTA(inode)) { if (IS_NOQUOTA(inode)) {
up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); up_read(&sb_dqopt(inode->i_sb)->dqptr_sem);
return QUOTA_OK; return QUOTA_OK;
...@@ -956,8 +957,14 @@ void dquot_free_space(struct inode *inode, qsize_t number) ...@@ -956,8 +957,14 @@ void dquot_free_space(struct inode *inode, qsize_t number)
{ {
unsigned int cnt; unsigned int cnt;
/* Solve deadlock when we recurse when holding dqptr_sem... */
if (IS_NOQUOTA(inode)) {
inode_sub_bytes(inode, number);
return;
}
down_read(&sb_dqopt(inode->i_sb)->dqptr_sem); down_read(&sb_dqopt(inode->i_sb)->dqptr_sem);
spin_lock(&dq_data_lock); spin_lock(&dq_data_lock);
/* Now recheck reliably when holding dqptr_sem */
if (IS_NOQUOTA(inode)) if (IS_NOQUOTA(inode))
goto sub_bytes; goto sub_bytes;
for (cnt = 0; cnt < MAXQUOTAS; cnt++) { for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
...@@ -978,7 +985,11 @@ void dquot_free_inode(const struct inode *inode, unsigned long number) ...@@ -978,7 +985,11 @@ void dquot_free_inode(const struct inode *inode, unsigned long number)
{ {
unsigned int cnt; unsigned int cnt;
/* Solve deadlock when we recurse when holding dqptr_sem... */
if (IS_NOQUOTA(inode))
return;
down_read(&sb_dqopt(inode->i_sb)->dqptr_sem); down_read(&sb_dqopt(inode->i_sb)->dqptr_sem);
/* Now recheck reliably when holding dqptr_sem */
if (IS_NOQUOTA(inode)) { if (IS_NOQUOTA(inode)) {
up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); up_read(&sb_dqopt(inode->i_sb)->dqptr_sem);
return; return;
...@@ -1007,14 +1018,20 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr) ...@@ -1007,14 +1018,20 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr)
chgid = (iattr->ia_valid & ATTR_GID) && inode->i_gid != iattr->ia_gid; chgid = (iattr->ia_valid & ATTR_GID) && inode->i_gid != iattr->ia_gid;
char warntype[MAXQUOTAS]; char warntype[MAXQUOTAS];
/* Solve deadlock when we recurse when holding dqptr_sem... */
if (IS_NOQUOTA(inode))
return QUOTA_OK;
/* Clear the arrays */ /* Clear the arrays */
for (cnt = 0; cnt < MAXQUOTAS; cnt++) { for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
transfer_to[cnt] = transfer_from[cnt] = NODQUOT; transfer_to[cnt] = transfer_from[cnt] = NODQUOT;
warntype[cnt] = NOWARN; warntype[cnt] = NOWARN;
} }
down(&sb_dqopt(inode->i_sb)->dqonoff_sem);
down_write(&sb_dqopt(inode->i_sb)->dqptr_sem); down_write(&sb_dqopt(inode->i_sb)->dqptr_sem);
/* Now recheck reliably when holding dqptr_sem */
if (IS_NOQUOTA(inode)) { /* File without quota accounting? */ if (IS_NOQUOTA(inode)) { /* File without quota accounting? */
up_write(&sb_dqopt(inode->i_sb)->dqptr_sem); up_write(&sb_dqopt(inode->i_sb)->dqptr_sem);
up(&sb_dqopt(inode->i_sb)->dqonoff_sem);
return QUOTA_OK; return QUOTA_OK;
} }
/* First build the transfer_to list - here we can block on reading of dquots... */ /* First build the transfer_to list - here we can block on reading of dquots... */
...@@ -1065,6 +1082,7 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr) ...@@ -1065,6 +1082,7 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr)
ret = QUOTA_OK; ret = QUOTA_OK;
warn_put_all: warn_put_all:
spin_unlock(&dq_data_lock); spin_unlock(&dq_data_lock);
up_write(&sb_dqopt(inode->i_sb)->dqptr_sem);
flush_warnings(transfer_to, warntype); flush_warnings(transfer_to, warntype);
for (cnt = 0; cnt < MAXQUOTAS; cnt++) { for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
...@@ -1073,7 +1091,7 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr) ...@@ -1073,7 +1091,7 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr)
if (ret == NO_QUOTA && transfer_to[cnt] != NODQUOT) if (ret == NO_QUOTA && transfer_to[cnt] != NODQUOT)
dqput(transfer_to[cnt]); dqput(transfer_to[cnt]);
} }
up_write(&sb_dqopt(inode->i_sb)->dqptr_sem); up(&sb_dqopt(inode->i_sb)->dqonoff_sem);
return ret; return ret;
} }
...@@ -1121,9 +1139,6 @@ static inline void reset_enable_flags(struct quota_info *dqopt, int type) ...@@ -1121,9 +1139,6 @@ static inline void reset_enable_flags(struct quota_info *dqopt, int type)
} }
} }
/* Function in inode.c - remove pointers to dquots in icache */
extern void remove_dquot_ref(struct super_block *, int);
/* /*
* Turn quota off on a device. type == -1 ==> quotaoff for all types (umount) * Turn quota off on a device. type == -1 ==> quotaoff for all types (umount)
*/ */
...@@ -1137,7 +1152,6 @@ int vfs_quota_off(struct super_block *sb, int type) ...@@ -1137,7 +1152,6 @@ int vfs_quota_off(struct super_block *sb, int type)
/* We need to serialize quota_off() for device */ /* We need to serialize quota_off() for device */
down(&dqopt->dqonoff_sem); down(&dqopt->dqonoff_sem);
down_write(&dqopt->dqptr_sem);
for (cnt = 0; cnt < MAXQUOTAS; cnt++) { for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
if (type != -1 && cnt != type) if (type != -1 && cnt != type)
continue; continue;
...@@ -1146,7 +1160,7 @@ int vfs_quota_off(struct super_block *sb, int type) ...@@ -1146,7 +1160,7 @@ int vfs_quota_off(struct super_block *sb, int type)
reset_enable_flags(dqopt, cnt); reset_enable_flags(dqopt, cnt);
/* Note: these are blocking operations */ /* Note: these are blocking operations */
remove_dquot_ref(sb, cnt); drop_dquot_ref(sb, cnt);
invalidate_dquots(sb, cnt); invalidate_dquots(sb, cnt);
/* /*
* Now all dquots should be invalidated, all writes done so we should be only * Now all dquots should be invalidated, all writes done so we should be only
...@@ -1168,7 +1182,6 @@ int vfs_quota_off(struct super_block *sb, int type) ...@@ -1168,7 +1182,6 @@ int vfs_quota_off(struct super_block *sb, int type)
dqopt->info[cnt].dqi_bgrace = 0; dqopt->info[cnt].dqi_bgrace = 0;
dqopt->ops[cnt] = NULL; dqopt->ops[cnt] = NULL;
} }
up_write(&dqopt->dqptr_sem);
up(&dqopt->dqonoff_sem); up(&dqopt->dqonoff_sem);
out: out:
return 0; return 0;
...@@ -1180,7 +1193,8 @@ int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path) ...@@ -1180,7 +1193,8 @@ int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path)
struct inode *inode; struct inode *inode;
struct quota_info *dqopt = sb_dqopt(sb); struct quota_info *dqopt = sb_dqopt(sb);
struct quota_format_type *fmt = find_quota_format(format_id); struct quota_format_type *fmt = find_quota_format(format_id);
int error; int error, cnt;
struct dquot *to_drop[MAXQUOTAS];
unsigned int oldflags; unsigned int oldflags;
if (!fmt) if (!fmt)
...@@ -1202,7 +1216,6 @@ int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path) ...@@ -1202,7 +1216,6 @@ int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path)
goto out_f; goto out_f;
down(&dqopt->dqonoff_sem); down(&dqopt->dqonoff_sem);
down_write(&dqopt->dqptr_sem);
if (sb_has_quota_enabled(sb, type)) { if (sb_has_quota_enabled(sb, type)) {
error = -EBUSY; error = -EBUSY;
goto out_lock; goto out_lock;
...@@ -1213,8 +1226,20 @@ int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path) ...@@ -1213,8 +1226,20 @@ int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path)
if (!fmt->qf_ops->check_quota_file(sb, type)) if (!fmt->qf_ops->check_quota_file(sb, type))
goto out_file_init; goto out_file_init;
/* We don't want quota and atime on quota files (deadlocks possible) */ /* We don't want quota and atime on quota files (deadlocks possible) */
dquot_drop_nolock(inode); down_write(&dqopt->dqptr_sem);
inode->i_flags |= S_NOQUOTA | S_NOATIME; inode->i_flags |= S_NOQUOTA | S_NOATIME;
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
to_drop[cnt] = inode->i_dquot[cnt];
inode->i_dquot[cnt] = NODQUOT;
}
inode->i_flags &= ~S_QUOTA;
up_write(&dqopt->dqptr_sem);
/* We must put dquots outside of dqptr_sem because we may need to
* start transaction for write */
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
if (to_drop[cnt])
dqput(to_drop[cnt]);
}
dqopt->ops[type] = fmt->qf_ops; dqopt->ops[type] = fmt->qf_ops;
dqopt->info[type].dqi_format = fmt; dqopt->info[type].dqi_format = fmt;
...@@ -1225,7 +1250,6 @@ int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path) ...@@ -1225,7 +1250,6 @@ int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path)
} }
up(&dqopt->dqio_sem); up(&dqopt->dqio_sem);
set_enable_flags(dqopt, type); set_enable_flags(dqopt, type);
up_write(&dqopt->dqptr_sem);
add_dquot_ref(sb, type); add_dquot_ref(sb, type);
up(&dqopt->dqonoff_sem); up(&dqopt->dqonoff_sem);
...@@ -1268,14 +1292,14 @@ int vfs_get_dqblk(struct super_block *sb, int type, qid_t id, struct if_dqblk *d ...@@ -1268,14 +1292,14 @@ int vfs_get_dqblk(struct super_block *sb, int type, qid_t id, struct if_dqblk *d
{ {
struct dquot *dquot; struct dquot *dquot;
down_read(&sb_dqopt(sb)->dqptr_sem); down(&sb_dqopt(sb)->dqonoff_sem);
if (!(dquot = dqget(sb, id, type))) { if (!(dquot = dqget(sb, id, type))) {
up_read(&sb_dqopt(sb)->dqptr_sem); up(&sb_dqopt(sb)->dqonoff_sem);
return -ESRCH; return -ESRCH;
} }
do_get_dqblk(dquot, di); do_get_dqblk(dquot, di);
dqput(dquot); dqput(dquot);
up_read(&sb_dqopt(sb)->dqptr_sem); up(&sb_dqopt(sb)->dqonoff_sem);
return 0; return 0;
} }
...@@ -1337,14 +1361,14 @@ int vfs_set_dqblk(struct super_block *sb, int type, qid_t id, struct if_dqblk *d ...@@ -1337,14 +1361,14 @@ int vfs_set_dqblk(struct super_block *sb, int type, qid_t id, struct if_dqblk *d
{ {
struct dquot *dquot; struct dquot *dquot;
down_read(&sb_dqopt(sb)->dqptr_sem); down(&sb_dqopt(sb)->dqonoff_sem);
if (!(dquot = dqget(sb, id, type))) { if (!(dquot = dqget(sb, id, type))) {
up_read(&sb_dqopt(sb)->dqptr_sem); up(&sb_dqopt(sb)->dqonoff_sem);
return -ESRCH; return -ESRCH;
} }
do_set_dqblk(dquot, di); do_set_dqblk(dquot, di);
dqput(dquot); dqput(dquot);
up_read(&sb_dqopt(sb)->dqptr_sem); up(&sb_dqopt(sb)->dqonoff_sem);
return 0; return 0;
} }
...@@ -1353,9 +1377,9 @@ int vfs_get_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii) ...@@ -1353,9 +1377,9 @@ int vfs_get_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii)
{ {
struct mem_dqinfo *mi; struct mem_dqinfo *mi;
down_read(&sb_dqopt(sb)->dqptr_sem); down(&sb_dqopt(sb)->dqonoff_sem);
if (!sb_has_quota_enabled(sb, type)) { if (!sb_has_quota_enabled(sb, type)) {
up_read(&sb_dqopt(sb)->dqptr_sem); up(&sb_dqopt(sb)->dqonoff_sem);
return -ESRCH; return -ESRCH;
} }
mi = sb_dqopt(sb)->info + type; mi = sb_dqopt(sb)->info + type;
...@@ -1365,7 +1389,7 @@ int vfs_get_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii) ...@@ -1365,7 +1389,7 @@ int vfs_get_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii)
ii->dqi_flags = mi->dqi_flags & DQF_MASK; ii->dqi_flags = mi->dqi_flags & DQF_MASK;
ii->dqi_valid = IIF_ALL; ii->dqi_valid = IIF_ALL;
spin_unlock(&dq_data_lock); spin_unlock(&dq_data_lock);
up_read(&sb_dqopt(sb)->dqptr_sem); up(&sb_dqopt(sb)->dqonoff_sem);
return 0; return 0;
} }
...@@ -1374,9 +1398,9 @@ int vfs_set_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii) ...@@ -1374,9 +1398,9 @@ int vfs_set_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii)
{ {
struct mem_dqinfo *mi; struct mem_dqinfo *mi;
down_read(&sb_dqopt(sb)->dqptr_sem); down(&sb_dqopt(sb)->dqonoff_sem);
if (!sb_has_quota_enabled(sb, type)) { if (!sb_has_quota_enabled(sb, type)) {
up_read(&sb_dqopt(sb)->dqptr_sem); up(&sb_dqopt(sb)->dqonoff_sem);
return -ESRCH; return -ESRCH;
} }
mi = sb_dqopt(sb)->info + type; mi = sb_dqopt(sb)->info + type;
...@@ -1389,7 +1413,7 @@ int vfs_set_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii) ...@@ -1389,7 +1413,7 @@ int vfs_set_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii)
mi->dqi_flags = (mi->dqi_flags & ~DQF_MASK) | (ii->dqi_flags & DQF_MASK); mi->dqi_flags = (mi->dqi_flags & ~DQF_MASK) | (ii->dqi_flags & DQF_MASK);
mark_info_dirty(mi); mark_info_dirty(mi);
spin_unlock(&dq_data_lock); spin_unlock(&dq_data_lock);
up_read(&sb_dqopt(sb)->dqptr_sem); up(&sb_dqopt(sb)->dqonoff_sem);
return 0; return 0;
} }
......
...@@ -1958,6 +1958,18 @@ int ext3_statfs (struct super_block * sb, struct kstatfs * buf) ...@@ -1958,6 +1958,18 @@ int ext3_statfs (struct super_block * sb, struct kstatfs * buf)
#define EXT3_V0_QFMT_BLOCKS 27 #define EXT3_V0_QFMT_BLOCKS 27
static int (*old_write_dquot)(struct dquot *dquot); static int (*old_write_dquot)(struct dquot *dquot);
static void (*old_drop_dquot)(struct inode *inode);
static int fmt_to_blocks(int fmt)
{
switch (fmt) {
case QFMT_VFS_OLD:
return EXT3_OLD_QFMT_BLOCKS;
case QFMT_VFS_V0:
return EXT3_V0_QFMT_BLOCKS;
}
return EXT3_MAX_TRANS_DATA;
}
static int ext3_write_dquot(struct dquot *dquot) static int ext3_write_dquot(struct dquot *dquot)
{ {
...@@ -1965,20 +1977,11 @@ static int ext3_write_dquot(struct dquot *dquot) ...@@ -1965,20 +1977,11 @@ static int ext3_write_dquot(struct dquot *dquot)
int ret; int ret;
int err; int err;
handle_t *handle; handle_t *handle;
struct quota_info *dqops = sb_dqopt(dquot->dq_sb); struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
struct inode *qinode; struct inode *qinode;
switch (dqops->info[dquot->dq_type].dqi_format->qf_fmt_id) { nblocks = fmt_to_blocks(dqopt->info[dquot->dq_type].dqi_format->qf_fmt_id);
case QFMT_VFS_OLD: qinode = dqopt->files[dquot->dq_type]->f_dentry->d_inode;
nblocks = EXT3_OLD_QFMT_BLOCKS;
break;
case QFMT_VFS_V0:
nblocks = EXT3_V0_QFMT_BLOCKS;
break;
default:
nblocks = EXT3_MAX_TRANS_DATA;
}
qinode = dqops->files[dquot->dq_type]->f_dentry->d_inode;
handle = ext3_journal_start(qinode, nblocks); handle = ext3_journal_start(qinode, nblocks);
if (IS_ERR(handle)) { if (IS_ERR(handle)) {
ret = PTR_ERR(handle); ret = PTR_ERR(handle);
...@@ -1991,6 +1994,28 @@ static int ext3_write_dquot(struct dquot *dquot) ...@@ -1991,6 +1994,28 @@ static int ext3_write_dquot(struct dquot *dquot)
out: out:
return ret; return ret;
} }
static void ext3_drop_dquot(struct inode *inode)
{
int nblocks, type;
struct quota_info *dqopt = sb_dqopt(inode->i_sb);
handle_t *handle;
for (type = 0; type < MAXQUOTAS; type++) {
if (sb_has_quota_enabled(inode->i_sb, type))
break;
}
if (type < MAXQUOTAS)
nblocks = fmt_to_blocks(dqopt->info[type].dqi_format->qf_fmt_id);
else
nblocks = 0; /* No quota => no drop */
handle = ext3_journal_start(inode, 2*nblocks);
if (IS_ERR(handle))
return;
old_drop_dquot(inode);
ext3_journal_stop(handle);
return;
}
#endif #endif
static struct super_block *ext3_get_sb(struct file_system_type *fs_type, static struct super_block *ext3_get_sb(struct file_system_type *fs_type,
...@@ -2018,7 +2043,9 @@ static int __init init_ext3_fs(void) ...@@ -2018,7 +2043,9 @@ static int __init init_ext3_fs(void)
#ifdef CONFIG_QUOTA #ifdef CONFIG_QUOTA
init_dquot_operations(&ext3_qops); init_dquot_operations(&ext3_qops);
old_write_dquot = ext3_qops.write_dquot; old_write_dquot = ext3_qops.write_dquot;
old_drop_dquot = ext3_qops.drop;
ext3_qops.write_dquot = ext3_write_dquot; ext3_qops.write_dquot = ext3_write_dquot;
ext3_qops.drop = ext3_drop_dquot;
#endif #endif
err = register_filesystem(&ext3_fs_type); err = register_filesystem(&ext3_fs_type);
if (err) if (err)
......
...@@ -1216,15 +1216,13 @@ EXPORT_SYMBOL(inode_needs_sync); ...@@ -1216,15 +1216,13 @@ EXPORT_SYMBOL(inode_needs_sync);
*/ */
#ifdef CONFIG_QUOTA #ifdef CONFIG_QUOTA
/* Functions back in dquot.c */ /* Function back in dquot.c */
void put_dquot_list(struct list_head *);
int remove_inode_dquot_ref(struct inode *, int, struct list_head *); int remove_inode_dquot_ref(struct inode *, int, struct list_head *);
void remove_dquot_ref(struct super_block *sb, int type) void remove_dquot_ref(struct super_block *sb, int type, struct list_head *tofree_head)
{ {
struct inode *inode; struct inode *inode;
struct list_head *act_head; struct list_head *act_head;
LIST_HEAD(tofree_head);
if (!sb->dq_op) if (!sb->dq_op)
return; /* nothing to do */ return; /* nothing to do */
...@@ -1234,26 +1232,24 @@ void remove_dquot_ref(struct super_block *sb, int type) ...@@ -1234,26 +1232,24 @@ void remove_dquot_ref(struct super_block *sb, int type)
list_for_each(act_head, &inode_in_use) { list_for_each(act_head, &inode_in_use) {
inode = list_entry(act_head, struct inode, i_list); inode = list_entry(act_head, struct inode, i_list);
if (inode->i_sb == sb && IS_QUOTAINIT(inode)) if (inode->i_sb == sb && IS_QUOTAINIT(inode))
remove_inode_dquot_ref(inode, type, &tofree_head); remove_inode_dquot_ref(inode, type, tofree_head);
} }
list_for_each(act_head, &inode_unused) { list_for_each(act_head, &inode_unused) {
inode = list_entry(act_head, struct inode, i_list); inode = list_entry(act_head, struct inode, i_list);
if (inode->i_sb == sb && IS_QUOTAINIT(inode)) if (inode->i_sb == sb && IS_QUOTAINIT(inode))
remove_inode_dquot_ref(inode, type, &tofree_head); remove_inode_dquot_ref(inode, type, tofree_head);
} }
list_for_each(act_head, &sb->s_dirty) { list_for_each(act_head, &sb->s_dirty) {
inode = list_entry(act_head, struct inode, i_list); inode = list_entry(act_head, struct inode, i_list);
if (IS_QUOTAINIT(inode)) if (IS_QUOTAINIT(inode))
remove_inode_dquot_ref(inode, type, &tofree_head); remove_inode_dquot_ref(inode, type, tofree_head);
} }
list_for_each(act_head, &sb->s_io) { list_for_each(act_head, &sb->s_io) {
inode = list_entry(act_head, struct inode, i_list); inode = list_entry(act_head, struct inode, i_list);
if (IS_QUOTAINIT(inode)) if (IS_QUOTAINIT(inode))
remove_inode_dquot_ref(inode, type, &tofree_head); remove_inode_dquot_ref(inode, type, tofree_head);
} }
spin_unlock(&inode_lock); spin_unlock(&inode_lock);
put_dquot_list(&tofree_head);
} }
#endif #endif
......
...@@ -64,11 +64,8 @@ static __inline__ int DQUOT_PREALLOC_SPACE_NODIRTY(struct inode *inode, qsize_t ...@@ -64,11 +64,8 @@ static __inline__ int DQUOT_PREALLOC_SPACE_NODIRTY(struct inode *inode, qsize_t
if (inode->i_sb->dq_op->alloc_space(inode, nr, 1) == NO_QUOTA) if (inode->i_sb->dq_op->alloc_space(inode, nr, 1) == NO_QUOTA)
return 1; return 1;
} }
else { else
spin_lock(&dq_data_lock);
inode_add_bytes(inode, nr); inode_add_bytes(inode, nr);
spin_unlock(&dq_data_lock);
}
return 0; return 0;
} }
...@@ -87,11 +84,8 @@ static __inline__ int DQUOT_ALLOC_SPACE_NODIRTY(struct inode *inode, qsize_t nr) ...@@ -87,11 +84,8 @@ static __inline__ int DQUOT_ALLOC_SPACE_NODIRTY(struct inode *inode, qsize_t nr)
if (inode->i_sb->dq_op->alloc_space(inode, nr, 0) == NO_QUOTA) if (inode->i_sb->dq_op->alloc_space(inode, nr, 0) == NO_QUOTA)
return 1; return 1;
} }
else { else
spin_lock(&dq_data_lock);
inode_add_bytes(inode, nr); inode_add_bytes(inode, nr);
spin_unlock(&dq_data_lock);
}
return 0; return 0;
} }
...@@ -117,11 +111,8 @@ static __inline__ void DQUOT_FREE_SPACE_NODIRTY(struct inode *inode, qsize_t nr) ...@@ -117,11 +111,8 @@ static __inline__ void DQUOT_FREE_SPACE_NODIRTY(struct inode *inode, qsize_t nr)
{ {
if (sb_any_quota_enabled(inode->i_sb)) if (sb_any_quota_enabled(inode->i_sb))
inode->i_sb->dq_op->free_space(inode, nr); inode->i_sb->dq_op->free_space(inode, nr);
else { else
spin_lock(&dq_data_lock);
inode_sub_bytes(inode, nr); inode_sub_bytes(inode, nr);
spin_unlock(&dq_data_lock);
}
} }
static __inline__ void DQUOT_FREE_SPACE(struct inode *inode, qsize_t nr) static __inline__ void DQUOT_FREE_SPACE(struct inode *inode, qsize_t nr)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment