summaryrefslogtreecommitdiffstats
path: root/fs/ext2/fsync.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ext2/fsync.c')
-rw-r--r--fs/ext2/fsync.c133
1 files changed, 118 insertions, 15 deletions
diff --git a/fs/ext2/fsync.c b/fs/ext2/fsync.c
index 5b58f6cad..52ffd6138 100644
--- a/fs/ext2/fsync.c
+++ b/fs/ext2/fsync.c
@@ -27,28 +27,131 @@
#include <linux/smp_lock.h>
+#define blocksize (EXT2_BLOCK_SIZE(inode->i_sb))
+#define addr_per_block (EXT2_ADDR_PER_BLOCK(inode->i_sb))
+
+static int sync_indirect(struct inode * inode, u32 * block, int wait)
+{
+ struct buffer_head * bh;
+
+ if (!*block)
+ return 0;
+ bh = get_hash_table(inode->i_dev, le32_to_cpu(*block), blocksize);
+ if (!bh)
+ return 0;
+ if (wait && buffer_req(bh) && !buffer_uptodate(bh)) {
+ /* There can be a parallell read(2) that started read-I/O
+ on the buffer so we can't assume that there's been
+ an I/O error without first waiting I/O completation. */
+ wait_on_buffer(bh);
+ if (!buffer_uptodate(bh))
+ {
+ brelse (bh);
+ return -1;
+ }
+ }
+ if (wait || !buffer_uptodate(bh) || !buffer_dirty(bh)) {
+ if (wait)
+ /* when we return from fsync all the blocks
+ must be _just_ stored on disk */
+ wait_on_buffer(bh);
+ brelse(bh);
+ return 0;
+ }
+ ll_rw_block(WRITE, 1, &bh);
+ atomic_dec(&bh->b_count);
+ return 0;
+}
+
+static int sync_iblock(struct inode * inode, u32 * iblock,
+ struct buffer_head ** bh, int wait)
+{
+ int rc, tmp;
+
+ *bh = NULL;
+ tmp = le32_to_cpu(*iblock);
+ if (!tmp)
+ return 0;
+ rc = sync_indirect(inode, iblock, wait);
+ if (rc)
+ return rc;
+ *bh = bread(inode->i_dev, tmp, blocksize);
+ if (!*bh)
+ return -1;
+ return 0;
+}
+
+static int sync_dindirect(struct inode * inode, u32 * diblock, int wait)
+{
+ int i;
+ struct buffer_head * dind_bh;
+ int rc, err = 0;
+
+ rc = sync_iblock(inode, diblock, &dind_bh, wait);
+ if (rc || !dind_bh)
+ return rc;
+
+ for (i = 0; i < addr_per_block; i++) {
+ rc = sync_indirect(inode, ((u32 *) dind_bh->b_data) + i, wait);
+ if (rc)
+ err = rc;
+ }
+ brelse(dind_bh);
+ return err;
+}
+
+static int sync_tindirect(struct inode * inode, u32 * tiblock, int wait)
+{
+ int i;
+ struct buffer_head * tind_bh;
+ int rc, err = 0;
+
+ rc = sync_iblock(inode, tiblock, &tind_bh, wait);
+ if (rc || !tind_bh)
+ return rc;
+
+ for (i = 0; i < addr_per_block; i++) {
+ rc = sync_dindirect(inode, ((u32 *) tind_bh->b_data) + i, wait);
+ if (rc)
+ err = rc;
+ }
+ brelse(tind_bh);
+ return err;
+}
+
/*
* File may be NULL when we are called. Perhaps we shouldn't
* even pass file to fsync ?
*/
-int ext2_fsync_file(struct file * file, struct dentry *dentry, int datasync)
+int ext2_sync_file(struct file * file, struct dentry *dentry)
{
+ int wait, err = 0;
struct inode *inode = dentry->d_inode;
- return ext2_fsync_inode(inode, datasync);
-}
-int ext2_fsync_inode(struct inode *inode, int datasync)
-{
- int err;
-
- err = fsync_inode_buffers(inode);
- if (!(inode->i_state & I_DIRTY))
- return err;
- if (datasync && !(inode->i_state & I_DIRTY_DATASYNC))
- return err;
-
- err |= ext2_sync_inode(inode);
+ lock_kernel();
+ if (S_ISLNK(inode->i_mode) && !(inode->i_blocks))
+ /*
+ * Don't sync fast links!
+ */
+ goto skip;
+
+ err = generic_buffer_fdatasync(inode, 0, ~0UL);
+
+ for (wait=0; wait<=1; wait++)
+ {
+ err |= sync_indirect(inode,
+ inode->u.ext2_i.i_data+EXT2_IND_BLOCK,
+ wait);
+ err |= sync_dindirect(inode,
+ inode->u.ext2_i.i_data+EXT2_DIND_BLOCK,
+ wait);
+ err |= sync_tindirect(inode,
+ inode->u.ext2_i.i_data+EXT2_TIND_BLOCK,
+ wait);
+ }
+skip:
+ err |= ext2_sync_inode (inode);
+ unlock_kernel();
return err ? -EIO : 0;
}
-