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 | |
| 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>
| -rw-r--r-- | fs/ufs/balloc.c | 48 | ||||
| -rw-r--r-- | fs/ufs/file.c | 4 | ||||
| -rw-r--r-- | fs/ufs/inode.c | 7 | ||||
| -rw-r--r-- | fs/ufs/truncate.c | 148 | ||||
| -rw-r--r-- | fs/ufs/util.c | 54 | ||||
| -rw-r--r-- | fs/ufs/util.h | 8 | ||||
| -rw-r--r-- | include/linux/ufs_fs.h | 2 |
7 files changed, 205 insertions, 66 deletions
diff --git a/fs/ufs/balloc.c b/fs/ufs/balloc.c index 95b878e5c7a0..b01804baa120 100644 --- a/fs/ufs/balloc.c +++ b/fs/ufs/balloc.c | |||
| @@ -217,48 +217,6 @@ failed: | |||
| 217 | return; | 217 | return; |
| 218 | } | 218 | } |
| 219 | 219 | ||
| 220 | static struct page *ufs_get_locked_page(struct address_space *mapping, | ||
| 221 | unsigned long index) | ||
| 222 | { | ||
| 223 | struct page *page; | ||
| 224 | |||
| 225 | try_again: | ||
| 226 | page = find_lock_page(mapping, index); | ||
| 227 | if (!page) { | ||
| 228 | page = read_cache_page(mapping, index, | ||
| 229 | (filler_t*)mapping->a_ops->readpage, | ||
| 230 | NULL); | ||
| 231 | if (IS_ERR(page)) { | ||
| 232 | printk(KERN_ERR "ufs_change_blocknr: " | ||
| 233 | "read_cache_page error: ino %lu, index: %lu\n", | ||
| 234 | mapping->host->i_ino, index); | ||
| 235 | goto out; | ||
| 236 | } | ||
| 237 | |||
| 238 | lock_page(page); | ||
| 239 | |||
| 240 | if (!PageUptodate(page) || PageError(page)) { | ||
| 241 | unlock_page(page); | ||
| 242 | page_cache_release(page); | ||
| 243 | |||
| 244 | printk(KERN_ERR "ufs_change_blocknr: " | ||
| 245 | "can not read page: ino %lu, index: %lu\n", | ||
| 246 | mapping->host->i_ino, index); | ||
| 247 | |||
| 248 | page = ERR_PTR(-EIO); | ||
| 249 | goto out; | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | if (unlikely(!page->mapping || !page_has_buffers(page))) { | ||
| 254 | unlock_page(page); | ||
| 255 | page_cache_release(page); | ||
| 256 | goto try_again;/*we really need these buffers*/ | ||
| 257 | } | ||
| 258 | out: | ||
| 259 | return page; | ||
| 260 | } | ||
| 261 | |||
| 262 | /* | 220 | /* |
| 263 | * Modify inode page cache in such way: | 221 | * Modify inode page cache in such way: |
| 264 | * have - blocks with b_blocknr equal to oldb...oldb+count-1 | 222 | * have - blocks with b_blocknr equal to oldb...oldb+count-1 |
| @@ -311,10 +269,8 @@ static void ufs_change_blocknr(struct inode *inode, unsigned int baseblk, | |||
| 311 | 269 | ||
| 312 | set_page_dirty(page); | 270 | set_page_dirty(page); |
| 313 | 271 | ||
| 314 | if (likely(cur_index != index)) { | 272 | if (likely(cur_index != index)) |
| 315 | unlock_page(page); | 273 | ufs_put_locked_page(page); |
| 316 | page_cache_release(page); | ||
| 317 | } | ||
| 318 | } | 274 | } |
| 319 | UFSD("EXIT\n"); | 275 | UFSD("EXIT\n"); |
| 320 | } | 276 | } |
diff --git a/fs/ufs/file.c b/fs/ufs/file.c index 0e5001512a9d..a9c6e5f04fae 100644 --- a/fs/ufs/file.c +++ b/fs/ufs/file.c | |||
| @@ -60,7 +60,3 @@ const struct file_operations ufs_file_operations = { | |||
| 60 | .fsync = ufs_sync_file, | 60 | .fsync = ufs_sync_file, |
| 61 | .sendfile = generic_file_sendfile, | 61 | .sendfile = generic_file_sendfile, |
| 62 | }; | 62 | }; |
| 63 | |||
| 64 | struct inode_operations ufs_file_inode_operations = { | ||
| 65 | .truncate = ufs_truncate, | ||
| 66 | }; | ||
diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c index 488b5ff48afb..e7c8615beb65 100644 --- a/fs/ufs/inode.c +++ b/fs/ufs/inode.c | |||
| @@ -843,14 +843,17 @@ int ufs_sync_inode (struct inode *inode) | |||
| 843 | 843 | ||
| 844 | void ufs_delete_inode (struct inode * inode) | 844 | void ufs_delete_inode (struct inode * inode) |
| 845 | { | 845 | { |
| 846 | loff_t old_i_size; | ||
| 847 | |||
| 846 | truncate_inode_pages(&inode->i_data, 0); | 848 | truncate_inode_pages(&inode->i_data, 0); |
| 847 | /*UFS_I(inode)->i_dtime = CURRENT_TIME;*/ | 849 | /*UFS_I(inode)->i_dtime = CURRENT_TIME;*/ |
| 848 | lock_kernel(); | 850 | lock_kernel(); |
| 849 | mark_inode_dirty(inode); | 851 | mark_inode_dirty(inode); |
| 850 | ufs_update_inode(inode, IS_SYNC(inode)); | 852 | ufs_update_inode(inode, IS_SYNC(inode)); |
| 853 | old_i_size = inode->i_size; | ||
| 851 | inode->i_size = 0; | 854 | inode->i_size = 0; |
| 852 | if (inode->i_blocks) | 855 | if (inode->i_blocks && ufs_truncate(inode, old_i_size)) |
| 853 | ufs_truncate (inode); | 856 | ufs_warning(inode->i_sb, __FUNCTION__, "ufs_truncate failed\n"); |
| 854 | ufs_free_inode (inode); | 857 | ufs_free_inode (inode); |
| 855 | unlock_kernel(); | 858 | unlock_kernel(); |
| 856 | } | 859 | } |
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 | }; | ||
diff --git a/fs/ufs/util.c b/fs/ufs/util.c index a2f13f45708b..337cf2c46d10 100644 --- a/fs/ufs/util.c +++ b/fs/ufs/util.c | |||
| @@ -233,3 +233,57 @@ ufs_set_inode_dev(struct super_block *sb, struct ufs_inode_info *ufsi, dev_t dev | |||
| 233 | else | 233 | else |
| 234 | ufsi->i_u1.i_data[0] = fs32; | 234 | ufsi->i_u1.i_data[0] = fs32; |
| 235 | } | 235 | } |
| 236 | |||
| 237 | /** | ||
| 238 | * ufs_get_locked_page() - locate, pin and lock a pagecache page, if not exist | ||
| 239 | * read it from disk. | ||
| 240 | * @mapping: the address_space to search | ||
| 241 | * @index: the page index | ||
| 242 | * | ||
| 243 | * Locates the desired pagecache page, if not exist we'll read it, | ||
| 244 | * locks it, increments its reference | ||
| 245 | * count and returns its address. | ||
| 246 | * | ||
| 247 | */ | ||
| 248 | |||
| 249 | struct page *ufs_get_locked_page(struct address_space *mapping, | ||
| 250 | pgoff_t index) | ||
| 251 | { | ||
| 252 | struct page *page; | ||
| 253 | |||
| 254 | try_again: | ||
| 255 | page = find_lock_page(mapping, index); | ||
| 256 | if (!page) { | ||
| 257 | page = read_cache_page(mapping, index, | ||
| 258 | (filler_t*)mapping->a_ops->readpage, | ||
| 259 | NULL); | ||
| 260 | if (IS_ERR(page)) { | ||
| 261 | printk(KERN_ERR "ufs_change_blocknr: " | ||
| 262 | "read_cache_page error: ino %lu, index: %lu\n", | ||
| 263 | mapping->host->i_ino, index); | ||
| 264 | goto out; | ||
| 265 | } | ||
| 266 | |||
| 267 | lock_page(page); | ||
| 268 | |||
| 269 | if (!PageUptodate(page) || PageError(page)) { | ||
| 270 | unlock_page(page); | ||
| 271 | page_cache_release(page); | ||
| 272 | |||
| 273 | printk(KERN_ERR "ufs_change_blocknr: " | ||
| 274 | "can not read page: ino %lu, index: %lu\n", | ||
| 275 | mapping->host->i_ino, index); | ||
| 276 | |||
| 277 | page = ERR_PTR(-EIO); | ||
| 278 | goto out; | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | if (unlikely(!page->mapping || !page_has_buffers(page))) { | ||
| 283 | unlock_page(page); | ||
| 284 | page_cache_release(page); | ||
| 285 | goto try_again;/*we really need these buffers*/ | ||
| 286 | } | ||
| 287 | out: | ||
| 288 | return page; | ||
| 289 | } | ||
diff --git a/fs/ufs/util.h b/fs/ufs/util.h index 406981fff5e7..28fce6c239b5 100644 --- a/fs/ufs/util.h +++ b/fs/ufs/util.h | |||
| @@ -251,6 +251,14 @@ extern void _ubh_ubhcpymem_(struct ufs_sb_private_info *, unsigned char *, struc | |||
| 251 | #define ubh_memcpyubh(ubh,mem,size) _ubh_memcpyubh_(uspi,ubh,mem,size) | 251 | #define ubh_memcpyubh(ubh,mem,size) _ubh_memcpyubh_(uspi,ubh,mem,size) |
| 252 | extern void _ubh_memcpyubh_(struct ufs_sb_private_info *, struct ufs_buffer_head *, unsigned char *, unsigned); | 252 | extern void _ubh_memcpyubh_(struct ufs_sb_private_info *, struct ufs_buffer_head *, unsigned char *, unsigned); |
| 253 | 253 | ||
| 254 | /* This functions works with cache pages*/ | ||
| 255 | extern struct page *ufs_get_locked_page(struct address_space *mapping, | ||
| 256 | pgoff_t index); | ||
| 257 | static inline void ufs_put_locked_page(struct page *page) | ||
| 258 | { | ||
| 259 | unlock_page(page); | ||
| 260 | page_cache_release(page); | ||
| 261 | } | ||
| 254 | 262 | ||
| 255 | 263 | ||
| 256 | /* | 264 | /* |
diff --git a/include/linux/ufs_fs.h b/include/linux/ufs_fs.h index e39b7cc43390..fc62887c5206 100644 --- a/include/linux/ufs_fs.h +++ b/include/linux/ufs_fs.h | |||
| @@ -993,7 +993,7 @@ extern void ufs_panic (struct super_block *, const char *, const char *, ...) __ | |||
| 993 | extern struct inode_operations ufs_fast_symlink_inode_operations; | 993 | extern struct inode_operations ufs_fast_symlink_inode_operations; |
| 994 | 994 | ||
| 995 | /* truncate.c */ | 995 | /* truncate.c */ |
| 996 | extern void ufs_truncate (struct inode *); | 996 | extern int ufs_truncate (struct inode *, loff_t); |
| 997 | 997 | ||
| 998 | static inline struct ufs_sb_info *UFS_SB(struct super_block *sb) | 998 | static inline struct ufs_sb_info *UFS_SB(struct super_block *sb) |
| 999 | { | 999 | { |
