Commit 2df2c24a authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] ext3: journalled quotas

From: Jan Kara <jack@ucw.cz>

Journalled quota support for ext3: The patch consists of two parts - ext3
changes and changes in generic quota code.  The main idea of the changes is
that a transaction is always started before any operation which changes quota
file and dirtifying of the quota causes its write to disk.  These two changes
assure that quota change is journalled into the same transaction as the file
change and hence after journal replay quota is consistent with the filesystem
state.  As during journal replay inodes from orphan list are deleted/truncated
we have to do quota_on before the replay of the orphan list - this problem is
solved by additional mount options to ext3 with quota file names and format.

Some changes in generic code were also needed to assure that quota structure
in file is always allocated and so ordinary quota operations (like
adding/deleting a block/inode) need only a few blocks from the transaction.
parent 6ea4e454
......@@ -406,12 +406,15 @@ config QUOTA
help
If you say Y here, you will be able to set per user limits for disk
usage (also called disk quotas). Currently, it works for the
ext2, ext3, and reiserfs file system. You need additional software
in order to use quota support (you can download sources from
ext2, ext3, and reiserfs file system. ext3 also supports journalled
quotas for which you don't need to run quotacheck(8) after an unclean
shutdown. You need additional software in order to use quota support
(you can download sources from
<http://www.sf.net/projects/linuxquota/>). For further details, read
the Quota mini-HOWTO, available from
<http://www.tldp.org/docs.html#howto>. Probably the quota
support is only useful for multi user systems. If unsure, say N.
<http://www.tldp.org/docs.html#howto>, or the documentation provided
with the quota tools. Probably the quota support is only useful for
multi user systems. If unsure, say N.
config QFMT_V1
tristate "Old quota format support"
......
This diff is collapsed.
......@@ -2772,10 +2772,29 @@ int ext3_setattr(struct dentry *dentry, struct iattr *attr)
if ((ia_valid & ATTR_UID && attr->ia_uid != inode->i_uid) ||
(ia_valid & ATTR_GID && attr->ia_gid != inode->i_gid)) {
handle_t *handle;
/* (user+group)*(old+new) structure, inode write (sb,
* inode block, ? - but truncate inode update has it) */
handle = ext3_journal_start(inode, 4*EXT3_QUOTA_INIT_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) &&
attr->ia_valid & ATTR_SIZE && attr->ia_size < inode->i_size) {
......@@ -2853,7 +2872,9 @@ int ext3_writepage_trans_blocks(struct inode *inode)
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;
......
......@@ -1631,7 +1631,8 @@ static int ext3_create (struct inode * dir, struct dentry * dentry, int mode,
int err;
handle = ext3_journal_start(dir, EXT3_DATA_TRANS_BLOCKS +
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 3);
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 3 +
2*EXT3_QUOTA_INIT_BLOCKS);
if (IS_ERR(handle))
return PTR_ERR(handle);
......@@ -1661,7 +1662,8 @@ static int ext3_mknod (struct inode * dir, struct dentry *dentry,
return -EINVAL;
handle = ext3_journal_start(dir, EXT3_DATA_TRANS_BLOCKS +
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 3);
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 3 +
2*EXT3_QUOTA_INIT_BLOCKS);
if (IS_ERR(handle))
return PTR_ERR(handle);
......@@ -1693,7 +1695,8 @@ static int ext3_mkdir(struct inode * dir, struct dentry * dentry, int mode)
return -EMLINK;
handle = ext3_journal_start(dir, EXT3_DATA_TRANS_BLOCKS +
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 3);
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 3 +
2*EXT3_QUOTA_INIT_BLOCKS);
if (IS_ERR(handle))
return PTR_ERR(handle);
......@@ -1972,6 +1975,9 @@ static int ext3_rmdir (struct inode * dir, struct dentry *dentry)
struct ext3_dir_entry_2 * de;
handle_t *handle;
/* Initialize quotas before so that eventual writes go 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);
......@@ -1985,7 +1991,6 @@ static int ext3_rmdir (struct inode * dir, struct dentry *dentry)
handle->h_sync = 1;
inode = dentry->d_inode;
DQUOT_INIT(inode);
retval = -EIO;
if (le32_to_cpu(de->inode) != inode->i_ino)
......@@ -2029,6 +2034,9 @@ static int ext3_unlink(struct inode * dir, struct dentry *dentry)
struct ext3_dir_entry_2 * de;
handle_t *handle;
/* Initialize quotas before so that eventual writes go
* 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);
......@@ -2042,7 +2050,6 @@ static int ext3_unlink(struct inode * dir, struct dentry *dentry)
goto end_unlink;
inode = dentry->d_inode;
DQUOT_INIT(inode);
retval = -EIO;
if (le32_to_cpu(de->inode) != inode->i_ino)
......@@ -2085,7 +2092,8 @@ static int ext3_symlink (struct inode * dir,
return -ENAMETOOLONG;
handle = ext3_journal_start(dir, EXT3_DATA_TRANS_BLOCKS +
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 5);
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 5 +
2*EXT3_QUOTA_INIT_BLOCKS);
if (IS_ERR(handle))
return PTR_ERR(handle);
......@@ -2170,6 +2178,10 @@ static int ext3_rename (struct inode * old_dir, struct dentry *old_dentry,
old_bh = new_bh = dir_bh = NULL;
/* Initialize quotas before so that eventual writes go
* 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))
......@@ -2196,8 +2208,6 @@ static int ext3_rename (struct inode * old_dir, struct dentry *old_dentry,
if (!new_inode) {
brelse (new_bh);
new_bh = NULL;
} else {
DQUOT_INIT(new_inode);
}
}
if (S_ISDIR(old_inode->i_mode)) {
......
This diff is collapsed.
......@@ -60,7 +60,7 @@ static int v1_read_dqblk(struct dquot *dquot)
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;
......@@ -80,12 +80,7 @@ static int v1_commit_dqblk(struct dquot *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;
......
This diff is collapsed.
......@@ -398,6 +398,8 @@ EXPORT_SYMBOL(inode_get_bytes);
void inode_set_bytes(struct inode *inode, loff_t bytes)
{
/* Caller is here responsible for sufficient locking
* (ie. inode->i_lock) */
inode->i_blocks = bytes >> 9;
inode->i_bytes = bytes & 511;
}
......
......@@ -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 */
......@@ -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,19 @@ extern int ext3_writepage_trans_blocks(struct inode *inode);
#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
/* Amount of blocks needed for quota insert/delete - we do some block writes
* but inode, sb and group updates are done only once */
#define EXT3_QUOTA_INIT_BLOCKS (DQUOT_MAX_WRITES*\
(EXT3_SINGLEDATA_TRANS_BLOCKS-3)+3)
#else
#define EXT3_QUOTA_TRANS_BLOCKS 0
#define EXT3_QUOTA_INIT_BLOCKS 0
#endif
int
ext3_mark_iloc_dirty(handle_t *handle,
struct inode *inode,
......
......@@ -138,6 +138,10 @@ struct if_dqinfo {
#include <linux/dqblk_v1.h>
#include <linux/dqblk_v2.h>
/* Maximal numbers of writes for quota operation (insert/delete/update)
* (over all formats) - info block, 4 pointer blocks, data block */
#define DQUOT_MAX_WRITES 6
/*
* Data for one user/group kept in memory
*/
......@@ -168,22 +172,21 @@ struct mem_dqinfo {
} u;
};
struct super_block;
#define DQF_MASK 0xffff /* Mask for format specific flags */
#define DQF_INFO_DIRTY_B 16
#define DQF_ANY_DQUOT_DIRTY_B 17
#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;
......@@ -200,15 +203,13 @@ extern struct dqstats dqstats;
#define NR_DQHASH 43 /* Just an arbitrary number */
#define DQ_MOD_B 0
#define DQ_BLKS_B 1
#define DQ_INODES_B 2
#define DQ_FAKE_B 3
#define DQ_MOD (1 << DQ_MOD_B) /* dquot modified since read */
#define DQ_BLKS (1 << DQ_BLKS_B) /* uid/gid has been warned about blk limit */
#define DQ_INODES (1 << DQ_INODES_B) /* uid/gid has been warned about inode limit */
#define DQ_FAKE (1 << DQ_FAKE_B) /* no limits only usage */
#define DQ_MOD_B 0 /* dquot modified since read */
#define DQ_BLKS_B 1 /* uid/gid has been warned about blk limit */
#define DQ_INODES_B 2 /* uid/gid has been warned about inode limit */
#define DQ_FAKE_B 3 /* no limits only usage */
#define DQ_READ_B 4 /* dquot was read into memory */
#define DQ_ACTIVE_B 5 /* dquot is active (dquot_release not called) */
#define DQ_WAITFREE_B 6 /* dquot being waited (by invalidate_dquots) */
struct dquot {
struct list_head dq_hash; /* Hash list in memory */
......@@ -216,8 +217,7 @@ struct dquot {
struct list_head dq_free; /* Free list element */
struct semaphore dq_lock; /* dquot IO lock */
atomic_t dq_count; /* Use count */
/* fields after this point are cleared when invalidating */
wait_queue_head_t dq_wait_unused; /* Wait queue for dquot to become unused */
struct super_block *dq_sb; /* superblock this applies to */
unsigned int dq_id; /* ID this applies to (uid, gid) */
loff_t dq_off; /* Offset of dquot on disk */
......@@ -238,19 +238,22 @@ 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); /* Write structure for one user */
int (*release_dqblk)(struct dquot *dquot); /* Called when last reference to dquot is being dropped */
};
/* 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 *); /* Ordinary dquot write */
int (*mark_dirty) (struct dquot *); /* Dquot is marked dirty */
int (*write_info) (struct super_block *, int); /* Write of quota "superblock" */
};
/* Operations handling requests from userspace */
......@@ -289,10 +292,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 +304,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;
......
......@@ -22,16 +22,31 @@
*/
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 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_commit(struct dquot *dquot);
extern int dquot_acquire(struct dquot *dquot);
extern int dquot_release(struct dquot *dquot);
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);
#define vfs_quota_off_mount(sb, type) vfs_quota_off(sb, 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.
......@@ -42,6 +57,8 @@ extern struct quotactl_ops vfs_quotactl_ops;
#define sb_dquot_ops (&dquot_operations)
#define sb_quotactl_ops (&vfs_quotactl_ops)
/* It is better to call this function outside of any transaction as it might
* need a lot of space in journal for dquot structure allocation. */
static __inline__ void DQUOT_INIT(struct inode *inode)
{
BUG_ON(!inode->i_sb);
......@@ -49,6 +66,7 @@ static __inline__ void DQUOT_INIT(struct inode *inode)
inode->i_sb->dq_op->initialize(inode, -1);
}
/* The same as with DQUOT_INIT */
static __inline__ void DQUOT_DROP(struct inode *inode)
{
if (IS_QUOTAINIT(inode)) {
......@@ -57,6 +75,8 @@ static __inline__ void DQUOT_DROP(struct inode *inode)
}
}
/* The following allocation/freeing/transfer functions *must* be called inside
* a transaction (deadlocks possible otherwise) */
static __inline__ int DQUOT_PREALLOC_SPACE_NODIRTY(struct inode *inode, qsize_t nr)
{
if (sb_any_quota_enabled(inode->i_sb)) {
......@@ -137,6 +157,7 @@ static __inline__ int DQUOT_TRANSFER(struct inode *inode, struct iattr *iattr)
return 0;
}
/* The following two functions cannot be called inside a transaction */
#define DQUOT_SYNC(sb) sync_dquots(sb, -1)
static __inline__ int DQUOT_OFF(struct super_block *sb)
......
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