diff options
author | Evgeniy Dushistov <dushistov@mail.ru> | 2006-07-01 07:36:24 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-07-01 12:56:03 -0400 |
commit | 10e5dce07e6f8f9cea1b54161a888bb099484f88 (patch) | |
tree | 9c7949cf82763344d86ae302748f8e1d278b565a /fs/ufs/truncate.c | |
parent | eb28931e4a2c89e53d2b0c1a02a843240bff0806 (diff) |
[PATCH] ufs: truncate should allocate block for last byte
This patch fixes buggy behaviour of UFS
in such kind of scenario:
open(, O_TRUNC...)
ftruncate(, 1024)
ftruncate(, 0)
Such a scenario causes ufs_panic and remount read-only. This happen
because of according to specification UFS should always allocate block for
last byte, and many parts of our implementation rely on this, but
`ufs_truncate' doesn't care about this.
To make possible return error code and to know about old size, this patch
removes `truncate' from ufs inode_operations and uses `setattr' method to
call ufs_truncate.
Signed-off-by: Evgeniy Dushistov <dushistov@mail.ru>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'fs/ufs/truncate.c')
-rw-r--r-- | fs/ufs/truncate.c | 148 |
1 files changed, 135 insertions, 13 deletions
diff --git a/fs/ufs/truncate.c b/fs/ufs/truncate.c index 3c3b301f8701..c9b55872079b 100644 --- a/fs/ufs/truncate.c +++ b/fs/ufs/truncate.c | |||
@@ -369,24 +369,97 @@ static int ufs_trunc_tindirect (struct inode * inode) | |||
369 | UFSD("EXIT\n"); | 369 | UFSD("EXIT\n"); |
370 | return retry; | 370 | return retry; |
371 | } | 371 | } |
372 | 372 | ||
373 | void ufs_truncate (struct inode * inode) | 373 | static int ufs_alloc_lastblock(struct inode *inode) |
374 | { | 374 | { |
375 | int err = 0; | ||
376 | struct address_space *mapping = inode->i_mapping; | ||
377 | struct ufs_sb_private_info *uspi = UFS_SB(inode->i_sb)->s_uspi; | ||
375 | struct ufs_inode_info *ufsi = UFS_I(inode); | 378 | struct ufs_inode_info *ufsi = UFS_I(inode); |
376 | struct super_block * sb; | 379 | unsigned lastfrag, i, end; |
377 | struct ufs_sb_private_info * uspi; | 380 | struct page *lastpage; |
378 | int retry; | 381 | struct buffer_head *bh; |
382 | |||
383 | lastfrag = (i_size_read(inode) + uspi->s_fsize - 1) >> uspi->s_fshift; | ||
384 | |||
385 | if (!lastfrag) { | ||
386 | ufsi->i_lastfrag = 0; | ||
387 | goto out; | ||
388 | } | ||
389 | lastfrag--; | ||
390 | |||
391 | lastpage = ufs_get_locked_page(mapping, lastfrag >> | ||
392 | (PAGE_CACHE_SHIFT - inode->i_blkbits)); | ||
393 | if (IS_ERR(lastpage)) { | ||
394 | err = -EIO; | ||
395 | goto out; | ||
396 | } | ||
397 | |||
398 | end = lastfrag & ((1 << (PAGE_CACHE_SHIFT - inode->i_blkbits)) - 1); | ||
399 | bh = page_buffers(lastpage); | ||
400 | for (i = 0; i < end; ++i) | ||
401 | bh = bh->b_this_page; | ||
402 | |||
403 | if (!buffer_mapped(bh)) { | ||
404 | err = ufs_getfrag_block(inode, lastfrag, bh, 1); | ||
405 | |||
406 | if (unlikely(err)) | ||
407 | goto out_unlock; | ||
408 | |||
409 | if (buffer_new(bh)) { | ||
410 | clear_buffer_new(bh); | ||
411 | unmap_underlying_metadata(bh->b_bdev, | ||
412 | bh->b_blocknr); | ||
413 | /* | ||
414 | * we do not zeroize fragment, because of | ||
415 | * if it maped to hole, it already contains zeroes | ||
416 | */ | ||
417 | set_buffer_uptodate(bh); | ||
418 | mark_buffer_dirty(bh); | ||
419 | set_page_dirty(lastpage); | ||
420 | } | ||
421 | } | ||
422 | out_unlock: | ||
423 | ufs_put_locked_page(lastpage); | ||
424 | out: | ||
425 | return err; | ||
426 | } | ||
427 | |||
428 | int ufs_truncate(struct inode *inode, loff_t old_i_size) | ||
429 | { | ||
430 | struct ufs_inode_info *ufsi = UFS_I(inode); | ||
431 | struct super_block *sb = inode->i_sb; | ||
432 | struct ufs_sb_private_info *uspi = UFS_SB(sb)->s_uspi; | ||
433 | int retry, err = 0; | ||
379 | 434 | ||
380 | UFSD("ENTER\n"); | 435 | UFSD("ENTER\n"); |
381 | sb = inode->i_sb; | ||
382 | uspi = UFS_SB(sb)->s_uspi; | ||
383 | 436 | ||
384 | if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))) | 437 | if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || |
385 | return; | 438 | S_ISLNK(inode->i_mode))) |
439 | return -EINVAL; | ||
386 | if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) | 440 | if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) |
387 | return; | 441 | return -EPERM; |
442 | |||
443 | if (inode->i_size > old_i_size) { | ||
444 | /* | ||
445 | * if we expand file we should care about | ||
446 | * allocation of block for last byte first of all | ||
447 | */ | ||
448 | err = ufs_alloc_lastblock(inode); | ||
449 | |||
450 | if (err) { | ||
451 | i_size_write(inode, old_i_size); | ||
452 | goto out; | ||
453 | } | ||
454 | /* | ||
455 | * go away, because of we expand file, and we do not | ||
456 | * need free blocks, and zeroizes page | ||
457 | */ | ||
458 | lock_kernel(); | ||
459 | goto almost_end; | ||
460 | } | ||
388 | 461 | ||
389 | block_truncate_page(inode->i_mapping, inode->i_size, ufs_getfrag_block); | 462 | block_truncate_page(inode->i_mapping, inode->i_size, ufs_getfrag_block); |
390 | 463 | ||
391 | lock_kernel(); | 464 | lock_kernel(); |
392 | while (1) { | 465 | while (1) { |
@@ -404,9 +477,58 @@ void ufs_truncate (struct inode * inode) | |||
404 | yield(); | 477 | yield(); |
405 | } | 478 | } |
406 | 479 | ||
480 | if (inode->i_size < old_i_size) { | ||
481 | /* | ||
482 | * now we should have enough space | ||
483 | * to allocate block for last byte | ||
484 | */ | ||
485 | err = ufs_alloc_lastblock(inode); | ||
486 | if (err) | ||
487 | /* | ||
488 | * looks like all the same - we have no space, | ||
489 | * but we truncate file already | ||
490 | */ | ||
491 | inode->i_size = (ufsi->i_lastfrag - 1) * uspi->s_fsize; | ||
492 | } | ||
493 | almost_end: | ||
407 | inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; | 494 | inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; |
408 | ufsi->i_lastfrag = DIRECT_FRAGMENT; | ||
409 | unlock_kernel(); | 495 | unlock_kernel(); |
410 | mark_inode_dirty(inode); | 496 | mark_inode_dirty(inode); |
411 | UFSD("EXIT\n"); | 497 | out: |
498 | UFSD("EXIT: err %d\n", err); | ||
499 | return err; | ||
412 | } | 500 | } |
501 | |||
502 | |||
503 | /* | ||
504 | * We don't define our `inode->i_op->truncate', and call it here, | ||
505 | * because of: | ||
506 | * - there is no way to know old size | ||
507 | * - there is no way inform user about error, if it happens in `truncate' | ||
508 | */ | ||
509 | static int ufs_setattr(struct dentry *dentry, struct iattr *attr) | ||
510 | { | ||
511 | struct inode *inode = dentry->d_inode; | ||
512 | unsigned int ia_valid = attr->ia_valid; | ||
513 | int error; | ||
514 | |||
515 | error = inode_change_ok(inode, attr); | ||
516 | if (error) | ||
517 | return error; | ||
518 | |||
519 | if (ia_valid & ATTR_SIZE && | ||
520 | attr->ia_size != i_size_read(inode)) { | ||
521 | loff_t old_i_size = inode->i_size; | ||
522 | error = vmtruncate(inode, attr->ia_size); | ||
523 | if (error) | ||
524 | return error; | ||
525 | error = ufs_truncate(inode, old_i_size); | ||
526 | if (error) | ||
527 | return error; | ||
528 | } | ||
529 | return inode_setattr(inode, attr); | ||
530 | } | ||
531 | |||
532 | struct inode_operations ufs_file_inode_operations = { | ||
533 | .setattr = ufs_setattr, | ||
534 | }; | ||