diff options
Diffstat (limited to 'fs/ext2/fsync.c')
-rw-r--r-- | fs/ext2/fsync.c | 133 |
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; } - |