From: Jan Kara Here comes journalled quota patch for 2.6.3-rc2. The patch implements quota journalling for ext3. What the patch basically does is that all quota writes are journalled in the same transaction as an update of changed inode so quota information should always stay consistent with a real usage (i.e. no quotacheck needed). The patch also handles a case of orphan cleanup in ext3 by turning quotas on before cleanup is started and quotas are turned off after the cleanup is finished. This requires some tricks because quota code needs to open a file in a not-yet mounted filesystem but hopefully I managed to implement it in a clean way. To test journalled quota you need to apply attached patch (also available at ftp://atrey.karlin.mff.cuni.cz/pub/local/jack/quota/v2.6/quota-2.6.3-rc2-1-jquota.diff.gz) to kernel tree. Compile the kernel and run it. To use journalled quota you need to mount a filesystem with following options: usrjquota= grpjquota=dq_lock); } +#define mark_dquot_dirty(dquot) ((dquot)->dq_sb->dq_op->mark_dirty(dquot)) + +int dquot_mark_dquot_dirty(struct dquot *dquot) +{ + spin_lock(&dq_data_lock); + set_bit(DQF_ANY_DQUOT_DIRTY_B, &(sb_dqopt((dquot)->dq_sb)->info[(dquot)->dq_type].dqi_flags)); + set_bit(DQ_MOD_B, &(dquot)->dq_flags); + spin_unlock(&dq_data_lock); + return 0; +} + +void mark_info_dirty(struct super_block *sb, int type) +{ + spin_lock(&dq_data_lock); + set_bit(DQF_INFO_DIRTY_B, &sb_dqopt(sb)->info[type].dqi_flags); + spin_unlock(&dq_data_lock); +} + + static int read_dqblk(struct dquot *dquot) { int ret; @@ -274,14 +293,21 @@ static int read_dqblk(struct dquot *dquo return ret; } -static int commit_dqblk(struct dquot *dquot) +/* + * Write dquot to disk + */ +int dquot_commit(struct dquot *dquot, int init) { - int ret; + int ret = 0; struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); down(&dqopt->dqio_sem); - ret = dqopt->ops[dquot->dq_type]->commit_dqblk(dquot); + clear_bit(DQ_MOD_B, &dquot->dq_flags); + if (!init || !dquot->dq_off) /* Isn't dquot already allocated? */ + ret = dqopt->ops[dquot->dq_type]->commit_dqblk(dquot, init); up(&dqopt->dqio_sem); + if (info_dirty(&dqopt->info[dquot->dq_type])) + dquot->dq_sb->dq_op->write_info(dquot->dq_sb, dquot->dq_type); return ret; } @@ -316,7 +342,7 @@ static void invalidate_dquots(struct sup spin_unlock(&dq_list_lock); } -static int vfs_quota_sync(struct super_block *sb, int type) +int vfs_quota_sync(struct super_block *sb, int type) { struct list_head *head; struct dquot *dquot; @@ -327,9 +353,11 @@ static int vfs_quota_sync(struct super_b restart: /* At this point any dirty dquot will definitely be written so we can clear dirty flag from info */ + spin_lock(&dq_data_lock); for (cnt = 0; cnt < MAXQUOTAS; cnt++) if ((cnt == type || type == -1) && sb_has_quota_enabled(sb, cnt)) clear_bit(DQF_ANY_DQUOT_DIRTY_B, &dqopt->info[cnt].dqi_flags); + spin_unlock(&dq_data_lock); spin_lock(&dq_list_lock); list_for_each(head, &inuse_list) { dquot = list_entry(head, struct dquot, dq_inuse); @@ -344,18 +372,15 @@ restart: atomic_inc(&dquot->dq_count); dqstats.lookups++; spin_unlock(&dq_list_lock); - sb->dq_op->write_dquot(dquot); + sb->dq_op->write_dquot(dquot, 0); dqput(dquot); goto restart; } spin_unlock(&dq_list_lock); for (cnt = 0; cnt < MAXQUOTAS; cnt++) - if ((cnt == type || type == -1) && sb_has_quota_enabled(sb, cnt) && info_dirty(&dqopt->info[cnt])) { - down(&dqopt->dqio_sem); - dqopt->ops[cnt]->write_file_info(sb, cnt); - up(&dqopt->dqio_sem); - } + if ((cnt == type || type == -1) && sb_has_quota_enabled(sb, cnt) && info_dirty(&dqopt->info[cnt])) + sb->dq_op->write_info(sb, cnt); spin_lock(&dq_list_lock); dqstats.syncs++; spin_unlock(&dq_list_lock); @@ -430,9 +455,10 @@ we_slept: spin_unlock(&dq_list_lock); return; } + /* This should never happen when we are journalling quota */ if (dquot_dirty(dquot)) { spin_unlock(&dq_list_lock); - dquot->dq_sb->dq_op->write_dquot(dquot); + dquot->dq_sb->dq_op->write_dquot(dquot, 0); goto we_slept; } atomic_dec(&dquot->dq_count); @@ -539,12 +565,10 @@ restart: struct file *filp = list_entry(p, struct file, f_list); struct inode *inode = filp->f_dentry->d_inode; if (filp->f_mode & FMODE_WRITE && dqinit_needed(inode, type)) { - struct vfsmount *mnt = mntget(filp->f_vfsmnt); struct dentry *dentry = dget(filp->f_dentry); file_list_unlock(); sb->dq_op->initialize(inode, type); dput(dentry); - mntput(mnt); /* As we may have blocked we had better restart... */ goto restart; } @@ -612,13 +636,11 @@ void put_dquot_list(struct list_head *to static inline void dquot_incr_inodes(struct dquot *dquot, unsigned long number) { dquot->dq_dqb.dqb_curinodes += number; - mark_dquot_dirty(dquot); } static inline void dquot_incr_space(struct dquot *dquot, qsize_t number) { dquot->dq_dqb.dqb_curspace += number; - mark_dquot_dirty(dquot); } static inline void dquot_decr_inodes(struct dquot *dquot, unsigned long number) @@ -630,7 +652,6 @@ static inline void dquot_decr_inodes(str if (dquot->dq_dqb.dqb_curinodes < dquot->dq_dqb.dqb_isoftlimit) dquot->dq_dqb.dqb_itime = (time_t) 0; clear_bit(DQ_INODES_B, &dquot->dq_flags); - mark_dquot_dirty(dquot); } static inline void dquot_decr_space(struct dquot *dquot, qsize_t number) @@ -642,7 +663,6 @@ static inline void dquot_decr_space(stru if (toqb(dquot->dq_dqb.dqb_curspace) < dquot->dq_dqb.dqb_bsoftlimit) dquot->dq_dqb.dqb_btime = (time_t) 0; clear_bit(DQ_BLKS_B, &dquot->dq_flags); - mark_dquot_dirty(dquot); } static inline int need_print_warning(struct dquot *dquot) @@ -799,17 +819,19 @@ static int check_bdq(struct dquot *dquot * * Note: this is a blocking operation. */ -void dquot_initialize(struct inode *inode, int type) +int dquot_initialize(struct inode *inode, int type) { unsigned int id = 0; - int cnt; + int cnt, ret = 0; + /* First test before acquiring semaphore - solves deadlocks when we + * re-enter the quota code and are already holding the semaphore */ + if (IS_NOQUOTA(inode)) + return 0; down_write(&sb_dqopt(inode->i_sb)->dqptr_sem); /* Having dqptr_sem we know NOQUOTA flags can't be altered... */ - if (IS_NOQUOTA(inode)) { - up_write(&sb_dqopt(inode->i_sb)->dqptr_sem); - return; - } + if (IS_NOQUOTA(inode)) + goto out_err; /* Build list of quotas to initialize... */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (type != -1 && cnt != type) @@ -824,18 +846,23 @@ void dquot_initialize(struct inode *inod break; } inode->i_dquot[cnt] = dqget(inode->i_sb, id, cnt); - if (inode->i_dquot[cnt]) + if (inode->i_dquot[cnt]) { inode->i_flags |= S_QUOTA; + /* Write dquot to allocate space in file if it's not allocated yet */ + inode->i_sb->dq_op->write_dquot(inode->i_dquot[cnt], 1); + } } } +out_err: up_write(&sb_dqopt(inode->i_sb)->dqptr_sem); + return ret; } /* * 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) +static int dquot_drop_iupdate(struct inode *inode, struct dquot **to_drop) { int cnt; @@ -844,22 +871,24 @@ static void dquot_drop_iupdate(struct in to_drop[cnt] = inode->i_dquot[cnt]; inode->i_dquot[cnt] = NODQUOT; } + return 0; } /* * Release all quotas referenced by inode */ -void dquot_drop(struct inode *inode) +int dquot_drop(struct inode *inode) { struct dquot *to_drop[MAXQUOTAS]; - int cnt; + int cnt, ret; down_write(&sb_dqopt(inode->i_sb)->dqptr_sem); - dquot_drop_iupdate(inode, to_drop); + ret = dquot_drop_iupdate(inode, to_drop); up_write(&sb_dqopt(inode->i_sb)->dqptr_sem); for (cnt = 0; cnt < MAXQUOTAS; cnt++) if (to_drop[cnt] != NODQUOT) dqput(to_drop[cnt]); + return ret; } /* @@ -878,6 +907,15 @@ void dquot_drop_nolock(struct inode *ino } /* + * Following four functions update i_blocks+i_bytes fields and + * quota information (togethter with appropriate checks) + * NOTE: We absolutely rely on the fact that caller dirtifies + * the inode (usually macros in quotaops.h care about this) and + * holds a handle for the current transaction so that dquot write and + * inode write go into the same transaction. + */ + +/* * This operation can block, but only after everything is updated */ int dquot_alloc_space(struct inode *inode, qsize_t number, int warn) @@ -885,13 +923,22 @@ int dquot_alloc_space(struct inode *inod int cnt, ret = NO_QUOTA; char warntype[MAXQUOTAS]; + /* First test before acquiring semaphore - solves deadlocks when we + * re-enter the quota code and are already holding the semaphore */ + if (IS_NOQUOTA(inode)) { +out_add: + 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); + if (IS_NOQUOTA(inode)) { /* Now we can do reliable test... */ + up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); + goto out_add; + } spin_lock(&dq_data_lock); - if (IS_NOQUOTA(inode)) - goto add_bytes; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (inode->i_dquot[cnt] == NODQUOT) continue; @@ -903,11 +950,15 @@ int dquot_alloc_space(struct inode *inod continue; dquot_incr_space(inode->i_dquot[cnt], number); } -add_bytes: inode_add_bytes(inode, number); ret = QUOTA_OK; warn_put_all: spin_unlock(&dq_data_lock); + if (ret == QUOTA_OK) + /* Dirtify all the dquots - this can block when journalling */ + for (cnt = 0; cnt < MAXQUOTAS; cnt++) + if (inode->i_dquot[cnt]) + mark_dquot_dirty(inode->i_dquot[cnt]); flush_warnings(inode->i_dquot, warntype); up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); return ret; @@ -921,6 +972,10 @@ int dquot_alloc_inode(const struct inode int cnt, ret = NO_QUOTA; char warntype[MAXQUOTAS]; + /* First test before acquiring semaphore - solves deadlocks when we + * re-enter the quota code and are already holding the semaphore */ + 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); @@ -944,6 +999,11 @@ int dquot_alloc_inode(const struct inode ret = QUOTA_OK; warn_put_all: spin_unlock(&dq_data_lock); + if (ret == QUOTA_OK) + /* Dirtify all the dquots - this can block when journalling */ + for (cnt = 0; cnt < MAXQUOTAS; cnt++) + if (inode->i_dquot[cnt]) + mark_dquot_dirty(inode->i_dquot[cnt]); flush_warnings((struct dquot **)inode->i_dquot, warntype); up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); return ret; @@ -952,36 +1012,53 @@ warn_put_all: /* * This is a non-blocking operation. */ -void dquot_free_space(struct inode *inode, qsize_t number) +int dquot_free_space(struct inode *inode, qsize_t number) { unsigned int cnt; + /* First test before acquiring semaphore - solves deadlocks when we + * re-enter the quota code and are already holding the semaphore */ + if (IS_NOQUOTA(inode)) { +out_sub: + inode_sub_bytes(inode, number); + return QUOTA_OK; + } down_read(&sb_dqopt(inode->i_sb)->dqptr_sem); + if (IS_NOQUOTA(inode)) { + up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); + goto out_sub; + } spin_lock(&dq_data_lock); - if (IS_NOQUOTA(inode)) - goto sub_bytes; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (inode->i_dquot[cnt] == NODQUOT) continue; dquot_decr_space(inode->i_dquot[cnt], number); } -sub_bytes: inode_sub_bytes(inode, number); spin_unlock(&dq_data_lock); + /* Dirtify all the dquots - this can block when journalling */ + for (cnt = 0; cnt < MAXQUOTAS; cnt++) + if (inode->i_dquot[cnt]) + mark_dquot_dirty(inode->i_dquot[cnt]); up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); + return QUOTA_OK; } /* * This is a non-blocking operation. */ -void dquot_free_inode(const struct inode *inode, unsigned long number) +int dquot_free_inode(const struct inode *inode, unsigned long number) { unsigned int cnt; + /* First test before acquiring semaphore - solves deadlocks when we + * re-enter the quota code and are already holding the semaphore */ + if (IS_NOQUOTA(inode)) + return QUOTA_OK; down_read(&sb_dqopt(inode->i_sb)->dqptr_sem); if (IS_NOQUOTA(inode)) { up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); - return; + return QUOTA_OK; } spin_lock(&dq_data_lock); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { @@ -990,7 +1067,12 @@ void dquot_free_inode(const struct inode dquot_decr_inodes(inode->i_dquot[cnt], number); } spin_unlock(&dq_data_lock); + /* Dirtify all the dquots - this can block when journalling */ + for (cnt = 0; cnt < MAXQUOTAS; cnt++) + if (inode->i_dquot[cnt]) + mark_dquot_dirty(inode->i_dquot[cnt]); up_read(&sb_dqopt(inode->i_sb)->dqptr_sem); + return QUOTA_OK; } /* @@ -1007,6 +1089,10 @@ int dquot_transfer(struct inode *inode, chgid = (iattr->ia_valid & ATTR_GID) && inode->i_gid != iattr->ia_gid; char warntype[MAXQUOTAS]; + /* First test before acquiring semaphore - solves deadlocks when we + * re-enter the quota code and are already holding the semaphore */ + if (IS_NOQUOTA(inode)) + return QUOTA_OK; /* Clear the arrays */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { transfer_to[cnt] = transfer_from[cnt] = NODQUOT; @@ -1065,6 +1151,13 @@ int dquot_transfer(struct inode *inode, ret = QUOTA_OK; warn_put_all: spin_unlock(&dq_data_lock); + /* Dirtify all the dquots - this can block when journalling */ + for (cnt = 0; cnt < MAXQUOTAS; cnt++) { + if (transfer_from[cnt]) + mark_dquot_dirty(transfer_from[cnt]); + if (transfer_to[cnt]) + mark_dquot_dirty(transfer_to[cnt]); + } flush_warnings(transfer_to, warntype); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { @@ -1078,25 +1171,35 @@ warn_put_all: } /* + * Write info of quota file to disk + */ +int dquot_commit_info(struct super_block *sb, int type) +{ + int ret; + struct quota_info *dqopt = sb_dqopt(sb); + + down(&dqopt->dqio_sem); + ret = dqopt->ops[type]->write_file_info(sb, type); + up(&dqopt->dqio_sem); + return ret; +} + +/* * Definitions of diskquota operations. */ struct dquot_operations dquot_operations = { - .initialize = dquot_initialize, /* mandatory */ - .drop = dquot_drop, /* mandatory */ + .initialize = dquot_initialize, + .drop = 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 = commit_dqblk + .write_dquot = dquot_commit, + .mark_dirty = dquot_mark_dquot_dirty, + .write_info = dquot_commit_info }; -/* Function used by filesystems for initializing the dquot_operations structure */ -void init_dquot_operations(struct dquot_operations *fsdqops) -{ - memcpy(fsdqops, &dquot_operations, sizeof(dquot_operations)); -} - static inline void set_enable_flags(struct quota_info *dqopt, int type) { switch (type) { @@ -1127,7 +1230,7 @@ extern void remove_dquot_ref(struct supe /* * Turn quota off on a device. type == -1 ==> quotaoff for all types (umount) */ -int vfs_quota_off(struct super_block *sb, int type) +static int __vfs_quota_off(struct super_block *sb, int type, int onmount) { int cnt; struct quota_info *dqopt = sb_dqopt(sb); @@ -1152,17 +1255,14 @@ int vfs_quota_off(struct super_block *sb * Now all dquots should be invalidated, all writes done so we should be only * users of the info. No locks needed. */ - if (info_dirty(&dqopt->info[cnt])) { - down(&dqopt->dqio_sem); - dqopt->ops[cnt]->write_file_info(sb, cnt); - up(&dqopt->dqio_sem); - } + if (info_dirty(&dqopt->info[cnt])) + sb->dq_op->write_info(sb, cnt); if (dqopt->ops[cnt]->free_file_info) dqopt->ops[cnt]->free_file_info(sb, cnt); put_quota_format(dqopt->info[cnt].dqi_format); fput(dqopt->files[cnt]); - dqopt->files[cnt] = (struct file *)NULL; + dqopt->files[cnt] = NULL; dqopt->info[cnt].dqi_flags = 0; dqopt->info[cnt].dqi_igrace = 0; dqopt->info[cnt].dqi_bgrace = 0; @@ -1174,32 +1274,39 @@ out: return 0; } -int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path) +int vfs_quota_off(struct super_block *sb, int type) { - struct file *f; + return __vfs_quota_off(sb, type, 0); +} + +int vfs_quota_off_mount(struct super_block *sb, int type) +{ + return __vfs_quota_off(sb, type, 1); +} + +/* + * Turn quotas on on a device + */ + +/* Helper function when we already have file open */ +static int vfs_quota_on_file(struct file *f, int type, int format_id) +{ + struct quota_format_type *fmt = find_quota_format(format_id); struct inode *inode; + struct super_block *sb = f->f_dentry->d_sb; struct quota_info *dqopt = sb_dqopt(sb); - struct quota_format_type *fmt = find_quota_format(format_id); int error; unsigned int oldflags; if (!fmt) return -ESRCH; - f = filp_open(path, O_RDWR, 0600); - if (IS_ERR(f)) { - error = PTR_ERR(f); - goto out_fmt; - } error = -EIO; if (!f->f_op || !f->f_op->read || !f->f_op->write) - goto out_f; - error = security_quota_on(f); - if (error) - goto out_f; + goto out_fmt; inode = f->f_dentry->d_inode; error = -EACCES; if (!S_ISREG(inode->i_mode)) - goto out_f; + goto out_fmt; down(&dqopt->dqonoff_sem); down_write(&dqopt->dqptr_sem); @@ -1238,14 +1345,57 @@ out_file_init: out_lock: up_write(&dqopt->dqptr_sem); up(&dqopt->dqonoff_sem); -out_f: - filp_close(f, NULL); out_fmt: put_quota_format(fmt); return error; } +/* Actual function called from quotactl() */ +int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path) +{ + struct file *f; + int error; + + f = filp_open(path, O_RDWR, 0600); + if (IS_ERR(f)) + return PTR_ERR(f); + error = security_quota_on(f); + if (error) + goto out_f; + error = vfs_quota_on_file(f, type, format_id); + if (!error) + return 0; +out_f: + filp_close(f, NULL); + return error; +} + +/* + * Function used by filesystems when filp_open() would fail (filesystem is being mounted now) + * We will use a private file structure. Caller is responsible that it's IO functions won't + * need vfsmnt structure or some dentry tricks... + */ +int vfs_quota_on_mount(int type, int format_id, struct dentry *dentry) +{ + struct file *f; + int error; + + dget(dentry); /* Get a reference for struct file */ + f = dentry_open(dentry, NULL, O_RDWR); + if (IS_ERR(f)) { + error = PTR_ERR(f); + goto out_dentry; + } + error = vfs_quota_on_file(f, type, format_id); + if (!error) + return 0; + fput(f); +out_dentry: + dput(dentry); + return error; +} + /* Generic routine for getting common part of quota structure */ static void do_get_dqblk(struct dquot *dquot, struct if_dqblk *di) { @@ -1329,8 +1479,8 @@ static void do_set_dqblk(struct dquot *d clear_bit(DQ_FAKE_B, &dquot->dq_flags); else set_bit(DQ_FAKE_B, &dquot->dq_flags); - mark_dquot_dirty(dquot); spin_unlock(&dq_data_lock); + mark_dquot_dirty(dquot); } int vfs_set_dqblk(struct super_block *sb, int type, qid_t id, struct if_dqblk *di) @@ -1387,9 +1537,11 @@ int vfs_set_dqinfo(struct super_block *s mi->dqi_igrace = ii->dqi_igrace; if (ii->dqi_valid & IIF_FLAGS) 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); + mark_info_dirty(sb, type); + /* Force write to disk */ + sb->dq_op->write_info(sb, type); return 0; } @@ -1520,4 +1672,3 @@ EXPORT_SYMBOL(unregister_quota_format); EXPORT_SYMBOL(dqstats); EXPORT_SYMBOL(dq_list_lock); EXPORT_SYMBOL(dq_data_lock); -EXPORT_SYMBOL(init_dquot_operations); diff -puN fs/ext3/inode.c~ext3-journalled-quotas fs/ext3/inode.c --- 25/fs/ext3/inode.c~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/fs/ext3/inode.c Thu Feb 12 17:33:42 2004 @@ -2772,9 +2772,25 @@ int ext3_setattr(struct dentry *dentry, if ((ia_valid & ATTR_UID && attr->ia_uid != inode->i_uid) || (ia_valid & ATTR_GID && attr->ia_gid != inode->i_gid)) { + handle_t *handle; + + handle = ext3_journal_start(inode, 2*EXT3_QUOTA_TRANS_BLOCKS+3); + if (IS_ERR(handle)) { + error = PTR_ERR(handle); + goto err_out; + } error = DQUOT_TRANSFER(inode, attr) ? -EDQUOT : 0; - if (error) + if (error) { + ext3_journal_stop(handle); return error; + } + /* Update corresponding info in inode so that everything is in one transaction */ + if (attr->ia_valid & ATTR_UID) + inode->i_uid = attr->ia_uid; + if (attr->ia_valid & ATTR_GID) + inode->i_gid = attr->ia_gid; + error = ext3_mark_inode_dirty(handle, inode); + ext3_journal_stop(handle); } if (S_ISREG(inode->i_mode) && @@ -2853,7 +2869,9 @@ int ext3_writepage_trans_blocks(struct i ret = 2 * (bpp + indirects) + 2; #ifdef CONFIG_QUOTA - ret += 2 * EXT3_SINGLEDATA_TRANS_BLOCKS; + /* We know that structure was already allocated during DQUOT_INIT so + * we will be updating only the data blocks + inodes */ + ret += 2*EXT3_QUOTA_TRANS_BLOCKS; #endif return ret; diff -puN fs/ext3/namei.c~ext3-journalled-quotas fs/ext3/namei.c --- 25/fs/ext3/namei.c~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/fs/ext3/namei.c Thu Feb 12 17:33:42 2004 @@ -1974,6 +1974,8 @@ static int ext3_rmdir (struct inode * di struct ext3_dir_entry_2 * de; handle_t *handle; + /* Initialize quotas before so that eventual writes goes in separate transaction */ + DQUOT_INIT(dentry->d_inode); handle = ext3_journal_start(dir, EXT3_DELETE_TRANS_BLOCKS); if (IS_ERR(handle)) return PTR_ERR(handle); @@ -1987,7 +1989,6 @@ static int ext3_rmdir (struct inode * di handle->h_sync = 1; inode = dentry->d_inode; - DQUOT_INIT(inode); retval = -EIO; if (le32_to_cpu(de->inode) != inode->i_ino) @@ -2031,6 +2032,8 @@ static int ext3_unlink(struct inode * di struct ext3_dir_entry_2 * de; handle_t *handle; + /* Initialize quotas before so that eventual writes goes in separate transaction */ + DQUOT_INIT(dentry->d_inode); handle = ext3_journal_start(dir, EXT3_DELETE_TRANS_BLOCKS); if (IS_ERR(handle)) return PTR_ERR(handle); @@ -2044,7 +2047,6 @@ static int ext3_unlink(struct inode * di goto end_unlink; inode = dentry->d_inode; - DQUOT_INIT(inode); retval = -EIO; if (le32_to_cpu(de->inode) != inode->i_ino) @@ -2172,6 +2174,9 @@ static int ext3_rename (struct inode * o old_bh = new_bh = dir_bh = NULL; + /* Initialize quotas before so that eventual writes goes in separate transaction */ + if (new_dentry->d_inode) + DQUOT_INIT(new_dentry->d_inode); handle = ext3_journal_start(old_dir, 2 * EXT3_DATA_TRANS_BLOCKS + EXT3_INDEX_EXTRA_TRANS_BLOCKS + 2); if (IS_ERR(handle)) @@ -2198,8 +2203,6 @@ static int ext3_rename (struct inode * o if (!new_inode) { brelse (new_bh); new_bh = NULL; - } else { - DQUOT_INIT(new_inode); } } if (S_ISDIR(old_inode->i_mode)) { diff -puN fs/ext3/super.c~ext3-journalled-quotas fs/ext3/super.c --- 25/fs/ext3/super.c~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/fs/ext3/super.c Thu Feb 12 17:33:42 2004 @@ -32,6 +32,9 @@ #include #include #include +#include +#include +#include #include #include "xattr.h" #include "acl.h" @@ -504,7 +507,41 @@ static void ext3_clear_inode(struct inod # define ext3_clear_inode NULL #endif -static struct dquot_operations ext3_qops; +#ifdef CONFIG_QUOTA + +#define QTYPE2NAME(t) ((t)==USRQUOTA?"user":"group") +#define QTYPE2MOPT(on, t) ((t)==USRQUOTA?((on)##USRJQUOTA):((on)##GRPJQUOTA)) + +static int ext3_write_dquot(struct dquot *dquot, int init); +static int ext3_mark_dquot_dirty(struct dquot *dquot); +static int ext3_write_info(struct super_block *sb, int type); +static int ext3_quota_on(struct super_block *sb, int type, int format_id, char *path); +static int ext3_quota_on_mount(struct super_block *sb, int type); +static int ext3_quota_off_mount(struct super_block *sb, int type); + +static struct dquot_operations ext3_quota_operations = { + .initialize = dquot_initialize, + .drop = 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 = ext3_write_dquot, + .mark_dirty = ext3_mark_dquot_dirty, + .write_info = ext3_write_info +}; + +static struct quotactl_ops ext3_qctl_operations = { + .quota_on = ext3_quota_on, + .quota_off = vfs_quota_off, + .quota_sync = vfs_quota_sync, + .get_info = vfs_get_dqinfo, + .set_info = vfs_set_dqinfo, + .get_dqblk = vfs_get_dqblk, + .set_dqblk = vfs_set_dqblk +}; +#endif static struct super_operations ext3_sops = { .alloc_inode = ext3_alloc_inode, @@ -536,6 +573,8 @@ enum { Opt_user_xattr, Opt_nouser_xattr, Opt_acl, Opt_noacl, Opt_noload, Opt_commit, Opt_journal_update, Opt_journal_inum, Opt_abort, Opt_data_journal, Opt_data_ordered, Opt_data_writeback, + Opt_usrjquota, Opt_grpjquota, Opt_offusrjquota, Opt_offgrpjquota, + Opt_jqfmt_vfsold, Opt_jqfmt_vfsv0, Opt_ignore, Opt_err, }; @@ -571,6 +610,12 @@ static match_table_t tokens = { {Opt_data_journal, "data=journal"}, {Opt_data_ordered, "data=ordered"}, {Opt_data_writeback, "data=writeback"}, + {Opt_offusrjquota, "usrjquota="}, + {Opt_usrjquota, "usrjquota=%s"}, + {Opt_offgrpjquota, "grpjquota="}, + {Opt_grpjquota, "grpjquota=%s"}, + {Opt_jqfmt_vfsold, "jqfmt=vfsold"}, + {Opt_jqfmt_vfsv0, "jqfmt=vfsv0"}, {Opt_ignore, "grpquota"}, {Opt_ignore, "noquota"}, {Opt_ignore, "quota"}, @@ -598,13 +643,17 @@ static unsigned long get_sb_block(void * return sb_block; } -static int parse_options (char * options, struct ext3_sb_info *sbi, +static int parse_options (char * options, struct super_block *sb, unsigned long * inum, int is_remount) { + struct ext3_sb_info *sbi = EXT3_SB(sb); char * p; substring_t args[MAX_OPT_ARGS]; int data_opt = 0; int option; +#ifdef CONFIG_QUOTA + int qtype; +#endif if (!options) return 1; @@ -759,6 +808,64 @@ static int parse_options (char * options sbi->s_mount_opt |= data_opt; } break; +#ifdef CONFIG_QUOTA + case Opt_usrjquota: + qtype = USRQUOTA; + goto set_qf_name; + case Opt_grpjquota: + qtype = GRPQUOTA; +set_qf_name: + if (sb_any_quota_enabled(sb)) { + printk(KERN_ERR "EXT3-fs: Cannot change journalled quota options when quota turned on.\n"); + return 0; + } + if (sbi->s_qf_names[qtype]) { + printk(KERN_ERR "EXT3-fs: %s quota file already specified.\n", QTYPE2NAME(qtype)); + return 0; + } + sbi->s_qf_names[qtype] = match_strdup(&args[0]); + if (!sbi->s_qf_names[qtype]) { + printk(KERN_ERR "EXT3-fs: not enough memory for storing quotafile name.\n"); + return 0; + } + if (strchr(sbi->s_qf_names[qtype], '/')) { + printk(KERN_ERR "EXT3-fs: quotafile must be on filesystem root.\n"); + kfree(sbi->s_qf_names[qtype]); + sbi->s_qf_names[qtype] = NULL; + return 0; + } + break; + case Opt_offusrjquota: + qtype = USRQUOTA; + goto clear_qf_name; + case Opt_offgrpjquota: + qtype = GRPQUOTA; +clear_qf_name: + if (sb_any_quota_enabled(sb)) { + printk(KERN_ERR "EXT3-fs: Cannot change journalled quota options when quota turned on.\n"); + return 0; + } + if (sbi->s_qf_names[qtype]) { + kfree(sbi->s_qf_names[qtype]); + sbi->s_qf_names[qtype] = NULL; + } + break; + case Opt_jqfmt_vfsold: + sbi->s_jquota_fmt = QFMT_VFS_OLD; + break; + case Opt_jqfmt_vfsv0: + sbi->s_jquota_fmt = QFMT_VFS_V0; + break; +#else + case Opt_usrjquota: + case Opt_grpjquota: + case Opt_offusrjquota: + case Opt_offgrpjquota: + case Opt_jqfmt_vfsold: + case Opt_jqfmt_vfsv0: + printk (KERN_ERR "EXT3-fs: journalled quota options not supported.\n"); + break; +#endif case Opt_abort: set_opt(sbi->s_mount_opt, ABORT); break; @@ -771,6 +878,12 @@ static int parse_options (char * options return 0; } } +#ifdef CONFIG_QUOTA + if (!sbi->s_jquota_fmt && (sbi->s_qf_names[0] || sbi->s_qf_names[1])) { + printk(KERN_ERR "EXT3-fs: journalled quota format not specified.\n"); + return 0; + } +#endif return 1; } @@ -929,7 +1042,7 @@ static void ext3_orphan_cleanup (struct struct ext3_super_block * es) { unsigned int s_flags = sb->s_flags; - int nr_orphans = 0, nr_truncates = 0; + int nr_orphans = 0, nr_truncates = 0, i; if (!es->s_last_orphan) { jbd_debug(4, "no orphan inodes to clean up\n"); return; @@ -949,6 +1062,16 @@ static void ext3_orphan_cleanup (struct sb->s_id); sb->s_flags &= ~MS_RDONLY; } +#ifdef CONFIG_QUOTA + sb->s_flags |= MS_ACTIVE; /* Needed for iput() to work correctly and not trash data */ + /* Turn on quotas so that they are updated correctly */ + for (i=0; i < MAXQUOTAS; i++) + if (EXT3_SB(sb)->s_qf_names[i]) { + int ret = ext3_quota_on_mount(sb, i); + if (ret < 0) + printk(KERN_ERR "EXT3-fs: Cannot turn on journalled quota: error %d\n", ret); + } +#endif while (es->s_last_orphan) { struct inode *inode; @@ -960,6 +1083,7 @@ static void ext3_orphan_cleanup (struct } list_add(&EXT3_I(inode)->i_orphan, &EXT3_SB(sb)->s_orphan); + DQUOT_INIT(inode); if (inode->i_nlink) { printk(KERN_DEBUG "%s: truncating inode %ld to %Ld bytes\n", @@ -987,6 +1111,12 @@ static void ext3_orphan_cleanup (struct if (nr_truncates) printk(KERN_INFO "EXT3-fs: %s: %d truncate%s cleaned up\n", sb->s_id, PLURAL(nr_truncates)); +#ifdef CONFIG_QUOTA + /* Turn quotas off */ + for (i = 0; i < MAXQUOTAS; i++) + if (sb_dqopt(sb)->files[i]) + ext3_quota_off_mount(sb, i); +#endif sb->s_flags = s_flags; /* Restore MS_RDONLY status */ } @@ -1116,7 +1246,7 @@ static int ext3_fill_super (struct super sbi->s_resuid = le16_to_cpu(es->s_def_resuid); sbi->s_resgid = le16_to_cpu(es->s_def_resgid); - if (!parse_options ((char *) data, sbi, &journal_inum, 0)) + if (!parse_options ((char *) data, sb, &journal_inum, 0)) goto failed_mount; sb->s_flags |= MS_ONE_SECOND; @@ -1295,7 +1425,10 @@ static int ext3_fill_super (struct super */ sb->s_op = &ext3_sops; sb->s_export_op = &ext3_export_ops; - sb->dq_op = &ext3_qops; +#ifdef CONFIG_QUOTA + sb->s_qcop = &ext3_qctl_operations; + sb->dq_op = &ext3_quota_operations; +#endif INIT_LIST_HEAD(&sbi->s_orphan); /* unlinked but open files */ sb->s_root = 0; @@ -1404,6 +1537,11 @@ failed_mount2: brelse(sbi->s_group_desc[i]); kfree(sbi->s_group_desc); failed_mount: +#ifdef CONFIG_QUOTA + for (i = 0; i < MAXQUOTAS; i++) + if (sbi->s_qf_names[i]) + kfree(sbi->s_qf_names[i]); +#endif ext3_blkdev_remove(sbi); brelse(bh); out_fail: @@ -1830,7 +1968,7 @@ int ext3_remount (struct super_block * s /* * Allow the "check" option to be passed as a remount option. */ - if (!parse_options(data, sbi, &tmp, 1)) + if (!parse_options(data, sb, &tmp, 1)) return -EINVAL; if (sbi->s_mount_opt & EXT3_MOUNT_ABORT) @@ -1950,45 +2088,113 @@ int ext3_statfs (struct super_block * sb #ifdef CONFIG_QUOTA -/* Blocks: (2 data blocks) * (3 indirect + 1 descriptor + 1 bitmap) + superblock */ -#define EXT3_OLD_QFMT_BLOCKS 11 -/* Blocks: quota info + (4 pointer blocks + 1 entry block) * (3 indirect + 1 descriptor + 1 bitmap) + superblock */ -#define EXT3_V0_QFMT_BLOCKS 27 +static inline int writes_to_blocks(int writes) +{ + return writes*5+2; +} -static int (*old_write_dquot)(struct dquot *dquot); +static inline struct inode *dquot_to_inode(struct dquot *dquot) +{ + return sb_dqopt(dquot->dq_sb)->files[dquot->dq_type]->f_dentry->d_inode; +} -static int ext3_write_dquot(struct dquot *dquot) +static int ext3_write_dquot(struct dquot *dquot, int init) { - int nblocks; - int ret; - int err; + int ret, err; handle_t *handle; - struct quota_info *dqops = 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; + handle = ext3_journal_start(dquot_to_inode(dquot), init ? EXT3_QUOTA_INIT_BLOCKS : EXT3_QUOTA_TRANS_BLOCKS); + if (IS_ERR(handle)) { + ret = PTR_ERR(handle); + goto out; } - qinode = dqops->files[dquot->dq_type]->f_dentry->d_inode; - handle = ext3_journal_start(qinode, nblocks); + ret = dquot_commit(dquot, init); + err = ext3_journal_stop(handle); + if (!ret) + ret = err; +out: + return ret; +} + +static int ext3_mark_dquot_dirty(struct dquot * dquot) +{ + /* Are we journalling quotas? */ + if (EXT3_SB(dquot->dq_sb)->s_qf_names[0] || EXT3_SB(dquot->dq_sb)->s_qf_names[1]) + return ext3_write_dquot(dquot, 0); + else + return dquot_mark_dquot_dirty(dquot); +} + +static int ext3_write_info(struct super_block *sb, int type) +{ + int ret, err; + handle_t *handle; + + handle = ext3_journal_start(sb->s_root->d_inode, writes_to_blocks(1)); if (IS_ERR(handle)) { ret = PTR_ERR(handle); goto out; } - ret = old_write_dquot(dquot); + ret = dquot_commit_info(sb, type); err = ext3_journal_stop(handle); - if (ret == 0) + if (!ret) ret = err; out: return ret; } + +/* Turn on quotas during mount time - we need to find the quota file and such... */ +static int ext3_quota_on_mount(struct super_block *sb, int type) +{ + int err; + struct dentry *dentry; + struct qstr name = { .name = EXT3_SB(sb)->s_qf_names[type], + .hash = 0, + .len = strlen(EXT3_SB(sb)->s_qf_names[type])}; + + dentry = lookup_hash(&name, sb->s_root); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + err = vfs_quota_on_mount(type, EXT3_SB(sb)->s_jquota_fmt, dentry); + if (err) + dput(dentry); + /* We keep the dentry reference if everything went ok - we drop it on quota_off time */ + return err; +} + +/* Turn quotas off during mount time */ +static int ext3_quota_off_mount(struct super_block *sb, int type) +{ + int err; + struct dentry *dentry; + + dentry = sb_dqopt(sb)->files[type]->f_dentry; + err = vfs_quota_off_mount(sb, type); + d_invalidate(dentry); /* We invalidate dentry - it has at least wrong hash... */ + dput(dentry); + return err; +} + +static int ext3_quota_on(struct super_block *sb, int type, int format_id, char *path) +{ + int err; + struct nameidata nd; + + if (!EXT3_SB(sb)->s_qf_names[0] && !EXT3_SB(sb)->s_qf_names[1]) /* Not journalling quota? */ + return vfs_quota_on(sb, type, format_id, path); + err = path_lookup(path, LOOKUP_FOLLOW, &nd); + if (err) + return err; + if (nd.mnt->mnt_sb != sb) /* Quotafile not on the same filesystem? */ + return -EXDEV; + if (nd.dentry->d_parent->d_inode != sb->s_root->d_inode) /* Quotafile not of fs root? */ + printk(KERN_WARNING "EXT3-fs: Quota file not on filesystem root. Journalled quota will not work.\n"); + if (!ext3_should_journal_data(nd.dentry->d_inode)) + printk(KERN_WARNING "EXT3-fs: Quota file does not have data-journalling. Journalled quota will not work.\n"); + path_release(&nd); + return vfs_quota_on(sb, type, format_id, path); +} + #endif static struct super_block *ext3_get_sb(struct file_system_type *fs_type, @@ -2013,11 +2219,6 @@ static int __init init_ext3_fs(void) err = init_inodecache(); if (err) goto out1; -#ifdef CONFIG_QUOTA - init_dquot_operations(&ext3_qops); - old_write_dquot = ext3_qops.write_dquot; - ext3_qops.write_dquot = ext3_write_dquot; -#endif err = register_filesystem(&ext3_fs_type); if (err) goto out; diff -puN fs/quota_v1.c~ext3-journalled-quotas fs/quota_v1.c --- 25/fs/quota_v1.c~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/fs/quota_v1.c Thu Feb 12 17:33:42 2004 @@ -60,13 +60,13 @@ static int v1_read_dqblk(struct dquot *d v1_disk2mem_dqblk(&dquot->dq_dqb, &dqblk); if (dquot->dq_dqb.dqb_bhardlimit == 0 && dquot->dq_dqb.dqb_bsoftlimit == 0 && dquot->dq_dqb.dqb_ihardlimit == 0 && dquot->dq_dqb.dqb_isoftlimit == 0) - dquot->dq_flags |= DQ_FAKE; + set_bit(DQ_FAKE_B, &dquot->dq_flags); dqstats.reads++; return 0; } -static int v1_commit_dqblk(struct dquot *dquot) +static int v1_commit_dqblk(struct dquot *dquot, int init) { short type = dquot->dq_type; struct file *filp; @@ -80,12 +80,7 @@ static int v1_commit_dqblk(struct dquot fs = get_fs(); set_fs(KERNEL_DS); - /* - * Note: clear the DQ_MOD flag unconditionally, - * so we don't loop forever on failure. - */ v1_mem2disk_dqblk(&dqblk, &dquot->dq_dqb); - dquot->dq_flags &= ~DQ_MOD; if (dquot->dq_id == 0) { dqblk.dqb_btime = sb_dqopt(dquot->dq_sb)->info[type].dqi_bgrace; dqblk.dqb_itime = sb_dqopt(dquot->dq_sb)->info[type].dqi_igrace; diff -puN fs/quota_v2.c~ext3-journalled-quotas fs/quota_v2.c --- 25/fs/quota_v2.c~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/fs/quota_v2.c Thu Feb 12 17:33:42 2004 @@ -65,7 +65,7 @@ static int v2_read_file_info(struct supe set_fs(fs); if (size != sizeof(struct v2_disk_dqinfo)) { printk(KERN_WARNING "Can't read info structure on device %s.\n", - f->f_vfsmnt->mnt_sb->s_id); + f->f_dentry->d_sb->s_id); return -1; } info->dqi_bgrace = le32_to_cpu(dinfo.dqi_bgrace); @@ -87,10 +87,12 @@ static int v2_write_file_info(struct sup ssize_t size; loff_t offset = V2_DQINFOOFF; + 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); dinfo.dqi_flags = cpu_to_le32(info->dqi_flags & DQF_MASK); + spin_unlock(&dq_data_lock); dinfo.dqi_blocks = cpu_to_le32(info->u.v2_i.dqi_blocks); dinfo.dqi_free_blk = cpu_to_le32(info->u.v2_i.dqi_free_blk); dinfo.dqi_free_entry = cpu_to_le32(info->u.v2_i.dqi_free_entry); @@ -100,7 +102,7 @@ static int v2_write_file_info(struct sup set_fs(fs); if (size != sizeof(struct v2_disk_dqinfo)) { printk(KERN_WARNING "Can't write info structure on device %s.\n", - f->f_vfsmnt->mnt_sb->s_id); + f->f_dentry->d_sb->s_id); return -1; } return 0; @@ -173,9 +175,10 @@ static ssize_t write_blk(struct file *fi } /* Remove empty block from list and return it */ -static int get_free_dqblk(struct file *filp, struct mem_dqinfo *info) +static int get_free_dqblk(struct file *filp, int type) { dqbuf_t buf = getdqbuf(); + struct mem_dqinfo *info = sb_dqinfo(filp->f_dentry->d_sb, type); struct v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf; int ret, blk; @@ -193,7 +196,7 @@ static int get_free_dqblk(struct file *f goto out_buf; blk = info->u.v2_i.dqi_blocks++; } - mark_info_dirty(info); + mark_info_dirty(filp->f_dentry->d_sb, type); ret = blk; out_buf: freedqbuf(buf); @@ -201,8 +204,9 @@ out_buf: } /* Insert empty block to the list */ -static int put_free_dqblk(struct file *filp, struct mem_dqinfo *info, dqbuf_t buf, uint blk) +static int put_free_dqblk(struct file *filp, int type, dqbuf_t buf, uint blk) { + struct mem_dqinfo *info = sb_dqinfo(filp->f_dentry->d_sb, type); struct v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf; int err; @@ -210,16 +214,17 @@ static int put_free_dqblk(struct file *f dh->dqdh_prev_free = cpu_to_le32(0); dh->dqdh_entries = cpu_to_le16(0); info->u.v2_i.dqi_free_blk = blk; - mark_info_dirty(info); + mark_info_dirty(filp->f_dentry->d_sb, type); if ((err = write_blk(filp, blk, buf)) < 0) /* Some strange block. We had better leave it... */ return err; return 0; } /* Remove given block from the list of blocks with free entries */ -static int remove_free_dqentry(struct file *filp, struct mem_dqinfo *info, dqbuf_t buf, uint blk) +static int remove_free_dqentry(struct file *filp, int type, dqbuf_t buf, uint blk) { dqbuf_t tmpbuf = getdqbuf(); + struct mem_dqinfo *info = sb_dqinfo(filp->f_dentry->d_sb, type); struct v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf; uint nextblk = le32_to_cpu(dh->dqdh_next_free), prevblk = le32_to_cpu(dh->dqdh_prev_free); int err; @@ -242,7 +247,7 @@ static int remove_free_dqentry(struct fi } else { info->u.v2_i.dqi_free_entry = nextblk; - mark_info_dirty(info); + mark_info_dirty(filp->f_dentry->d_sb, type); } freedqbuf(tmpbuf); dh->dqdh_next_free = dh->dqdh_prev_free = cpu_to_le32(0); @@ -255,9 +260,10 @@ out_buf: } /* Insert given block to the beginning of list with free entries */ -static int insert_free_dqentry(struct file *filp, struct mem_dqinfo *info, dqbuf_t buf, uint blk) +static int insert_free_dqentry(struct file *filp, int type, dqbuf_t buf, uint blk) { dqbuf_t tmpbuf = getdqbuf(); + struct mem_dqinfo *info = sb_dqinfo(filp->f_dentry->d_sb, type); struct v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf; int err; @@ -276,7 +282,7 @@ static int insert_free_dqentry(struct fi } freedqbuf(tmpbuf); info->u.v2_i.dqi_free_entry = blk; - mark_info_dirty(info); + mark_info_dirty(filp->f_dentry->d_sb, type); return 0; out_buf: freedqbuf(tmpbuf); @@ -307,7 +313,7 @@ static uint find_free_dqentry(struct dqu goto out_buf; } else { - blk = get_free_dqblk(filp, info); + blk = get_free_dqblk(filp, dquot->dq_type); if ((int)blk < 0) { *err = blk; freedqbuf(buf); @@ -315,10 +321,10 @@ static uint find_free_dqentry(struct dqu } memset(buf, 0, V2_DQBLKSIZE); info->u.v2_i.dqi_free_entry = blk; /* This is enough as block is already zeroed and entry list is empty... */ - mark_info_dirty(info); + mark_info_dirty(dquot->dq_sb, dquot->dq_type); } if (le16_to_cpu(dh->dqdh_entries)+1 >= V2_DQSTRINBLK) /* Block will be full? */ - if ((*err = remove_free_dqentry(filp, info, buf, blk)) < 0) { + if ((*err = remove_free_dqentry(filp, dquot->dq_type, buf, blk)) < 0) { printk(KERN_ERR "VFS: find_free_dqentry(): Can't remove block (%u) from entry free list.\n", blk); goto out_buf; } @@ -349,7 +355,6 @@ out_buf: static int do_insert_tree(struct dquot *dquot, uint *treeblk, int depth) { struct file *filp = sb_dqopt(dquot->dq_sb)->files[dquot->dq_type]; - struct mem_dqinfo *info = sb_dqopt(dquot->dq_sb)->info + dquot->dq_type; dqbuf_t buf; int ret = 0, newson = 0, newact = 0; u32 *ref; @@ -358,7 +363,7 @@ static int do_insert_tree(struct dquot * if (!(buf = getdqbuf())) return -ENOMEM; if (!*treeblk) { - ret = get_free_dqblk(filp, info); + ret = get_free_dqblk(filp, dquot->dq_type); if (ret < 0) goto out_buf; *treeblk = ret; @@ -392,7 +397,7 @@ static int do_insert_tree(struct dquot * ret = write_blk(filp, *treeblk, buf); } else if (newact && ret < 0) - put_free_dqblk(filp, info, buf, *treeblk); + put_free_dqblk(filp, dquot->dq_type, buf, *treeblk); out_buf: freedqbuf(buf); return ret; @@ -417,6 +422,7 @@ static int v2_write_dquot(struct dquot * ssize_t ret; struct v2_disk_dqblk ddquot; + /* dq_off is guarded by dqio_sem */ if (!dquot->dq_off) if ((ret = dq_insert_tree(dquot)) < 0) { printk(KERN_ERR "VFS: Error %Zd occurred while creating quota.\n", ret); @@ -424,7 +430,9 @@ static int v2_write_dquot(struct dquot * } filp = sb_dqopt(dquot->dq_sb)->files[type]; offset = dquot->dq_off; + spin_lock(&dq_data_lock); mem2diskdqb(&ddquot, &dquot->dq_dqb, dquot->dq_id); + spin_unlock(&dq_data_lock); fs = get_fs(); set_fs(KERNEL_DS); ret = filp->f_op->write(filp, (char *)&ddquot, sizeof(struct v2_disk_dqblk), &offset); @@ -445,7 +453,6 @@ static int v2_write_dquot(struct dquot * static int free_dqentry(struct dquot *dquot, uint blk) { struct file *filp = sb_dqopt(dquot->dq_sb)->files[dquot->dq_type]; - struct mem_dqinfo *info = sb_dqopt(dquot->dq_sb)->info + dquot->dq_type; struct v2_disk_dqdbheader *dh; dqbuf_t buf = getdqbuf(); int ret = 0; @@ -463,8 +470,8 @@ static int free_dqentry(struct dquot *dq dh = (struct v2_disk_dqdbheader *)buf; dh->dqdh_entries = cpu_to_le16(le16_to_cpu(dh->dqdh_entries)-1); if (!le16_to_cpu(dh->dqdh_entries)) { /* Block got free? */ - if ((ret = remove_free_dqentry(filp, info, buf, blk)) < 0 || - (ret = put_free_dqblk(filp, info, buf, blk)) < 0) { + if ((ret = remove_free_dqentry(filp, dquot->dq_type, buf, blk)) < 0 || + (ret = put_free_dqblk(filp, dquot->dq_type, buf, blk)) < 0) { printk(KERN_ERR "VFS: Can't move quota data block (%u) to free list.\n", blk); goto out_buf; } @@ -473,7 +480,7 @@ static int free_dqentry(struct dquot *dq memset(buf+(dquot->dq_off & ((1 << V2_DQBLKSIZE_BITS)-1)), 0, sizeof(struct v2_disk_dqblk)); if (le16_to_cpu(dh->dqdh_entries) == V2_DQSTRINBLK-1) { /* Insert will write block itself */ - if ((ret = insert_free_dqentry(filp, info, buf, blk)) < 0) { + if ((ret = insert_free_dqentry(filp, dquot->dq_type, buf, blk)) < 0) { printk(KERN_ERR "VFS: Can't insert quota data block (%u) to free entry list.\n", blk); goto out_buf; } @@ -494,7 +501,6 @@ out_buf: static int remove_tree(struct dquot *dquot, uint *blk, int depth) { struct file *filp = sb_dqopt(dquot->dq_sb)->files[dquot->dq_type]; - struct mem_dqinfo *info = sb_dqopt(dquot->dq_sb)->info + dquot->dq_type; dqbuf_t buf = getdqbuf(); int ret = 0; uint newblk; @@ -518,7 +524,7 @@ static int remove_tree(struct dquot *dqu ref[GETIDINDEX(dquot->dq_id, depth)] = cpu_to_le32(0); for (i = 0; i < V2_DQBLKSIZE && !buf[i]; i++); /* Block got empty? */ if (i == V2_DQBLKSIZE) { - put_free_dqblk(filp, info, buf, *blk); + put_free_dqblk(filp, dquot->dq_type, buf, *blk); *blk = 0; } else @@ -632,7 +638,7 @@ static int v2_read_dquot(struct dquot *d if (offset < 0) printk(KERN_ERR "VFS: Can't read quota structure for id %u.\n", dquot->dq_id); dquot->dq_off = 0; - dquot->dq_flags |= DQ_FAKE; + set_bit(DQ_FAKE_B, &dquot->dq_flags); memset(&dquot->dq_dqb, 0, sizeof(struct mem_dqblk)); ret = offset; } @@ -650,6 +656,9 @@ static int v2_read_dquot(struct dquot *d ret = 0; set_fs(fs); disk2memdqb(&dquot->dq_dqb, &ddquot); + if (!dquot->dq_dqb.dqb_bhardlimit && !dquot->dq_dqb.dqb_bsoftlimit && !dquot->dq_dqb.dqb_ihardlimit && + !dquot->dq_dqb.dqb_isoftlimit) + set_bit(DQ_FAKE_B, &dquot->dq_flags); } dqstats.reads++; @@ -657,11 +666,14 @@ static int v2_read_dquot(struct dquot *d } /* Commit changes of dquot to disk - it might also mean deleting it when quota became fake one and user has no blocks... */ -static int v2_commit_dquot(struct dquot *dquot) +static int v2_commit_dquot(struct dquot *dquot, int init) { - /* We clear the flag everytime so we don't loop when there was an IO error... */ - dquot->dq_flags &= ~DQ_MOD; - if (dquot->dq_flags & DQ_FAKE && !(dquot->dq_dqb.dqb_curinodes | dquot->dq_dqb.dqb_curspace)) + int test; + + spin_lock(&dq_data_lock); + test = !init && test_bit(DQ_FAKE_B, &dquot->dq_flags) && !(dquot->dq_dqb.dqb_curinodes | dquot->dq_dqb.dqb_curspace); + spin_unlock(&dq_data_lock); + if (test) return v2_delete_dquot(dquot); else return v2_write_dquot(dquot); diff -puN fs/stat.c~ext3-journalled-quotas fs/stat.c --- 25/fs/stat.c~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/fs/stat.c Thu Feb 12 17:33:42 2004 @@ -397,6 +397,7 @@ EXPORT_SYMBOL(inode_get_bytes); void inode_set_bytes(struct inode *inode, loff_t bytes) { + /* Caller is here resposible for sufficient locking (ie. inode->i_lock) */ inode->i_blocks = bytes >> 9; inode->i_bytes = bytes & 511; } diff -puN include/linux/ext3_fs_sb.h~ext3-journalled-quotas include/linux/ext3_fs_sb.h --- 25/include/linux/ext3_fs_sb.h~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/include/linux/ext3_fs_sb.h Thu Feb 12 17:33:42 2004 @@ -69,6 +69,10 @@ struct ext3_sb_info { struct timer_list turn_ro_timer; /* For turning read-only (crash simulation) */ wait_queue_head_t ro_wait_queue; /* For people waiting for the fs to go read-only */ #endif +#ifdef CONFIG_QUOTA + char *s_qf_names[MAXQUOTAS]; /* Names of quota files with journalled quota */ + int s_jquota_fmt; /* Format of quota to use */ +#endif }; #endif /* _LINUX_EXT3_FS_SB */ diff -puN include/linux/ext3_jbd.h~ext3-journalled-quotas include/linux/ext3_jbd.h --- 25/include/linux/ext3_jbd.h~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/include/linux/ext3_jbd.h Thu Feb 12 17:33:42 2004 @@ -42,8 +42,9 @@ * superblock only gets updated once, of course, so don't bother * counting that again for the quota updates. */ -#define EXT3_DATA_TRANS_BLOCKS (3 * EXT3_SINGLEDATA_TRANS_BLOCKS + \ - EXT3_XATTR_TRANS_BLOCKS - 2) +#define EXT3_DATA_TRANS_BLOCKS (EXT3_SINGLEDATA_TRANS_BLOCKS + \ + EXT3_XATTR_TRANS_BLOCKS - 2 + \ + 2*EXT3_QUOTA_TRANS_BLOCKS) extern int ext3_writepage_trans_blocks(struct inode *inode); @@ -72,6 +73,15 @@ extern int ext3_writepage_trans_blocks(s #define EXT3_INDEX_EXTRA_TRANS_BLOCKS 8 +#ifdef CONFIG_QUOTA +/* Amount of blocks needed for quota update - we know that the structure was + * allocated so we need to update only inode+data */ +#define EXT3_QUOTA_TRANS_BLOCKS 2 +#define EXT3_QUOTA_INIT_BLOCKS (DQUOT_MAX_WRITES*(EXT3_SINGLEDATA_TRANS_BLOCKS-2)+2) +#else +#define EXT3_QUOTA_TRANS_BLOCKS 0 +#endif + int ext3_mark_iloc_dirty(handle_t *handle, struct inode *inode, diff -puN include/linux/quota.h~ext3-journalled-quotas include/linux/quota.h --- 25/include/linux/quota.h~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/include/linux/quota.h Thu Feb 12 17:33:42 2004 @@ -138,6 +138,9 @@ struct if_dqinfo { #include #include +/* Maximal numbers of writes for quota operations (over all formats) */ +#define DQUOT_MAX_WRITES 6 /* Number of writes needed when quota structure is written */ + /* * Data for one user/group kept in memory */ @@ -174,16 +177,13 @@ struct mem_dqinfo { #define DQF_INFO_DIRTY (1 << DQF_INFO_DIRTY_B) /* Is info dirty? */ #define DQF_ANY_DQUOT_DIRTY (1 << DQF_ANY_DQUOT_DIRTY_B) /* Is any dquot dirty? */ -extern inline void mark_info_dirty(struct mem_dqinfo *info) -{ - set_bit(DQF_INFO_DIRTY_B, &info->dqi_flags); -} - +extern void mark_info_dirty(struct super_block *sb, int type); #define info_dirty(info) test_bit(DQF_INFO_DIRTY_B, &(info)->dqi_flags) #define info_any_dquot_dirty(info) test_bit(DQF_ANY_DQUOT_DIRTY_B, &(info)->dqi_flags) #define info_any_dirty(info) (info_dirty(info) || info_any_dquot_dirty(info)) #define sb_dqopt(sb) (&(sb)->s_dquot) +#define sb_dqinfo(sb, type) (sb_dqopt(sb)->info+(type)) struct dqstats { int lookups; @@ -238,19 +238,21 @@ struct quota_format_ops { int (*write_file_info)(struct super_block *sb, int type); /* Write main info about file */ int (*free_file_info)(struct super_block *sb, int type); /* Called on quotaoff() */ int (*read_dqblk)(struct dquot *dquot); /* Read structure for one user */ - int (*commit_dqblk)(struct dquot *dquot); /* Write (or delete) structure for one user */ + int (*commit_dqblk)(struct dquot *dquot, int init); /* Write (or delete) structure for one user */ }; /* Operations working with dquots */ struct dquot_operations { - void (*initialize) (struct inode *, int); - void (*drop) (struct inode *); + int (*initialize) (struct inode *, int); + int (*drop) (struct inode *); int (*alloc_space) (struct inode *, qsize_t, int); int (*alloc_inode) (const struct inode *, unsigned long); - void (*free_space) (struct inode *, qsize_t); - void (*free_inode) (const struct inode *, unsigned long); + int (*free_space) (struct inode *, qsize_t); + int (*free_inode) (const struct inode *, unsigned long); int (*transfer) (struct inode *, struct iattr *); - int (*write_dquot) (struct dquot *); + int (*write_dquot) (struct dquot *, int); + int (*mark_dirty) (struct dquot *); + int (*write_info) (struct super_block *, int); }; /* Operations handling requests from userspace */ @@ -289,10 +291,7 @@ struct quota_info { }; /* Inline would be better but we need to dereference super_block which is not defined yet */ -#define mark_dquot_dirty(dquot) do {\ - set_bit(DQF_ANY_DQUOT_DIRTY_B, &(sb_dqopt((dquot)->dq_sb)->info[(dquot)->dq_type].dqi_flags));\ - set_bit(DQ_MOD_B, &(dquot)->dq_flags);\ -} while (0) +int mark_dquot_dirty(struct dquot *dquot); #define dquot_dirty(dquot) test_bit(DQ_MOD_B, &(dquot)->dq_flags) @@ -304,7 +303,6 @@ struct quota_info { int register_quota_format(struct quota_format_type *fmt); void unregister_quota_format(struct quota_format_type *fmt); -void init_dquot_operations(struct dquot_operations *fsdqops); struct quota_module_name { int qm_fmt_id; diff -puN include/linux/quotaops.h~ext3-journalled-quotas include/linux/quotaops.h --- 25/include/linux/quotaops.h~ext3-journalled-quotas Thu Feb 12 17:33:42 2004 +++ 25-akpm/include/linux/quotaops.h Thu Feb 12 17:33:42 2004 @@ -22,16 +22,29 @@ */ extern void sync_dquots(struct super_block *sb, int type); -extern void dquot_initialize(struct inode *inode, int type); -extern void dquot_drop(struct inode *inode); +extern int dquot_initialize(struct inode *inode, int type); +extern int dquot_drop(struct inode *inode); -extern int dquot_alloc_space(struct inode *inode, qsize_t number, int prealloc); -extern int dquot_alloc_inode(const struct inode *inode, unsigned long number); +extern int dquot_alloc_space(struct inode *inode, qsize_t number, int prealloc); +extern int dquot_alloc_inode(const struct inode *inode, unsigned long number); -extern void dquot_free_space(struct inode *inode, qsize_t number); -extern void dquot_free_inode(const struct inode *inode, unsigned long number); +extern int dquot_free_space(struct inode *inode, qsize_t number); +extern int dquot_free_inode(const struct inode *inode, unsigned long number); -extern int dquot_transfer(struct inode *inode, struct iattr *iattr); +extern int dquot_transfer(struct inode *inode, struct iattr *iattr); +extern int dquot_commit(struct dquot *dquot, int init); +extern int dquot_commit_info(struct super_block *sb, int type); +extern int dquot_mark_dquot_dirty(struct dquot *dquot); + +extern int vfs_quota_on(struct super_block *sb, int type, int format_id, char *path); +extern int vfs_quota_on_mount(int type, int format_id, struct dentry *dentry); +extern int vfs_quota_off(struct super_block *sb, int type); +extern int vfs_quota_off_mount(struct super_block *sb, int type); +extern int vfs_quota_sync(struct super_block *sb, int type); +extern int vfs_get_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii); +extern int vfs_set_dqinfo(struct super_block *sb, int type, struct if_dqinfo *ii); +extern int vfs_get_dqblk(struct super_block *sb, int type, qid_t id, struct if_dqblk *di); +extern int vfs_set_dqblk(struct super_block *sb, int type, qid_t id, struct if_dqblk *di); /* * Operations supported for diskquotas. @@ -64,11 +77,9 @@ 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->i_lock will guard the update */ inode_add_bytes(inode, nr); - spin_unlock(&dq_data_lock); - } return 0; } @@ -87,11 +98,9 @@ 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->i_lock will guard the update */ inode_add_bytes(inode, nr); - spin_unlock(&dq_data_lock); - } return 0; } @@ -117,11 +126,9 @@ 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->i_lock will guard the update */ inode_sub_bytes(inode, nr); - spin_unlock(&dq_data_lock); - } } static __inline__ void DQUOT_FREE_SPACE(struct inode *inode, qsize_t nr) _