Commit bd1939de authored by Jan Kara's avatar Jan Kara Committed by Linus Torvalds

ext3: fix lock inversion in direct IO

We cannot start transaction in ext3_direct_IO() and just let it last during
the whole write because dio_get_page() acquires mmap_sem which ranks above
transaction start (e.g.  because we have dependency chain
mmap_sem->PageLock->journal_start, or because we update atime while holding
mmap_sem) and thus deadlocks could happen.  We solve the problem by
starting a transaction separately for each ext3_get_block() call.

We *could* have a problem that we allocate a block and before its data are
written out the machine crashes and thus we expose stale data.  But that
does not happen because for hole-filling generic code falls back to
buffered writes and for file extension, we add inode to orphan list and
thus in case of crash, journal replay will truncate inode back to the
original size.

[akpm@linux-foundation.org: build fix]
Signed-off-by: default avatarJan Kara <jack@suse.cz>
Cc: <linux-ext4@vger.kernel.org>
Cc: Zach Brown <zach.brown@oracle.com>
Cc: Badari Pulavarty <pbadari@us.ibm.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent d8fd66aa
...@@ -939,55 +939,45 @@ out: ...@@ -939,55 +939,45 @@ out:
return err; return err;
} }
#define DIO_CREDITS (EXT3_RESERVE_TRANS_BLOCKS + 32) /* Maximum number of blocks we map for direct IO at once. */
#define DIO_MAX_BLOCKS 4096
/*
* Number of credits we need for writing DIO_MAX_BLOCKS:
* We need sb + group descriptor + bitmap + inode -> 4
* For B blocks with A block pointers per block we need:
* 1 (triple ind.) + (B/A/A + 2) (doubly ind.) + (B/A + 2) (indirect).
* If we plug in 4096 for B and 256 for A (for 1KB block size), we get 25.
*/
#define DIO_CREDITS 25
static int ext3_get_block(struct inode *inode, sector_t iblock, static int ext3_get_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create) struct buffer_head *bh_result, int create)
{ {
handle_t *handle = ext3_journal_current_handle(); handle_t *handle = ext3_journal_current_handle();
int ret = 0; int ret = 0, started = 0;
unsigned max_blocks = bh_result->b_size >> inode->i_blkbits; unsigned max_blocks = bh_result->b_size >> inode->i_blkbits;
if (!create) if (create && !handle) { /* Direct IO write... */
goto get_block; /* A read */ if (max_blocks > DIO_MAX_BLOCKS)
max_blocks = DIO_MAX_BLOCKS;
if (max_blocks == 1) handle = ext3_journal_start(inode, DIO_CREDITS +
goto get_block; /* A single block get */ 2 * EXT3_QUOTA_TRANS_BLOCKS(inode->i_sb));
if (IS_ERR(handle)) {
if (handle->h_transaction->t_state == T_LOCKED) {
/*
* Huge direct-io writes can hold off commits for long
* periods of time. Let this commit run.
*/
ext3_journal_stop(handle);
handle = ext3_journal_start(inode, DIO_CREDITS);
if (IS_ERR(handle))
ret = PTR_ERR(handle); ret = PTR_ERR(handle);
goto get_block; goto out;
}
if (handle->h_buffer_credits <= EXT3_RESERVE_TRANS_BLOCKS) {
/*
* Getting low on buffer credits...
*/
ret = ext3_journal_extend(handle, DIO_CREDITS);
if (ret > 0) {
/*
* Couldn't extend the transaction. Start a new one.
*/
ret = ext3_journal_restart(handle, DIO_CREDITS);
} }
started = 1;
} }
get_block:
if (ret == 0) {
ret = ext3_get_blocks_handle(handle, inode, iblock, ret = ext3_get_blocks_handle(handle, inode, iblock,
max_blocks, bh_result, create, 0); max_blocks, bh_result, create, 0);
if (ret > 0) { if (ret > 0) {
bh_result->b_size = (ret << inode->i_blkbits); bh_result->b_size = (ret << inode->i_blkbits);
ret = 0; ret = 0;
} }
} if (started)
ext3_journal_stop(handle);
out:
return ret; return ret;
} }
...@@ -1678,7 +1668,8 @@ static int ext3_releasepage(struct page *page, gfp_t wait) ...@@ -1678,7 +1668,8 @@ static int ext3_releasepage(struct page *page, gfp_t wait)
* if the machine crashes during the write. * if the machine crashes during the write.
* *
* If the O_DIRECT write is intantiating holes inside i_size and the machine * If the O_DIRECT write is intantiating holes inside i_size and the machine
* crashes then stale disk data _may_ be exposed inside the file. * crashes then stale disk data _may_ be exposed inside the file. But current
* VFS code falls back into buffered path in that case so we are safe.
*/ */
static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb, static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb,
const struct iovec *iov, loff_t offset, const struct iovec *iov, loff_t offset,
...@@ -1687,7 +1678,7 @@ static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb, ...@@ -1687,7 +1678,7 @@ static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb,
struct file *file = iocb->ki_filp; struct file *file = iocb->ki_filp;
struct inode *inode = file->f_mapping->host; struct inode *inode = file->f_mapping->host;
struct ext3_inode_info *ei = EXT3_I(inode); struct ext3_inode_info *ei = EXT3_I(inode);
handle_t *handle = NULL; handle_t *handle;
ssize_t ret; ssize_t ret;
int orphan = 0; int orphan = 0;
size_t count = iov_length(iov, nr_segs); size_t count = iov_length(iov, nr_segs);
...@@ -1695,17 +1686,21 @@ static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb, ...@@ -1695,17 +1686,21 @@ static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb,
if (rw == WRITE) { if (rw == WRITE) {
loff_t final_size = offset + count; loff_t final_size = offset + count;
handle = ext3_journal_start(inode, DIO_CREDITS); if (final_size > inode->i_size) {
/* Credits for sb + inode write */
handle = ext3_journal_start(inode, 2);
if (IS_ERR(handle)) { if (IS_ERR(handle)) {
ret = PTR_ERR(handle); ret = PTR_ERR(handle);
goto out; goto out;
} }
if (final_size > inode->i_size) {
ret = ext3_orphan_add(handle, inode); ret = ext3_orphan_add(handle, inode);
if (ret) if (ret) {
goto out_stop; ext3_journal_stop(handle);
goto out;
}
orphan = 1; orphan = 1;
ei->i_disksize = inode->i_size; ei->i_disksize = inode->i_size;
ext3_journal_stop(handle);
} }
} }
...@@ -1713,18 +1708,21 @@ static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb, ...@@ -1713,18 +1708,21 @@ static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb,
offset, nr_segs, offset, nr_segs,
ext3_get_block, NULL); ext3_get_block, NULL);
/* if (orphan) {
* Reacquire the handle: ext3_get_block() can restart the transaction
*/
handle = ext3_journal_current_handle();
out_stop:
if (handle) {
int err; int err;
if (orphan && inode->i_nlink) /* Credits for sb + inode write */
handle = ext3_journal_start(inode, 2);
if (IS_ERR(handle)) {
/* This is really bad luck. We've written the data
* but cannot extend i_size. Bail out and pretend
* the write failed... */
ret = PTR_ERR(handle);
goto out;
}
if (inode->i_nlink)
ext3_orphan_del(handle, inode); ext3_orphan_del(handle, inode);
if (orphan && ret > 0) { if (ret > 0) {
loff_t end = offset + ret; loff_t end = offset + ret;
if (end > inode->i_size) { if (end > inode->i_size) {
ei->i_disksize = end; ei->i_disksize = end;
......
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