diff options
author | Dmitry Monakhov <dmonakhov@openvz.org> | 2013-03-10 21:01:03 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2013-03-10 21:01:03 -0400 |
commit | 921f266bc6bfe6ebb599c559f10443af314c19ec (patch) | |
tree | 270647b58f430887b9f510693b73c88a157bdb56 /fs/ext4 | |
parent | bd384364c1185ecb01f3b8242c915ccb5921c60d (diff) |
ext4: add self-testing infrastructure to do a sanity check
This commit adds a self-testing infrastructure like extent tree does to
do a sanity check for extent status tree. After status tree is as a
extent cache, we'd better to make sure that it caches right result.
After applied this commit, we will get a lot of messages when we run
xfstests as below.
...
kernel: ES len assertation failed for inode: 230 retval 1 != map->m_len
3 in ext4_map_blocks (allocation)
...
kernel: ES cache assertation failed for inode: 230 es_cached ex
[974/2/4781/20] != found ex [974/1/4781/1000]
...
kernel: ES insert assertation failed for inode: 635 ex_status
[0/45/21388/w] != es_status [44/1/21432/u]
...
Signed-off-by: Dmitry Monakhov <dmonakhov@openvz.org>
Signed-off-by: Zheng Liu <wenqing.lz@taobao.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Diffstat (limited to 'fs/ext4')
-rw-r--r-- | fs/ext4/extents_status.c | 175 | ||||
-rw-r--r-- | fs/ext4/extents_status.h | 6 | ||||
-rw-r--r-- | fs/ext4/inode.c | 96 |
3 files changed, 277 insertions, 0 deletions
diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c index 37f9a2d8fd04..d2a8cb74676b 100644 --- a/fs/ext4/extents_status.c +++ b/fs/ext4/extents_status.c | |||
@@ -399,6 +399,179 @@ ext4_es_try_to_merge_right(struct inode *inode, struct extent_status *es) | |||
399 | return es; | 399 | return es; |
400 | } | 400 | } |
401 | 401 | ||
402 | #ifdef ES_AGGRESSIVE_TEST | ||
403 | static void ext4_es_insert_extent_ext_check(struct inode *inode, | ||
404 | struct extent_status *es) | ||
405 | { | ||
406 | struct ext4_ext_path *path = NULL; | ||
407 | struct ext4_extent *ex; | ||
408 | ext4_lblk_t ee_block; | ||
409 | ext4_fsblk_t ee_start; | ||
410 | unsigned short ee_len; | ||
411 | int depth, ee_status, es_status; | ||
412 | |||
413 | path = ext4_ext_find_extent(inode, es->es_lblk, NULL); | ||
414 | if (IS_ERR(path)) | ||
415 | return; | ||
416 | |||
417 | depth = ext_depth(inode); | ||
418 | ex = path[depth].p_ext; | ||
419 | |||
420 | if (ex) { | ||
421 | |||
422 | ee_block = le32_to_cpu(ex->ee_block); | ||
423 | ee_start = ext4_ext_pblock(ex); | ||
424 | ee_len = ext4_ext_get_actual_len(ex); | ||
425 | |||
426 | ee_status = ext4_ext_is_uninitialized(ex) ? 1 : 0; | ||
427 | es_status = ext4_es_is_unwritten(es) ? 1 : 0; | ||
428 | |||
429 | /* | ||
430 | * Make sure ex and es are not overlap when we try to insert | ||
431 | * a delayed/hole extent. | ||
432 | */ | ||
433 | if (!ext4_es_is_written(es) && !ext4_es_is_unwritten(es)) { | ||
434 | if (in_range(es->es_lblk, ee_block, ee_len)) { | ||
435 | pr_warn("ES insert assertation failed for " | ||
436 | "inode: %lu we can find an extent " | ||
437 | "at block [%d/%d/%llu/%c], but we " | ||
438 | "want to add an delayed/hole extent " | ||
439 | "[%d/%d/%llu/%llx]\n", | ||
440 | inode->i_ino, ee_block, ee_len, | ||
441 | ee_start, ee_status ? 'u' : 'w', | ||
442 | es->es_lblk, es->es_len, | ||
443 | ext4_es_pblock(es), ext4_es_status(es)); | ||
444 | } | ||
445 | goto out; | ||
446 | } | ||
447 | |||
448 | /* | ||
449 | * We don't check ee_block == es->es_lblk, etc. because es | ||
450 | * might be a part of whole extent, vice versa. | ||
451 | */ | ||
452 | if (es->es_lblk < ee_block || | ||
453 | ext4_es_pblock(es) != ee_start + es->es_lblk - ee_block) { | ||
454 | pr_warn("ES insert assertation failed for inode: %lu " | ||
455 | "ex_status [%d/%d/%llu/%c] != " | ||
456 | "es_status [%d/%d/%llu/%c]\n", inode->i_ino, | ||
457 | ee_block, ee_len, ee_start, | ||
458 | ee_status ? 'u' : 'w', es->es_lblk, es->es_len, | ||
459 | ext4_es_pblock(es), es_status ? 'u' : 'w'); | ||
460 | goto out; | ||
461 | } | ||
462 | |||
463 | if (ee_status ^ es_status) { | ||
464 | pr_warn("ES insert assertation failed for inode: %lu " | ||
465 | "ex_status [%d/%d/%llu/%c] != " | ||
466 | "es_status [%d/%d/%llu/%c]\n", inode->i_ino, | ||
467 | ee_block, ee_len, ee_start, | ||
468 | ee_status ? 'u' : 'w', es->es_lblk, es->es_len, | ||
469 | ext4_es_pblock(es), es_status ? 'u' : 'w'); | ||
470 | } | ||
471 | } else { | ||
472 | /* | ||
473 | * We can't find an extent on disk. So we need to make sure | ||
474 | * that we don't want to add an written/unwritten extent. | ||
475 | */ | ||
476 | if (!ext4_es_is_delayed(es) && !ext4_es_is_hole(es)) { | ||
477 | pr_warn("ES insert assertation failed for inode: %lu " | ||
478 | "can't find an extent at block %d but we want " | ||
479 | "to add an written/unwritten extent " | ||
480 | "[%d/%d/%llu/%llx]\n", inode->i_ino, | ||
481 | es->es_lblk, es->es_lblk, es->es_len, | ||
482 | ext4_es_pblock(es), ext4_es_status(es)); | ||
483 | } | ||
484 | } | ||
485 | out: | ||
486 | if (path) { | ||
487 | ext4_ext_drop_refs(path); | ||
488 | kfree(path); | ||
489 | } | ||
490 | } | ||
491 | |||
492 | static void ext4_es_insert_extent_ind_check(struct inode *inode, | ||
493 | struct extent_status *es) | ||
494 | { | ||
495 | struct ext4_map_blocks map; | ||
496 | int retval; | ||
497 | |||
498 | /* | ||
499 | * Here we call ext4_ind_map_blocks to lookup a block mapping because | ||
500 | * 'Indirect' structure is defined in indirect.c. So we couldn't | ||
501 | * access direct/indirect tree from outside. It is too dirty to define | ||
502 | * this function in indirect.c file. | ||
503 | */ | ||
504 | |||
505 | map.m_lblk = es->es_lblk; | ||
506 | map.m_len = es->es_len; | ||
507 | |||
508 | retval = ext4_ind_map_blocks(NULL, inode, &map, 0); | ||
509 | if (retval > 0) { | ||
510 | if (ext4_es_is_delayed(es) || ext4_es_is_hole(es)) { | ||
511 | /* | ||
512 | * We want to add a delayed/hole extent but this | ||
513 | * block has been allocated. | ||
514 | */ | ||
515 | pr_warn("ES insert assertation failed for inode: %lu " | ||
516 | "We can find blocks but we want to add a " | ||
517 | "delayed/hole extent [%d/%d/%llu/%llx]\n", | ||
518 | inode->i_ino, es->es_lblk, es->es_len, | ||
519 | ext4_es_pblock(es), ext4_es_status(es)); | ||
520 | return; | ||
521 | } else if (ext4_es_is_written(es)) { | ||
522 | if (retval != es->es_len) { | ||
523 | pr_warn("ES insert assertation failed for " | ||
524 | "inode: %lu retval %d != es_len %d\n", | ||
525 | inode->i_ino, retval, es->es_len); | ||
526 | return; | ||
527 | } | ||
528 | if (map.m_pblk != ext4_es_pblock(es)) { | ||
529 | pr_warn("ES insert assertation failed for " | ||
530 | "inode: %lu m_pblk %llu != " | ||
531 | "es_pblk %llu\n", | ||
532 | inode->i_ino, map.m_pblk, | ||
533 | ext4_es_pblock(es)); | ||
534 | return; | ||
535 | } | ||
536 | } else { | ||
537 | /* | ||
538 | * We don't need to check unwritten extent because | ||
539 | * indirect-based file doesn't have it. | ||
540 | */ | ||
541 | BUG_ON(1); | ||
542 | } | ||
543 | } else if (retval == 0) { | ||
544 | if (ext4_es_is_written(es)) { | ||
545 | pr_warn("ES insert assertation failed for inode: %lu " | ||
546 | "We can't find the block but we want to add " | ||
547 | "an written extent [%d/%d/%llu/%llx]\n", | ||
548 | inode->i_ino, es->es_lblk, es->es_len, | ||
549 | ext4_es_pblock(es), ext4_es_status(es)); | ||
550 | return; | ||
551 | } | ||
552 | } | ||
553 | } | ||
554 | |||
555 | static inline void ext4_es_insert_extent_check(struct inode *inode, | ||
556 | struct extent_status *es) | ||
557 | { | ||
558 | /* | ||
559 | * We don't need to worry about the race condition because | ||
560 | * caller takes i_data_sem locking. | ||
561 | */ | ||
562 | BUG_ON(!rwsem_is_locked(&EXT4_I(inode)->i_data_sem)); | ||
563 | if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) | ||
564 | ext4_es_insert_extent_ext_check(inode, es); | ||
565 | else | ||
566 | ext4_es_insert_extent_ind_check(inode, es); | ||
567 | } | ||
568 | #else | ||
569 | static inline void ext4_es_insert_extent_check(struct inode *inode, | ||
570 | struct extent_status *es) | ||
571 | { | ||
572 | } | ||
573 | #endif | ||
574 | |||
402 | static int __es_insert_extent(struct inode *inode, struct extent_status *newes) | 575 | static int __es_insert_extent(struct inode *inode, struct extent_status *newes) |
403 | { | 576 | { |
404 | struct ext4_es_tree *tree = &EXT4_I(inode)->i_es_tree; | 577 | struct ext4_es_tree *tree = &EXT4_I(inode)->i_es_tree; |
@@ -481,6 +654,8 @@ int ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk, | |||
481 | ext4_es_store_status(&newes, status); | 654 | ext4_es_store_status(&newes, status); |
482 | trace_ext4_es_insert_extent(inode, &newes); | 655 | trace_ext4_es_insert_extent(inode, &newes); |
483 | 656 | ||
657 | ext4_es_insert_extent_check(inode, &newes); | ||
658 | |||
484 | write_lock(&EXT4_I(inode)->i_es_lock); | 659 | write_lock(&EXT4_I(inode)->i_es_lock); |
485 | err = __es_remove_extent(inode, lblk, end); | 660 | err = __es_remove_extent(inode, lblk, end); |
486 | if (err != 0) | 661 | if (err != 0) |
diff --git a/fs/ext4/extents_status.h b/fs/ext4/extents_status.h index f190dfe969da..56140ad4150b 100644 --- a/fs/ext4/extents_status.h +++ b/fs/ext4/extents_status.h | |||
@@ -21,6 +21,12 @@ | |||
21 | #endif | 21 | #endif |
22 | 22 | ||
23 | /* | 23 | /* |
24 | * With ES_AGGRESSIVE_TEST defined, the result of es caching will be | ||
25 | * checked with old map_block's result. | ||
26 | */ | ||
27 | #define ES_AGGRESSIVE_TEST__ | ||
28 | |||
29 | /* | ||
24 | * These flags live in the high bits of extent_status.es_pblk | 30 | * These flags live in the high bits of extent_status.es_pblk |
25 | */ | 31 | */ |
26 | #define EXTENT_STATUS_WRITTEN (1ULL << 63) | 32 | #define EXTENT_STATUS_WRITTEN (1ULL << 63) |
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 95a0c62c5683..3186a43fa4b0 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c | |||
@@ -482,6 +482,58 @@ static pgoff_t ext4_num_dirty_pages(struct inode *inode, pgoff_t idx, | |||
482 | return num; | 482 | return num; |
483 | } | 483 | } |
484 | 484 | ||
485 | #ifdef ES_AGGRESSIVE_TEST | ||
486 | static void ext4_map_blocks_es_recheck(handle_t *handle, | ||
487 | struct inode *inode, | ||
488 | struct ext4_map_blocks *es_map, | ||
489 | struct ext4_map_blocks *map, | ||
490 | int flags) | ||
491 | { | ||
492 | int retval; | ||
493 | |||
494 | map->m_flags = 0; | ||
495 | /* | ||
496 | * There is a race window that the result is not the same. | ||
497 | * e.g. xfstests #223 when dioread_nolock enables. The reason | ||
498 | * is that we lookup a block mapping in extent status tree with | ||
499 | * out taking i_data_sem. So at the time the unwritten extent | ||
500 | * could be converted. | ||
501 | */ | ||
502 | if (!(flags & EXT4_GET_BLOCKS_NO_LOCK)) | ||
503 | down_read((&EXT4_I(inode)->i_data_sem)); | ||
504 | if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) { | ||
505 | retval = ext4_ext_map_blocks(handle, inode, map, flags & | ||
506 | EXT4_GET_BLOCKS_KEEP_SIZE); | ||
507 | } else { | ||
508 | retval = ext4_ind_map_blocks(handle, inode, map, flags & | ||
509 | EXT4_GET_BLOCKS_KEEP_SIZE); | ||
510 | } | ||
511 | if (!(flags & EXT4_GET_BLOCKS_NO_LOCK)) | ||
512 | up_read((&EXT4_I(inode)->i_data_sem)); | ||
513 | /* | ||
514 | * Clear EXT4_MAP_FROM_CLUSTER and EXT4_MAP_BOUNDARY flag | ||
515 | * because it shouldn't be marked in es_map->m_flags. | ||
516 | */ | ||
517 | map->m_flags &= ~(EXT4_MAP_FROM_CLUSTER | EXT4_MAP_BOUNDARY); | ||
518 | |||
519 | /* | ||
520 | * We don't check m_len because extent will be collpased in status | ||
521 | * tree. So the m_len might not equal. | ||
522 | */ | ||
523 | if (es_map->m_lblk != map->m_lblk || | ||
524 | es_map->m_flags != map->m_flags || | ||
525 | es_map->m_pblk != map->m_pblk) { | ||
526 | printk("ES cache assertation failed for inode: %lu " | ||
527 | "es_cached ex [%d/%d/%llu/%x] != " | ||
528 | "found ex [%d/%d/%llu/%x] retval %d flags %x\n", | ||
529 | inode->i_ino, es_map->m_lblk, es_map->m_len, | ||
530 | es_map->m_pblk, es_map->m_flags, map->m_lblk, | ||
531 | map->m_len, map->m_pblk, map->m_flags, | ||
532 | retval, flags); | ||
533 | } | ||
534 | } | ||
535 | #endif /* ES_AGGRESSIVE_TEST */ | ||
536 | |||
485 | /* | 537 | /* |
486 | * The ext4_map_blocks() function tries to look up the requested blocks, | 538 | * The ext4_map_blocks() function tries to look up the requested blocks, |
487 | * and returns if the blocks are already mapped. | 539 | * and returns if the blocks are already mapped. |
@@ -509,6 +561,11 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode, | |||
509 | { | 561 | { |
510 | struct extent_status es; | 562 | struct extent_status es; |
511 | int retval; | 563 | int retval; |
564 | #ifdef ES_AGGRESSIVE_TEST | ||
565 | struct ext4_map_blocks orig_map; | ||
566 | |||
567 | memcpy(&orig_map, map, sizeof(*map)); | ||
568 | #endif | ||
512 | 569 | ||
513 | map->m_flags = 0; | 570 | map->m_flags = 0; |
514 | ext_debug("ext4_map_blocks(): inode %lu, flag %d, max_blocks %u," | 571 | ext_debug("ext4_map_blocks(): inode %lu, flag %d, max_blocks %u," |
@@ -531,6 +588,10 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode, | |||
531 | } else { | 588 | } else { |
532 | BUG_ON(1); | 589 | BUG_ON(1); |
533 | } | 590 | } |
591 | #ifdef ES_AGGRESSIVE_TEST | ||
592 | ext4_map_blocks_es_recheck(handle, inode, map, | ||
593 | &orig_map, flags); | ||
594 | #endif | ||
534 | goto found; | 595 | goto found; |
535 | } | 596 | } |
536 | 597 | ||
@@ -551,6 +612,15 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode, | |||
551 | int ret; | 612 | int ret; |
552 | unsigned long long status; | 613 | unsigned long long status; |
553 | 614 | ||
615 | #ifdef ES_AGGRESSIVE_TEST | ||
616 | if (retval != map->m_len) { | ||
617 | printk("ES len assertation failed for inode: %lu " | ||
618 | "retval %d != map->m_len %d " | ||
619 | "in %s (lookup)\n", inode->i_ino, retval, | ||
620 | map->m_len, __func__); | ||
621 | } | ||
622 | #endif | ||
623 | |||
554 | status = map->m_flags & EXT4_MAP_UNWRITTEN ? | 624 | status = map->m_flags & EXT4_MAP_UNWRITTEN ? |
555 | EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; | 625 | EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; |
556 | if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) && | 626 | if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) && |
@@ -643,6 +713,15 @@ found: | |||
643 | int ret; | 713 | int ret; |
644 | unsigned long long status; | 714 | unsigned long long status; |
645 | 715 | ||
716 | #ifdef ES_AGGRESSIVE_TEST | ||
717 | if (retval != map->m_len) { | ||
718 | printk("ES len assertation failed for inode: %lu " | ||
719 | "retval %d != map->m_len %d " | ||
720 | "in %s (allocation)\n", inode->i_ino, retval, | ||
721 | map->m_len, __func__); | ||
722 | } | ||
723 | #endif | ||
724 | |||
646 | status = map->m_flags & EXT4_MAP_UNWRITTEN ? | 725 | status = map->m_flags & EXT4_MAP_UNWRITTEN ? |
647 | EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; | 726 | EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; |
648 | if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) && | 727 | if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) && |
@@ -1768,6 +1847,11 @@ static int ext4_da_map_blocks(struct inode *inode, sector_t iblock, | |||
1768 | struct extent_status es; | 1847 | struct extent_status es; |
1769 | int retval; | 1848 | int retval; |
1770 | sector_t invalid_block = ~((sector_t) 0xffff); | 1849 | sector_t invalid_block = ~((sector_t) 0xffff); |
1850 | #ifdef ES_AGGRESSIVE_TEST | ||
1851 | struct ext4_map_blocks orig_map; | ||
1852 | |||
1853 | memcpy(&orig_map, map, sizeof(*map)); | ||
1854 | #endif | ||
1771 | 1855 | ||
1772 | if (invalid_block < ext4_blocks_count(EXT4_SB(inode->i_sb)->s_es)) | 1856 | if (invalid_block < ext4_blocks_count(EXT4_SB(inode->i_sb)->s_es)) |
1773 | invalid_block = ~0; | 1857 | invalid_block = ~0; |
@@ -1809,6 +1893,9 @@ static int ext4_da_map_blocks(struct inode *inode, sector_t iblock, | |||
1809 | else | 1893 | else |
1810 | BUG_ON(1); | 1894 | BUG_ON(1); |
1811 | 1895 | ||
1896 | #ifdef ES_AGGRESSIVE_TEST | ||
1897 | ext4_map_blocks_es_recheck(NULL, inode, map, &orig_map, 0); | ||
1898 | #endif | ||
1812 | return retval; | 1899 | return retval; |
1813 | } | 1900 | } |
1814 | 1901 | ||
@@ -1873,6 +1960,15 @@ add_delayed: | |||
1873 | int ret; | 1960 | int ret; |
1874 | unsigned long long status; | 1961 | unsigned long long status; |
1875 | 1962 | ||
1963 | #ifdef ES_AGGRESSIVE_TEST | ||
1964 | if (retval != map->m_len) { | ||
1965 | printk("ES len assertation failed for inode: %lu " | ||
1966 | "retval %d != map->m_len %d " | ||
1967 | "in %s (lookup)\n", inode->i_ino, retval, | ||
1968 | map->m_len, __func__); | ||
1969 | } | ||
1970 | #endif | ||
1971 | |||
1876 | status = map->m_flags & EXT4_MAP_UNWRITTEN ? | 1972 | status = map->m_flags & EXT4_MAP_UNWRITTEN ? |
1877 | EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; | 1973 | EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; |
1878 | ret = ext4_es_insert_extent(inode, map->m_lblk, map->m_len, | 1974 | ret = ext4_es_insert_extent(inode, map->m_lblk, map->m_len, |