diff options
Diffstat (limited to 'fs/btrfs/file.c')
| -rw-r--r-- | fs/btrfs/file.c | 92 | 
1 files changed, 75 insertions, 17 deletions
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index 1fd827b99c1b..9dfde1af8a64 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -2323,25 +2323,62 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)  	 */  	btrfs_inode_unlock(inode, BTRFS_ILOCK_MMAP); -	if (ret != BTRFS_NO_LOG_SYNC) { +	if (ret == BTRFS_NO_LOG_SYNC) { +		ret = btrfs_end_transaction(trans); +		goto out; +	} + +	/* We successfully logged the inode, attempt to sync the log. */ +	if (!ret) { +		ret = btrfs_sync_log(trans, root, &ctx);  		if (!ret) { -			ret = btrfs_sync_log(trans, root, &ctx); -			if (!ret) { -				ret = btrfs_end_transaction(trans); -				goto out; -			} -		} -		if (!full_sync) { -			ret = btrfs_wait_ordered_range(inode, start, len); -			if (ret) { -				btrfs_end_transaction(trans); -				goto out; -			} +			ret = btrfs_end_transaction(trans); +			goto out;  		} -		ret = btrfs_commit_transaction(trans); -	} else { +	} + +	/* +	 * At this point we need to commit the transaction because we had +	 * btrfs_need_log_full_commit() or some other error. +	 * +	 * If we didn't do a full sync we have to stop the trans handle, wait on +	 * the ordered extents, start it again and commit the transaction.  If +	 * we attempt to wait on the ordered extents here we could deadlock with +	 * something like fallocate() that is holding the extent lock trying to +	 * start a transaction while some other thread is trying to commit the +	 * transaction while we (fsync) are currently holding the transaction +	 * open. +	 */ +	if (!full_sync) {  		ret = btrfs_end_transaction(trans); +		if (ret) +			goto out; +		ret = btrfs_wait_ordered_range(inode, start, len); +		if (ret) +			goto out; + +		/* +		 * This is safe to use here because we're only interested in +		 * making sure the transaction that had the ordered extents is +		 * committed.  We aren't waiting on anything past this point, +		 * we're purely getting the transaction and committing it. +		 */ +		trans = btrfs_attach_transaction_barrier(root); +		if (IS_ERR(trans)) { +			ret = PTR_ERR(trans); + +			/* +			 * We committed the transaction and there's no currently +			 * running transaction, this means everything we care +			 * about made it to disk and we are done. +			 */ +			if (ret == -ENOENT) +				ret = 0; +			goto out; +		}  	} + +	ret = btrfs_commit_transaction(trans);  out:  	ASSERT(list_empty(&ctx.list));  	err = file_check_and_advance_wb_err(file); @@ -2719,7 +2756,8 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,  	ret = btrfs_block_rsv_migrate(&fs_info->trans_block_rsv, rsv,  				      min_size, false); -	BUG_ON(ret); +	if (WARN_ON(ret)) +		goto out_trans;  	trans->block_rsv = rsv;  	cur_offset = start; @@ -2803,6 +2841,25 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,  			extent_info->file_offset += replace_len;  		} +		/* +		 * We are releasing our handle on the transaction, balance the +		 * dirty pages of the btree inode and flush delayed items, and +		 * then get a new transaction handle, which may now point to a +		 * new transaction in case someone else may have committed the +		 * transaction we used to replace/drop file extent items. So +		 * bump the inode's iversion and update mtime and ctime except +		 * if we are called from a dedupe context. This is because a +		 * power failure/crash may happen after the transaction is +		 * committed and before we finish replacing/dropping all the +		 * file extent items we need. +		 */ +		inode_inc_iversion(&inode->vfs_inode); + +		if (!extent_info || extent_info->update_times) { +			inode->vfs_inode.i_mtime = current_time(&inode->vfs_inode); +			inode->vfs_inode.i_ctime = inode->vfs_inode.i_mtime; +		} +  		ret = btrfs_update_inode(trans, root, inode);  		if (ret)  			break; @@ -2819,7 +2876,8 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,  		ret = btrfs_block_rsv_migrate(&fs_info->trans_block_rsv,  					      rsv, min_size, false); -		BUG_ON(ret);	/* shouldn't happen */ +		if (WARN_ON(ret)) +			break;  		trans->block_rsv = rsv;  		cur_offset = drop_args.drop_end;  |