From: Jan Kara I've extracted the locking changes from journalled quota patch relevant for vanilla kernel (basically fixes the lock ordering in some cases). The patch also includes documentation of the quota locking. --- 25-akpm/fs/dquot.c | 200 +++++++++++++++++++++------------------ 25-akpm/fs/ext3/super.c | 51 +++++++-- 25-akpm/fs/inode.c | 16 +-- 25-akpm/include/linux/quotaops.h | 15 -- 4 files changed, 160 insertions(+), 122 deletions(-) diff -puN fs/dquot.c~quota-locking-fixes fs/dquot.c --- 25/fs/dquot.c~quota-locking-fixes 2004-03-17 10:28:53.830431848 -0800 +++ 25-akpm/fs/dquot.c 2004-03-17 10:28:53.841430176 -0800 @@ -85,12 +85,31 @@ * 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 * 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 - * quota is disabled - i_sem should serialize the access. dq_data_lock should - * be always grabbed before dq_list_lock. + * i_blocks and i_bytes updates itself are guarded by i_lock acquired directly + * in inode_add_bytes() and inode_sub_bytes(). + * + * 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 * 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_data_lock = SPIN_LOCK_UNLOCKED; @@ -169,23 +188,6 @@ static void put_quota_format(struct quot * 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(free_dquots); static struct list_head dquot_hash[NR_DQHASH]; @@ -286,9 +288,9 @@ static int commit_dqblk(struct dquot *dq } /* 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 - * for writing and pointers were already removed from inodes we actually know that - * no quota for this sb+type should be held. */ + * quota is disabled so no new quota might be created. Because we hold + * dqonoff_sem and pointers were already removed from inodes we actually know + * that no quota for this sb+type should be held. */ static void invalidate_dquots(struct super_block *sb, int type) { struct dquot *dquot; @@ -303,7 +305,6 @@ static void invalidate_dquots(struct sup if (dquot->dq_type != type) continue; #ifdef __DQUOT_PARANOIA - /* There should be no users of quota - we hold dqptr_sem for writing */ if (atomic_read(&dquot->dq_count)) BUG(); #endif @@ -323,7 +324,7 @@ static int vfs_quota_sync(struct super_b struct quota_info *dqopt = sb_dqopt(sb); int cnt; - down_read(&dqopt->dqptr_sem); + down(&dqopt->dqonoff_sem); restart: /* At this point any dirty dquot will definitely be written so we can clear dirty flag from info */ @@ -359,7 +360,7 @@ restart: spin_lock(&dq_list_lock); dqstats.syncs++; spin_unlock(&dq_list_lock); - up_read(&dqopt->dqptr_sem); + up(&dqopt->dqonoff_sem); return 0; } @@ -402,7 +403,7 @@ static int shrink_dqcache_memory(int nr, /* * Put reference to dquot * 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) { @@ -467,7 +468,7 @@ static struct dquot *get_empty_dquot(str /* * 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) { @@ -528,7 +529,7 @@ static int dqinit_needed(struct inode *i 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) { struct list_head *p; @@ -594,7 +595,7 @@ put_it: /* 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 */ -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 dquot *dquot; @@ -609,6 +610,20 @@ void put_dquot_list(struct list_head *to } } +/* 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) { dquot->dq_dqb.dqb_curinodes += number; @@ -804,6 +819,9 @@ void dquot_initialize(struct inode *inod unsigned int id = 0; 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); /* Having dqptr_sem we know NOQUOTA flags can't be altered... */ if (IS_NOQUOTA(inode)) { @@ -832,49 +850,22 @@ void dquot_initialize(struct inode *inod } /* - * Remove references to quota from inode - * This function needs dqptr_sem for writing - */ -static void dquot_drop_iupdate(struct inode *inode, struct dquot **to_drop) -{ - int cnt; - - inode->i_flags &= ~S_QUOTA; - for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - to_drop[cnt] = inode->i_dquot[cnt]; - inode->i_dquot[cnt] = NODQUOT; - } -} - -/* * Release all quotas referenced by inode + * Needs dqonoff_sem to guard dqput() */ 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); + inode->i_flags &= ~S_QUOTA; + for (cnt = 0; cnt < MAXQUOTAS; cnt++) { + if (inode->i_dquot[cnt] != NODQUOT) { + dqput(inode->i_dquot[cnt]); + inode->i_dquot[cnt] = NODQUOT; + } + } 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 *inod int cnt, ret = NO_QUOTA; 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++) warntype[cnt] = NOWARN; down_read(&sb_dqopt(inode->i_sb)->dqptr_sem); spin_lock(&dq_data_lock); + /* Now recheck reliably when holding dqptr_sem */ if (IS_NOQUOTA(inode)) goto add_bytes; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { @@ -921,9 +918,13 @@ int dquot_alloc_inode(const struct inode int cnt, ret = NO_QUOTA; 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++) warntype[cnt] = NOWARN; down_read(&sb_dqopt(inode->i_sb)->dqptr_sem); + /* Now recheck reliably when holding dqptr_sem */ if (IS_NOQUOTA(inode)) { up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); return QUOTA_OK; @@ -956,8 +957,14 @@ void dquot_free_space(struct inode *inod { unsigned int cnt; + /* Solve deadlock when we recurse when holding dqptr_sem... */ + if (IS_NOQUOTA(inode)) { + inode_add_bytes(inode, number); + return; + } down_read(&sb_dqopt(inode->i_sb)->dqptr_sem); spin_lock(&dq_data_lock); + /* Now recheck reliably when holding dqptr_sem */ if (IS_NOQUOTA(inode)) goto sub_bytes; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { @@ -978,7 +985,11 @@ void dquot_free_inode(const struct inode { 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); + /* Now recheck reliably when holding dqptr_sem */ if (IS_NOQUOTA(inode)) { up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); return; @@ -1007,14 +1018,20 @@ int dquot_transfer(struct inode *inode, chgid = (iattr->ia_valid & ATTR_GID) && inode->i_gid != iattr->ia_gid; char warntype[MAXQUOTAS]; + /* Solve deadlock when we recurse when holding dqptr_sem... */ + if (IS_NOQUOTA(inode)) + return QUOTA_OK; /* Clear the arrays */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { transfer_to[cnt] = transfer_from[cnt] = NODQUOT; warntype[cnt] = NOWARN; } + down(&sb_dqopt(inode->i_sb)->dqonoff_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? */ up_write(&sb_dqopt(inode->i_sb)->dqptr_sem); + up(&sb_dqopt(inode->i_sb)->dqonoff_sem); return QUOTA_OK; } /* First build the transfer_to list - here we can block on reading of dquots... */ @@ -1065,6 +1082,7 @@ int dquot_transfer(struct inode *inode, ret = QUOTA_OK; warn_put_all: spin_unlock(&dq_data_lock); + up_write(&sb_dqopt(inode->i_sb)->dqptr_sem); flush_warnings(transfer_to, warntype); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { @@ -1073,7 +1091,7 @@ warn_put_all: if (ret == NO_QUOTA && transfer_to[cnt] != NODQUOT) dqput(transfer_to[cnt]); } - up_write(&sb_dqopt(inode->i_sb)->dqptr_sem); + up(&sb_dqopt(inode->i_sb)->dqonoff_sem); return ret; } @@ -1121,9 +1139,6 @@ static inline void reset_enable_flags(st } } -/* 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) */ @@ -1137,7 +1152,6 @@ int vfs_quota_off(struct super_block *sb /* We need to serialize quota_off() for device */ down(&dqopt->dqonoff_sem); - down_write(&dqopt->dqptr_sem); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (type != -1 && cnt != type) continue; @@ -1146,7 +1160,7 @@ int vfs_quota_off(struct super_block *sb reset_enable_flags(dqopt, cnt); /* Note: these are blocking operations */ - remove_dquot_ref(sb, cnt); + drop_dquot_ref(sb, cnt); invalidate_dquots(sb, cnt); /* * 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 dqopt->info[cnt].dqi_bgrace = 0; dqopt->ops[cnt] = NULL; } - up_write(&dqopt->dqptr_sem); up(&dqopt->dqonoff_sem); out: return 0; @@ -1180,7 +1193,8 @@ int vfs_quota_on(struct super_block *sb, struct inode *inode; struct quota_info *dqopt = sb_dqopt(sb); struct quota_format_type *fmt = find_quota_format(format_id); - int error; + int error, cnt; + struct dquot *to_drop[MAXQUOTAS]; unsigned int oldflags; if (!fmt) @@ -1202,7 +1216,6 @@ int vfs_quota_on(struct super_block *sb, goto out_f; down(&dqopt->dqonoff_sem); - down_write(&dqopt->dqptr_sem); if (sb_has_quota_enabled(sb, type)) { error = -EBUSY; goto out_lock; @@ -1213,8 +1226,20 @@ int vfs_quota_on(struct super_block *sb, if (!fmt->qf_ops->check_quota_file(sb, type)) goto out_file_init; /* 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; + 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->info[type].dqi_format = fmt; @@ -1225,7 +1250,6 @@ int vfs_quota_on(struct super_block *sb, } up(&dqopt->dqio_sem); set_enable_flags(dqopt, type); - up_write(&dqopt->dqptr_sem); add_dquot_ref(sb, type); up(&dqopt->dqonoff_sem); @@ -1268,14 +1292,14 @@ int vfs_get_dqblk(struct super_block *sb { struct dquot *dquot; - down_read(&sb_dqopt(sb)->dqptr_sem); + down(&sb_dqopt(sb)->dqonoff_sem); if (!(dquot = dqget(sb, id, type))) { - up_read(&sb_dqopt(sb)->dqptr_sem); + up(&sb_dqopt(sb)->dqonoff_sem); return -ESRCH; } do_get_dqblk(dquot, di); dqput(dquot); - up_read(&sb_dqopt(sb)->dqptr_sem); + up(&sb_dqopt(sb)->dqonoff_sem); return 0; } @@ -1337,14 +1361,14 @@ int vfs_set_dqblk(struct super_block *sb { struct dquot *dquot; - down_read(&sb_dqopt(sb)->dqptr_sem); + down(&sb_dqopt(sb)->dqonoff_sem); if (!(dquot = dqget(sb, id, type))) { - up_read(&sb_dqopt(sb)->dqptr_sem); + up(&sb_dqopt(sb)->dqonoff_sem); return -ESRCH; } do_set_dqblk(dquot, di); dqput(dquot); - up_read(&sb_dqopt(sb)->dqptr_sem); + up(&sb_dqopt(sb)->dqonoff_sem); return 0; } @@ -1353,9 +1377,9 @@ int vfs_get_dqinfo(struct super_block *s { struct mem_dqinfo *mi; - down_read(&sb_dqopt(sb)->dqptr_sem); + down(&sb_dqopt(sb)->dqonoff_sem); if (!sb_has_quota_enabled(sb, type)) { - up_read(&sb_dqopt(sb)->dqptr_sem); + up(&sb_dqopt(sb)->dqonoff_sem); return -ESRCH; } mi = sb_dqopt(sb)->info + type; @@ -1365,7 +1389,7 @@ int vfs_get_dqinfo(struct super_block *s ii->dqi_flags = mi->dqi_flags & DQF_MASK; ii->dqi_valid = IIF_ALL; spin_unlock(&dq_data_lock); - up_read(&sb_dqopt(sb)->dqptr_sem); + up(&sb_dqopt(sb)->dqonoff_sem); return 0; } @@ -1374,9 +1398,9 @@ int vfs_set_dqinfo(struct super_block *s { struct mem_dqinfo *mi; - down_read(&sb_dqopt(sb)->dqptr_sem); + down(&sb_dqopt(sb)->dqonoff_sem); if (!sb_has_quota_enabled(sb, type)) { - up_read(&sb_dqopt(sb)->dqptr_sem); + up(&sb_dqopt(sb)->dqonoff_sem); return -ESRCH; } mi = sb_dqopt(sb)->info + type; @@ -1389,7 +1413,7 @@ int vfs_set_dqinfo(struct super_block *s mi->dqi_flags = (mi->dqi_flags & ~DQF_MASK) | (ii->dqi_flags & DQF_MASK); mark_info_dirty(mi); spin_unlock(&dq_data_lock); - up_read(&sb_dqopt(sb)->dqptr_sem); + up(&sb_dqopt(sb)->dqonoff_sem); return 0; } diff -puN fs/ext3/super.c~quota-locking-fixes fs/ext3/super.c --- 25/fs/ext3/super.c~quota-locking-fixes 2004-03-17 10:28:53.832431544 -0800 +++ 25-akpm/fs/ext3/super.c 2004-03-17 10:28:53.843429872 -0800 @@ -1958,6 +1958,18 @@ int ext3_statfs (struct super_block * sb #define EXT3_V0_QFMT_BLOCKS 27 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) { @@ -1965,20 +1977,11 @@ static int ext3_write_dquot(struct dquot int ret; int err; handle_t *handle; - struct quota_info *dqops = sb_dqopt(dquot->dq_sb); + struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); struct inode *qinode; - switch (dqops->info[dquot->dq_type].dqi_format->qf_fmt_id) { - case QFMT_VFS_OLD: - 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; + nblocks = fmt_to_blocks(dqopt->info[dquot->dq_type].dqi_format->qf_fmt_id); + qinode = dqopt->files[dquot->dq_type]->f_dentry->d_inode; handle = ext3_journal_start(qinode, nblocks); if (IS_ERR(handle)) { ret = PTR_ERR(handle); @@ -1991,6 +1994,28 @@ static int ext3_write_dquot(struct dquot out: 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 static struct super_block *ext3_get_sb(struct file_system_type *fs_type, @@ -2018,7 +2043,9 @@ static int __init init_ext3_fs(void) #ifdef CONFIG_QUOTA init_dquot_operations(&ext3_qops); old_write_dquot = ext3_qops.write_dquot; + old_drop_dquot = ext3_qops.drop; ext3_qops.write_dquot = ext3_write_dquot; + ext3_qops.drop = ext3_drop_dquot; #endif err = register_filesystem(&ext3_fs_type); if (err) diff -puN fs/inode.c~quota-locking-fixes fs/inode.c --- 25/fs/inode.c~quota-locking-fixes 2004-03-17 10:28:53.834431240 -0800 +++ 25-akpm/fs/inode.c 2004-03-17 10:28:53.844429720 -0800 @@ -1216,15 +1216,13 @@ EXPORT_SYMBOL(inode_needs_sync); */ #ifdef CONFIG_QUOTA -/* Functions back in dquot.c */ -void put_dquot_list(struct list_head *); +/* Function back in dquot.c */ 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 list_head *act_head; - LIST_HEAD(tofree_head); if (!sb->dq_op) return; /* nothing to do */ @@ -1234,26 +1232,24 @@ void remove_dquot_ref(struct super_block list_for_each(act_head, &inode_in_use) { inode = list_entry(act_head, struct inode, i_list); 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) { inode = list_entry(act_head, struct inode, i_list); 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) { inode = list_entry(act_head, struct inode, i_list); 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) { inode = list_entry(act_head, struct inode, i_list); if (IS_QUOTAINIT(inode)) - remove_inode_dquot_ref(inode, type, &tofree_head); + remove_inode_dquot_ref(inode, type, tofree_head); } spin_unlock(&inode_lock); - - put_dquot_list(&tofree_head); } #endif diff -puN include/linux/quotaops.h~quota-locking-fixes include/linux/quotaops.h --- 25/include/linux/quotaops.h~quota-locking-fixes 2004-03-17 10:28:53.835431088 -0800 +++ 25-akpm/include/linux/quotaops.h 2004-03-17 10:28:53.845429568 -0800 @@ -64,11 +64,8 @@ static __inline__ int DQUOT_PREALLOC_SPA if (inode->i_sb->dq_op->alloc_space(inode, nr, 1) == NO_QUOTA) return 1; } - else { - spin_lock(&dq_data_lock); + else inode_add_bytes(inode, nr); - spin_unlock(&dq_data_lock); - } return 0; } @@ -87,11 +84,8 @@ static __inline__ int DQUOT_ALLOC_SPACE_ if (inode->i_sb->dq_op->alloc_space(inode, nr, 0) == NO_QUOTA) return 1; } - else { - spin_lock(&dq_data_lock); + else inode_add_bytes(inode, nr); - spin_unlock(&dq_data_lock); - } return 0; } @@ -117,11 +111,8 @@ static __inline__ void DQUOT_FREE_SPACE_ { if (sb_any_quota_enabled(inode->i_sb)) inode->i_sb->dq_op->free_space(inode, nr); - else { - spin_lock(&dq_data_lock); + else inode_sub_bytes(inode, nr); - spin_unlock(&dq_data_lock); - } } static __inline__ void DQUOT_FREE_SPACE(struct inode *inode, qsize_t nr) _