diff options
author | Jan Kara <jack@suse.cz> | 2011-05-03 11:12:58 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2011-05-03 11:12:58 -0400 |
commit | df5e6223407e3e645065c4bd968fee007f0e0287 (patch) | |
tree | d5027dc9645dea4a5536819ac98f4299757fff22 /fs | |
parent | 7ad8e4e6ae2a7c95445ee1715b1714106fb95037 (diff) |
ext4: fix deadlock in ext4_symlink() in ENOSPC conditions
ext4_symlink() cannot call __page_symlink() with transaction open.
__page_symlink() calls ext4_write_begin() which can wait for
transaction commit if we are running out of space thus causing a
deadlock. Also error recovery in ext4_truncate_failed_write() does not
count with the transaction being already started (although I'm not
aware of any particular deadlock here).
Fix the problem by stopping a transaction before calling
__page_symlink() (we have to be careful and put inode to orphan list
so that it gets deleted in case of crash) and starting another one
after __page_symlink() returns for addition of symlink into a
directory.
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/ext4/namei.c | 66 |
1 files changed, 55 insertions, 11 deletions
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index cadf04b924aa..3c7a06e58469 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c | |||
@@ -2250,6 +2250,7 @@ static int ext4_symlink(struct inode *dir, | |||
2250 | handle_t *handle; | 2250 | handle_t *handle; |
2251 | struct inode *inode; | 2251 | struct inode *inode; |
2252 | int l, err, retries = 0; | 2252 | int l, err, retries = 0; |
2253 | int credits; | ||
2253 | 2254 | ||
2254 | l = strlen(symname)+1; | 2255 | l = strlen(symname)+1; |
2255 | if (l > dir->i_sb->s_blocksize) | 2256 | if (l > dir->i_sb->s_blocksize) |
@@ -2257,10 +2258,26 @@ static int ext4_symlink(struct inode *dir, | |||
2257 | 2258 | ||
2258 | dquot_initialize(dir); | 2259 | dquot_initialize(dir); |
2259 | 2260 | ||
2261 | if (l > EXT4_N_BLOCKS * 4) { | ||
2262 | /* | ||
2263 | * For non-fast symlinks, we just allocate inode and put it on | ||
2264 | * orphan list in the first transaction => we need bitmap, | ||
2265 | * group descriptor, sb, inode block, quota blocks. | ||
2266 | */ | ||
2267 | credits = 4 + EXT4_MAXQUOTAS_INIT_BLOCKS(dir->i_sb); | ||
2268 | } else { | ||
2269 | /* | ||
2270 | * Fast symlink. We have to add entry to directory | ||
2271 | * (EXT4_DATA_TRANS_BLOCKS + EXT4_INDEX_EXTRA_TRANS_BLOCKS), | ||
2272 | * allocate new inode (bitmap, group descriptor, inode block, | ||
2273 | * quota blocks, sb is already counted in previous macros). | ||
2274 | */ | ||
2275 | credits = EXT4_DATA_TRANS_BLOCKS(dir->i_sb) + | ||
2276 | EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3 + | ||
2277 | EXT4_MAXQUOTAS_INIT_BLOCKS(dir->i_sb); | ||
2278 | } | ||
2260 | retry: | 2279 | retry: |
2261 | handle = ext4_journal_start(dir, EXT4_DATA_TRANS_BLOCKS(dir->i_sb) + | 2280 | handle = ext4_journal_start(dir, credits); |
2262 | EXT4_INDEX_EXTRA_TRANS_BLOCKS + 5 + | ||
2263 | EXT4_MAXQUOTAS_INIT_BLOCKS(dir->i_sb)); | ||
2264 | if (IS_ERR(handle)) | 2281 | if (IS_ERR(handle)) |
2265 | return PTR_ERR(handle); | 2282 | return PTR_ERR(handle); |
2266 | 2283 | ||
@@ -2273,21 +2290,44 @@ retry: | |||
2273 | if (IS_ERR(inode)) | 2290 | if (IS_ERR(inode)) |
2274 | goto out_stop; | 2291 | goto out_stop; |
2275 | 2292 | ||
2276 | if (l > sizeof(EXT4_I(inode)->i_data)) { | 2293 | if (l > EXT4_N_BLOCKS * 4) { |
2277 | inode->i_op = &ext4_symlink_inode_operations; | 2294 | inode->i_op = &ext4_symlink_inode_operations; |
2278 | ext4_set_aops(inode); | 2295 | ext4_set_aops(inode); |
2279 | /* | 2296 | /* |
2280 | * page_symlink() calls into ext4_prepare/commit_write. | 2297 | * We cannot call page_symlink() with transaction started |
2281 | * We have a transaction open. All is sweetness. It also sets | 2298 | * because it calls into ext4_write_begin() which can wait |
2282 | * i_size in generic_commit_write(). | 2299 | * for transaction commit if we are running out of space |
2300 | * and thus we deadlock. So we have to stop transaction now | ||
2301 | * and restart it when symlink contents is written. | ||
2302 | * | ||
2303 | * To keep fs consistent in case of crash, we have to put inode | ||
2304 | * to orphan list in the mean time. | ||
2283 | */ | 2305 | */ |
2306 | drop_nlink(inode); | ||
2307 | err = ext4_orphan_add(handle, inode); | ||
2308 | ext4_journal_stop(handle); | ||
2309 | if (err) | ||
2310 | goto err_drop_inode; | ||
2284 | err = __page_symlink(inode, symname, l, 1); | 2311 | err = __page_symlink(inode, symname, l, 1); |
2312 | if (err) | ||
2313 | goto err_drop_inode; | ||
2314 | /* | ||
2315 | * Now inode is being linked into dir (EXT4_DATA_TRANS_BLOCKS | ||
2316 | * + EXT4_INDEX_EXTRA_TRANS_BLOCKS), inode is also modified | ||
2317 | */ | ||
2318 | handle = ext4_journal_start(dir, | ||
2319 | EXT4_DATA_TRANS_BLOCKS(dir->i_sb) + | ||
2320 | EXT4_INDEX_EXTRA_TRANS_BLOCKS + 1); | ||
2321 | if (IS_ERR(handle)) { | ||
2322 | err = PTR_ERR(handle); | ||
2323 | goto err_drop_inode; | ||
2324 | } | ||
2325 | inc_nlink(inode); | ||
2326 | err = ext4_orphan_del(handle, inode); | ||
2285 | if (err) { | 2327 | if (err) { |
2328 | ext4_journal_stop(handle); | ||
2286 | clear_nlink(inode); | 2329 | clear_nlink(inode); |
2287 | unlock_new_inode(inode); | 2330 | goto err_drop_inode; |
2288 | ext4_mark_inode_dirty(handle, inode); | ||
2289 | iput(inode); | ||
2290 | goto out_stop; | ||
2291 | } | 2331 | } |
2292 | } else { | 2332 | } else { |
2293 | /* clear the extent format for fast symlink */ | 2333 | /* clear the extent format for fast symlink */ |
@@ -2303,6 +2343,10 @@ out_stop: | |||
2303 | if (err == -ENOSPC && ext4_should_retry_alloc(dir->i_sb, &retries)) | 2343 | if (err == -ENOSPC && ext4_should_retry_alloc(dir->i_sb, &retries)) |
2304 | goto retry; | 2344 | goto retry; |
2305 | return err; | 2345 | return err; |
2346 | err_drop_inode: | ||
2347 | unlock_new_inode(inode); | ||
2348 | iput(inode); | ||
2349 | return err; | ||
2306 | } | 2350 | } |
2307 | 2351 | ||
2308 | static int ext4_link(struct dentry *old_dentry, | 2352 | static int ext4_link(struct dentry *old_dentry, |