static void ext3_clear_journal_err(struct super_block * sb,
struct ext3_super_block * es);
static int ext3_sync_fs(struct super_block *sb, int wait);
+static const char *ext3_decode_error(struct super_block * sb, int errno,
+ char nbuf[16]);
+static int ext3_remount (struct super_block * sb, int * flags, char * data);
+static int ext3_statfs (struct super_block * sb, struct kstatfs * buf);
+static void ext3_unlockfs(struct super_block *sb);
+static void ext3_write_super (struct super_block * sb);
+static void ext3_write_super_lockfs(struct super_block *sb);
/*
* Wrappers for journal_start/end.
* that sync() will call the filesystem's write_super callback if
* appropriate.
*/
-handle_t *ext3_journal_start(struct inode *inode, int nblocks)
+handle_t *ext3_journal_start_sb(struct super_block *sb, int nblocks)
{
journal_t *journal;
- if (inode->i_sb->s_flags & MS_RDONLY)
+ if (sb->s_flags & MS_RDONLY)
return ERR_PTR(-EROFS);
/* Special case here: if the journal has aborted behind our
* backs (eg. EIO in the commit thread), then we still need to
* take the FS itself readonly cleanly. */
- journal = EXT3_JOURNAL(inode);
+ journal = EXT3_SB(sb)->s_journal;
if (is_journal_aborted(journal)) {
- ext3_abort(inode->i_sb, __FUNCTION__,
+ ext3_abort(sb, __FUNCTION__,
"Detected aborted journal");
return ERR_PTR(-EROFS);
}
char nbuf[16];
const char *errstr = ext3_decode_error(NULL, err, nbuf);
- printk(KERN_ERR "%s: aborting transaction: %s in %s",
- caller, errstr, err_fn);
-
if (bh)
BUFFER_TRACE(bh, "abort");
- journal_abort_handle(handle);
+
if (!handle->h_err)
handle->h_err = err;
+
+ if (is_handle_aborted(handle))
+ return;
+
+ printk(KERN_ERR "%s: aborting transaction: %s in %s\n",
+ caller, errstr, err_fn);
+
+ journal_abort_handle(handle);
}
/* Deal with the reporting of failure conditions on a filesystem such as
if (sb->s_flags & MS_RDONLY)
return;
- if (test_opt (sb, ERRORS_PANIC))
- panic ("EXT3-fs (device %s): panic forced after error\n",
- sb->s_id);
if (test_opt (sb, ERRORS_RO)) {
printk (KERN_CRIT "Remounting filesystem read-only\n");
sb->s_flags |= MS_RDONLY;
if (journal)
journal_abort(journal, -EIO);
}
+ if (test_opt(sb, ERRORS_PANIC))
+ panic("EXT3-fs (device %s): panic forced after error\n",
+ sb->s_id);
ext3_commit_super(sb, es, 1);
}
ext3_handle_error(sb);
}
-const char *ext3_decode_error(struct super_block * sb, int errno, char nbuf[16])
+static const char *ext3_decode_error(struct super_block * sb, int errno,
+ char nbuf[16])
{
char *errstr = NULL;
journal_abort(EXT3_SB(sb)->s_journal, -EIO);
}
-/* Deal with the reporting of failure conditions while running, such as
- * inconsistencies in operation or invalid system states.
- *
- * Use ext3_error() for cases of invalid filesystem states, as that will
- * record an error on disk and force a filesystem check on the next boot.
- */
-NORET_TYPE void ext3_panic (struct super_block * sb, const char * function,
- const char * fmt, ...)
-{
- va_list args;
-
- va_start(args, fmt);
- printk(KERN_CRIT "EXT3-fs error (device %s): %s: ",sb->s_id, function);
- vprintk(fmt, args);
- printk("\n");
- va_end(args);
-
- /* this is to prevent panic from syncing this filesystem */
- /* AKPM: is this sufficient? */
- sb->s_flags |= MS_RDONLY;
- panic ("EXT3-fs panic forced\n");
-}
-
void ext3_warning (struct super_block * sb, const char * function,
const char * fmt, ...)
{
}
}
-void ext3_put_super (struct super_block * sb)
+static void ext3_put_super (struct super_block * sb)
{
struct ext3_sb_info *sbi = EXT3_SB(sb);
struct ext3_super_block *es = sbi->s_es;
for (i = 0; i < sbi->s_gdb_count; i++)
brelse(sbi->s_group_desc[i]);
kfree(sbi->s_group_desc);
- kfree(sbi->s_debts);
+ percpu_counter_destroy(&sbi->s_freeblocks_counter);
+ percpu_counter_destroy(&sbi->s_freeinodes_counter);
+ percpu_counter_destroy(&sbi->s_dirs_counter);
brelse(sbi->s_sbh);
#ifdef CONFIG_QUOTA
for (i = 0; i < MAXQUOTAS; i++) {
ei->i_acl = EXT3_ACL_NOT_CACHED;
ei->i_default_acl = EXT3_ACL_NOT_CACHED;
#endif
+ ei->i_rsv_window.rsv_end = EXT3_RESERVE_WINDOW_NOT_ALLOCATED;
ei->vfs_inode.i_version = 1;
return &ei->vfs_inode;
}
printk(KERN_INFO "ext3_inode_cache: not all structures were freed\n");
}
-#ifdef CONFIG_EXT3_FS_POSIX_ACL
-
static void ext3_clear_inode(struct inode *inode)
{
+#ifdef CONFIG_EXT3_FS_POSIX_ACL
if (EXT3_I(inode)->i_acl &&
EXT3_I(inode)->i_acl != EXT3_ACL_NOT_CACHED) {
posix_acl_release(EXT3_I(inode)->i_acl);
posix_acl_release(EXT3_I(inode)->i_default_acl);
EXT3_I(inode)->i_default_acl = EXT3_ACL_NOT_CACHED;
}
-}
-
-#else
-# define ext3_clear_inode NULL
#endif
+ ext3_discard_reservation(inode);
+}
#ifdef CONFIG_QUOTA
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 ssize_t ext3_quota_read(struct super_block *sb, int type, char *data,
+ size_t len, loff_t off);
+static ssize_t ext3_quota_write(struct super_block *sb, int type,
+ const char *data, size_t len, loff_t off);
static struct dquot_operations ext3_quota_operations = {
.initialize = ext3_dquot_initialize,
.read_inode = ext3_read_inode,
.write_inode = ext3_write_inode,
.dirty_inode = ext3_dirty_inode,
- .put_inode = ext3_put_inode,
.delete_inode = ext3_delete_inode,
.put_super = ext3_put_super,
.write_super = ext3_write_super,
.statfs = ext3_statfs,
.remount_fs = ext3_remount,
.clear_inode = ext3_clear_inode,
+#ifdef CONFIG_QUOTA
+ .quota_read = ext3_quota_read,
+ .quota_write = ext3_quota_write,
+#endif
};
struct dentry *ext3_get_parent(struct dentry *child);
Opt_bsd_df, Opt_minix_df, Opt_grpid, Opt_nogrpid,
Opt_resgid, Opt_resuid, Opt_sb, Opt_err_cont, Opt_err_panic, Opt_err_ro,
Opt_nouid32, Opt_check, Opt_nocheck, Opt_debug, Opt_oldalloc, Opt_orlov,
- Opt_user_xattr, Opt_nouser_xattr, Opt_acl, Opt_noacl, Opt_noload,
+ Opt_user_xattr, Opt_nouser_xattr, Opt_acl, Opt_noacl,
+ Opt_reservation, Opt_noreservation, 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_tagxid, Opt_barrier, Opt_ignore, Opt_err,
+ Opt_tagxid, Opt_barrier, Opt_ignore, Opt_err, Opt_resize,
};
static match_table_t tokens = {
{Opt_nouser_xattr, "nouser_xattr"},
{Opt_acl, "acl"},
{Opt_noacl, "noacl"},
+ {Opt_reservation, "reservation"},
+ {Opt_noreservation, "noreservation"},
{Opt_noload, "noload"},
{Opt_commit, "commit=%u"},
{Opt_journal_update, "journal=update"},
{Opt_ignore, "quota"},
{Opt_ignore, "usrquota"},
{Opt_barrier, "barrier=%u"},
- {Opt_err, NULL}
+ {Opt_err, NULL},
+ {Opt_resize, "resize"},
};
static unsigned long get_sb_block(void **data)
}
static int parse_options (char * options, struct super_block *sb,
- unsigned long * inum, int is_remount)
+ unsigned long * inum, unsigned long *n_blocks_count, int is_remount)
{
struct ext3_sb_info *sbi = EXT3_SB(sb);
char * p;
int option;
#ifdef CONFIG_QUOTA
int qtype;
+ char *qname;
#endif
if (!options)
printk("EXT3 (no)acl options not supported\n");
break;
#endif
+ case Opt_reservation:
+ set_opt(sbi->s_mount_opt, RESERVATION);
+ break;
+ case Opt_noreservation:
+ clear_opt(sbi->s_mount_opt, RESERVATION);
+ break;
case Opt_journal_update:
/* @@@ FIXME */
/* Eventually we will want to be able to create
"quota options when quota turned on.\n");
return 0;
}
- if (sbi->s_qf_names[qtype]) {
+ qname = match_strdup(&args[0]);
+ if (!qname) {
printk(KERN_ERR
- "EXT3-fs: %s quota file already "
- "specified.\n", QTYPE2NAME(qtype));
+ "EXT3-fs: not enough memory for "
+ "storing quotafile name.\n");
return 0;
}
- sbi->s_qf_names[qtype] = match_strdup(&args[0]);
- if (!sbi->s_qf_names[qtype]) {
+ if (sbi->s_qf_names[qtype] &&
+ strcmp(sbi->s_qf_names[qtype], qname)) {
printk(KERN_ERR
- "EXT3-fs: not enough memory for "
- "storing quotafile name.\n");
+ "EXT3-fs: %s quota file already "
+ "specified.\n", QTYPE2NAME(qtype));
+ kfree(qname);
return 0;
}
+ sbi->s_qf_names[qtype] = qname;
if (strchr(sbi->s_qf_names[qtype], '/')) {
printk(KERN_ERR
"EXT3-fs: quotafile must be on "
break;
case Opt_ignore:
break;
+ case Opt_resize:
+ if (!n_blocks_count) {
+ printk("EXT3-fs: resize option only available "
+ "for remount\n");
+ return 0;
+ }
+ match_int(&args[0], &option);
+ *n_blocks_count = option;
+ break;
default:
printk (KERN_ERR
"EXT3-fs: Unrecognized mount option \"%s\" "
return res;
}
+/* Called at mount-time, super-block is locked */
static int ext3_check_descriptors (struct super_block * sb)
{
struct ext3_sb_info *sbi = EXT3_SB(sb);
/* Turn quotas off */
for (i = 0; i < MAXQUOTAS; i++) {
if (sb_dqopt(sb)->files[i])
- ext3_quota_off_mount(sb, i);
+ vfs_quota_off(sb, i);
}
#endif
sb->s_flags = s_flags; /* Restore MS_RDONLY status */
sbi->s_resuid = EXT3_DEF_RESUID;
sbi->s_resgid = EXT3_DEF_RESGID;
+ unlock_kernel();
+
blocksize = sb_min_blocksize(sb, EXT3_MIN_BLOCK_SIZE);
if (!blocksize) {
printk(KERN_ERR "EXT3-fs: unable to set blocksize\n");
es = (struct ext3_super_block *) (((char *)bh->b_data) + offset);
sbi->s_es = es;
sb->s_magic = le16_to_cpu(es->s_magic);
- if (sb->s_magic != EXT3_SUPER_MAGIC) {
- if (!silent)
- printk(KERN_ERR
- "VFS: Can't find ext3 filesystem on dev %s.\n",
- sb->s_id);
- goto failed_mount;
- }
+ if (sb->s_magic != EXT3_SUPER_MAGIC)
+ goto cantfind_ext3;
/* Set defaults before we parse the mount options */
def_mount_opts = le32_to_cpu(es->s_default_mount_opts);
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, sb, &journal_inum, 0))
+ set_opt(sbi->s_mount_opt, RESERVATION);
+
+ if (!parse_options ((char *) data, sb, &journal_inum, NULL, 0))
goto failed_mount;
if (EXT3_SB(sb)->s_mount_opt & EXT3_MOUNT_TAG_XID)
sb->s_flags |= MS_TAGXID;
- sb->s_flags |= MS_ONE_SECOND;
+
sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
((sbi->s_mount_opt & EXT3_MOUNT_POSIX_ACL) ? MS_POSIXACL : 0);
sbi->s_blocks_per_group = le32_to_cpu(es->s_blocks_per_group);
sbi->s_frags_per_group = le32_to_cpu(es->s_frags_per_group);
sbi->s_inodes_per_group = le32_to_cpu(es->s_inodes_per_group);
+ if (EXT3_INODE_SIZE(sb) == 0)
+ goto cantfind_ext3;
sbi->s_inodes_per_block = blocksize / EXT3_INODE_SIZE(sb);
- sbi->s_itb_per_group = sbi->s_inodes_per_group /sbi->s_inodes_per_block;
+ if (sbi->s_inodes_per_block == 0)
+ goto cantfind_ext3;
+ sbi->s_itb_per_group = sbi->s_inodes_per_group /
+ sbi->s_inodes_per_block;
sbi->s_desc_per_block = blocksize / sizeof(struct ext3_group_desc);
sbi->s_sbh = bh;
sbi->s_mount_state = le16_to_cpu(es->s_state);
goto failed_mount;
}
+ if (EXT3_BLOCKS_PER_GROUP(sb) == 0)
+ goto cantfind_ext3;
sbi->s_groups_count = (le32_to_cpu(es->s_blocks_count) -
le32_to_cpu(es->s_first_data_block) +
EXT3_BLOCKS_PER_GROUP(sb) - 1) /
printk (KERN_ERR "EXT3-fs: not enough memory\n");
goto failed_mount;
}
- sbi->s_debts = kmalloc(sbi->s_groups_count * sizeof(u8),
- GFP_KERNEL);
- if (!sbi->s_debts) {
- printk("EXT3-fs: not enough memory to allocate s_bgi\n");
- goto failed_mount2;
- }
- memset(sbi->s_debts, 0, sbi->s_groups_count * sizeof(u8));
percpu_counter_init(&sbi->s_freeblocks_counter);
percpu_counter_init(&sbi->s_freeinodes_counter);
sbi->s_gdb_count = db_count;
get_random_bytes(&sbi->s_next_generation, sizeof(u32));
spin_lock_init(&sbi->s_next_gen_lock);
+ /* per fileystem reservation list head & lock */
+ spin_lock_init(&sbi->s_rsv_window_lock);
+ sbi->s_rsv_window_root = RB_ROOT;
+ /* Add a single, static dummy reservation to the start of the
+ * reservation window list --- it gives us a placeholder for
+ * append-at-start-of-list which makes the allocation logic
+ * _much_ simpler. */
+ sbi->s_rsv_window_head.rsv_start = EXT3_RESERVE_WINDOW_NOT_ALLOCATED;
+ sbi->s_rsv_window_head.rsv_end = EXT3_RESERVE_WINDOW_NOT_ALLOCATED;
+ atomic_set(&sbi->s_rsv_window_head.rsv_alloc_hit, 0);
+ atomic_set(&sbi->s_rsv_window_head.rsv_goal_size, 0);
+ ext3_rsv_window_add(sb, &sbi->s_rsv_window_head);
+
/*
* set up enough so that it can read an inode
*/
sb->s_op = &ext3_sops;
sb->s_export_op = &ext3_export_ops;
+ sb->s_xattr = ext3_xattr_handlers;
#ifdef CONFIG_QUOTA
sb->s_qcop = &ext3_qctl_operations;
sb->dq_op = &ext3_quota_operations;
percpu_counter_mod(&sbi->s_dirs_counter,
ext3_count_dirs(sb));
+ lock_kernel();
return 0;
+cantfind_ext3:
+ if (!silent)
+ printk(KERN_ERR "VFS: Can't find ext3 filesystem on dev %s.\n",
+ sb->s_id);
+ goto failed_mount;
+
failed_mount3:
journal_destroy(sbi->s_journal);
failed_mount2:
- kfree(sbi->s_debts);
for (i = 0; i < db_count; i++)
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]);
- }
+ for (i = 0; i < MAXQUOTAS; i++)
+ kfree(sbi->s_qf_names[i]);
#endif
ext3_blkdev_remove(sbi);
brelse(bh);
out_fail:
sb->s_fs_info = NULL;
kfree(sbi);
+ lock_kernel();
return -EINVAL;
}
* This implicitly triggers the writebehind on sync().
*/
-void ext3_write_super (struct super_block * sb)
+static void ext3_write_super (struct super_block * sb)
{
if (down_trylock(&sb->s_lock) == 0)
BUG();
* LVM calls this function before a (read-only) snapshot is created. This
* gives us a chance to flush the journal completely and mark the fs clean.
*/
-void ext3_write_super_lockfs(struct super_block *sb)
+static void ext3_write_super_lockfs(struct super_block *sb)
{
sb->s_dirt = 0;
* Called by LVM after the snapshot is done. We need to reset the RECOVER
* flag here, even though the filesystem is not technically dirty yet.
*/
-void ext3_unlockfs(struct super_block *sb)
+static void ext3_unlockfs(struct super_block *sb)
{
if (!(sb->s_flags & MS_RDONLY)) {
lock_super(sb);
}
}
-int ext3_remount (struct super_block * sb, int * flags, char * data)
+static int ext3_remount (struct super_block * sb, int * flags, char * data)
{
struct ext3_super_block * es;
struct ext3_sb_info *sbi = EXT3_SB(sb);
unsigned long tmp;
+ unsigned long n_blocks_count = 0;
/*
* Allow the "check" option to be passed as a remount option.
*/
- if (!parse_options(data, sb, &tmp, 1))
+ if (!parse_options(data, sb, &tmp, &n_blocks_count, 1))
return -EINVAL;
if (sbi->s_mount_opt & EXT3_MOUNT_ABORT)
ext3_init_journal_params(sb, sbi->s_journal);
- if ((*flags & MS_RDONLY) != (sb->s_flags & MS_RDONLY)) {
+ if ((*flags & MS_RDONLY) != (sb->s_flags & MS_RDONLY) ||
+ n_blocks_count > le32_to_cpu(es->s_blocks_count)) {
if (sbi->s_mount_opt & EXT3_MOUNT_ABORT)
return -EROFS;
*/
ext3_clear_journal_err(sb, es);
sbi->s_mount_state = le16_to_cpu(es->s_state);
+ if ((ret = ext3_group_extend(sb, es, n_blocks_count)))
+ return ret;
if (!ext3_setup_super (sb, es, 0))
sb->s_flags &= ~MS_RDONLY;
}
return 0;
}
-int ext3_statfs (struct super_block * sb, struct kstatfs * buf)
+static int ext3_statfs (struct super_block * sb, struct kstatfs * buf)
{
struct ext3_super_block *es = EXT3_SB(sb)->s_es;
unsigned long overhead;
if (test_opt (sb, MINIX_DF))
overhead = 0;
else {
+ unsigned long ngroups;
+ ngroups = EXT3_SB(sb)->s_groups_count;
+ smp_rmb();
+
/*
* Compute the overhead (FS structures)
*/
* block group descriptors. If the sparse superblocks
* feature is turned on, then not all groups have this.
*/
- for (i = 0; i < EXT3_SB(sb)->s_groups_count; i++)
+ for (i = 0; i < ngroups; i++) {
overhead += ext3_bg_has_super(sb, i) +
ext3_bg_num_gdb(sb, i);
+ cond_resched();
+ }
/*
* Every block group has an inode bitmap, a block
* bitmap, and an inode table.
*/
- overhead += (EXT3_SB(sb)->s_groups_count *
- (2 + EXT3_SB(sb)->s_itb_per_group));
+ overhead += (ngroups * (2 + EXT3_SB(sb)->s_itb_per_group));
}
buf->f_type = EXT3_SUPER_MAGIC;
static inline struct inode *dquot_to_inode(struct dquot *dquot)
{
- return sb_dqopt(dquot->dq_sb)->files[dquot->dq_type]->f_dentry->d_inode;
+ return sb_dqopt(dquot->dq_sb)->files[dquot->dq_type];
}
static int ext3_dquot_initialize(struct inode *inode, int type)
{
int ret, err;
handle_t *handle;
+ struct inode *inode;
- handle = ext3_journal_start(dquot_to_inode(dquot),
+ inode = dquot_to_inode(dquot);
+ handle = ext3_journal_start(inode,
EXT3_QUOTA_TRANS_BLOCKS);
if (IS_ERR(handle))
return PTR_ERR(handle);
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);
- /* We invalidate dentry - it has at least wrong hash... */
+ /* Now invalidate and put the dentry - quota got its own reference
+ * to inode and dentry has at least wrong hash so we had better
+ * throw it away */
d_invalidate(dentry);
dput(dentry);
return err;
if (err)
return err;
/* Quotafile not on the same filesystem? */
- if (nd.mnt->mnt_sb != sb)
+ if (nd.mnt->mnt_sb != sb) {
+ path_release(&nd);
return -EXDEV;
+ }
/* Quotafile not of fs root? */
if (nd.dentry->d_parent->d_inode != sb->s_root->d_inode)
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);
}
+/* Read data from quotafile - avoid pagecache and such because we cannot afford
+ * acquiring the locks... As quota files are never truncated and quota code
+ * itself serializes the operations (and noone else should touch the files)
+ * we don't have to be afraid of races */
+static ssize_t ext3_quota_read(struct super_block *sb, int type, char *data,
+ size_t len, loff_t off)
+{
+ struct inode *inode = sb_dqopt(sb)->files[type];
+ sector_t blk = off >> EXT3_BLOCK_SIZE_BITS(sb);
+ int err = 0;
+ int offset = off & (sb->s_blocksize - 1);
+ int tocopy;
+ size_t toread;
+ struct buffer_head *bh;
+ loff_t i_size = i_size_read(inode);
+
+ if (off > i_size)
+ return 0;
+ if (off+len > i_size)
+ len = i_size-off;
+ toread = len;
+ while (toread > 0) {
+ tocopy = sb->s_blocksize - offset < toread ?
+ sb->s_blocksize - offset : toread;
+ bh = ext3_bread(NULL, inode, blk, 0, &err);
+ if (err)
+ return err;
+ if (!bh) /* A hole? */
+ memset(data, 0, tocopy);
+ else
+ memcpy(data, bh->b_data+offset, tocopy);
+ brelse(bh);
+ offset = 0;
+ toread -= tocopy;
+ data += tocopy;
+ blk++;
+ }
+ return len;
+}
+
+/* Write to quotafile (we know the transaction is already started and has
+ * enough credits) */
+static ssize_t ext3_quota_write(struct super_block *sb, int type,
+ const char *data, size_t len, loff_t off)
+{
+ struct inode *inode = sb_dqopt(sb)->files[type];
+ sector_t blk = off >> EXT3_BLOCK_SIZE_BITS(sb);
+ int err = 0;
+ int offset = off & (sb->s_blocksize - 1);
+ int tocopy;
+ int journal_quota = EXT3_SB(sb)->s_qf_names[type] != NULL;
+ size_t towrite = len;
+ struct buffer_head *bh;
+ handle_t *handle = journal_current_handle();
+
+ down(&inode->i_sem);
+ while (towrite > 0) {
+ tocopy = sb->s_blocksize - offset < towrite ?
+ sb->s_blocksize - offset : towrite;
+ bh = ext3_bread(handle, inode, blk, 1, &err);
+ if (!bh)
+ goto out;
+ if (journal_quota) {
+ err = ext3_journal_get_write_access(handle, bh);
+ if (err) {
+ brelse(bh);
+ goto out;
+ }
+ }
+ lock_buffer(bh);
+ memcpy(bh->b_data+offset, data, tocopy);
+ flush_dcache_page(bh->b_page);
+ unlock_buffer(bh);
+ if (journal_quota)
+ err = ext3_journal_dirty_metadata(handle, bh);
+ else {
+ /* Always do at least ordered writes for quotas */
+ err = ext3_journal_dirty_data(handle, bh);
+ mark_buffer_dirty(bh);
+ }
+ brelse(bh);
+ if (err)
+ goto out;
+ offset = 0;
+ towrite -= tocopy;
+ data += tocopy;
+ blk++;
+ }
+out:
+ if (len == towrite)
+ return err;
+ if (inode->i_size < off+len-towrite) {
+ i_size_write(inode, off+len-towrite);
+ EXT3_I(inode)->i_disksize = inode->i_size;
+ }
+ inode->i_version++;
+ inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+ ext3_mark_inode_dirty(handle, inode);
+ up(&inode->i_sem);
+ return len - towrite;
+}
+
#endif
static struct super_block *ext3_get_sb(struct file_system_type *fs_type,