diff options
author | Akira Fujita <a-fujita@rs.jp.nec.com> | 2009-11-23 07:25:48 -0500 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2009-11-23 07:25:48 -0500 |
commit | f868a48d06f8886cb0367568a12367fa4f21ea0d (patch) | |
tree | 9b15c2fa850f6c34589f80f8e41fad095aa04c43 /fs/ext4/move_extent.c | |
parent | 503358ae01b70ce6909d19dd01287093f6b6271c (diff) |
ext4: fix the returned block count if EXT4_IOC_MOVE_EXT fails
If the EXT4_IOC_MOVE_EXT ioctl fails, the number of blocks that were
exchanged before the failure should be returned to the userspace
caller. Unfortunately, currently if the block size is not the same as
the page size, the returned block count that is returned is the
page-aligned block count instead of the actual block count. This
commit addresses this bug.
Signed-off-by: Akira Fujita <a-fujita@rs.jp.nec.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Diffstat (limited to 'fs/ext4/move_extent.c')
-rw-r--r-- | fs/ext4/move_extent.c | 139 |
1 files changed, 73 insertions, 66 deletions
diff --git a/fs/ext4/move_extent.c b/fs/ext4/move_extent.c index 25b6b1457360..83f8c9e47c60 100644 --- a/fs/ext4/move_extent.c +++ b/fs/ext4/move_extent.c | |||
@@ -661,6 +661,7 @@ mext_calc_swap_extents(struct ext4_extent *tmp_dext, | |||
661 | * @donor_inode: donor inode | 661 | * @donor_inode: donor inode |
662 | * @from: block offset of orig_inode | 662 | * @from: block offset of orig_inode |
663 | * @count: block count to be replaced | 663 | * @count: block count to be replaced |
664 | * @err: pointer to save return value | ||
664 | * | 665 | * |
665 | * Replace original inode extents and donor inode extents page by page. | 666 | * Replace original inode extents and donor inode extents page by page. |
666 | * We implement this replacement in the following three steps: | 667 | * We implement this replacement in the following three steps: |
@@ -671,19 +672,18 @@ mext_calc_swap_extents(struct ext4_extent *tmp_dext, | |||
671 | * 3. Change the block information of donor inode to point at the saved | 672 | * 3. Change the block information of donor inode to point at the saved |
672 | * original inode blocks in the dummy extents. | 673 | * original inode blocks in the dummy extents. |
673 | * | 674 | * |
674 | * Return 0 on success, or a negative error value on failure. | 675 | * Return replaced block count. |
675 | */ | 676 | */ |
676 | static int | 677 | static int |
677 | mext_replace_branches(handle_t *handle, struct inode *orig_inode, | 678 | mext_replace_branches(handle_t *handle, struct inode *orig_inode, |
678 | struct inode *donor_inode, ext4_lblk_t from, | 679 | struct inode *donor_inode, ext4_lblk_t from, |
679 | ext4_lblk_t count) | 680 | ext4_lblk_t count, int *err) |
680 | { | 681 | { |
681 | struct ext4_ext_path *orig_path = NULL; | 682 | struct ext4_ext_path *orig_path = NULL; |
682 | struct ext4_ext_path *donor_path = NULL; | 683 | struct ext4_ext_path *donor_path = NULL; |
683 | struct ext4_extent *oext, *dext; | 684 | struct ext4_extent *oext, *dext; |
684 | struct ext4_extent tmp_dext, tmp_oext; | 685 | struct ext4_extent tmp_dext, tmp_oext; |
685 | ext4_lblk_t orig_off = from, donor_off = from; | 686 | ext4_lblk_t orig_off = from, donor_off = from; |
686 | int err = 0; | ||
687 | int depth; | 687 | int depth; |
688 | int replaced_count = 0; | 688 | int replaced_count = 0; |
689 | int dext_alen; | 689 | int dext_alen; |
@@ -691,13 +691,13 @@ mext_replace_branches(handle_t *handle, struct inode *orig_inode, | |||
691 | mext_double_down_write(orig_inode, donor_inode); | 691 | mext_double_down_write(orig_inode, donor_inode); |
692 | 692 | ||
693 | /* Get the original extent for the block "orig_off" */ | 693 | /* Get the original extent for the block "orig_off" */ |
694 | err = get_ext_path(orig_inode, orig_off, &orig_path); | 694 | *err = get_ext_path(orig_inode, orig_off, &orig_path); |
695 | if (err) | 695 | if (*err) |
696 | goto out; | 696 | goto out; |
697 | 697 | ||
698 | /* Get the donor extent for the head */ | 698 | /* Get the donor extent for the head */ |
699 | err = get_ext_path(donor_inode, donor_off, &donor_path); | 699 | *err = get_ext_path(donor_inode, donor_off, &donor_path); |
700 | if (err) | 700 | if (*err) |
701 | goto out; | 701 | goto out; |
702 | depth = ext_depth(orig_inode); | 702 | depth = ext_depth(orig_inode); |
703 | oext = orig_path[depth].p_ext; | 703 | oext = orig_path[depth].p_ext; |
@@ -707,9 +707,9 @@ mext_replace_branches(handle_t *handle, struct inode *orig_inode, | |||
707 | dext = donor_path[depth].p_ext; | 707 | dext = donor_path[depth].p_ext; |
708 | tmp_dext = *dext; | 708 | tmp_dext = *dext; |
709 | 709 | ||
710 | err = mext_calc_swap_extents(&tmp_dext, &tmp_oext, orig_off, | 710 | *err = mext_calc_swap_extents(&tmp_dext, &tmp_oext, orig_off, |
711 | donor_off, count); | 711 | donor_off, count); |
712 | if (err) | 712 | if (*err) |
713 | goto out; | 713 | goto out; |
714 | 714 | ||
715 | /* Loop for the donor extents */ | 715 | /* Loop for the donor extents */ |
@@ -718,7 +718,7 @@ mext_replace_branches(handle_t *handle, struct inode *orig_inode, | |||
718 | if (!dext) { | 718 | if (!dext) { |
719 | ext4_error(donor_inode->i_sb, __func__, | 719 | ext4_error(donor_inode->i_sb, __func__, |
720 | "The extent for donor must be found"); | 720 | "The extent for donor must be found"); |
721 | err = -EIO; | 721 | *err = -EIO; |
722 | goto out; | 722 | goto out; |
723 | } else if (donor_off != le32_to_cpu(tmp_dext.ee_block)) { | 723 | } else if (donor_off != le32_to_cpu(tmp_dext.ee_block)) { |
724 | ext4_error(donor_inode->i_sb, __func__, | 724 | ext4_error(donor_inode->i_sb, __func__, |
@@ -726,20 +726,20 @@ mext_replace_branches(handle_t *handle, struct inode *orig_inode, | |||
726 | "extent(%u) should be equal", | 726 | "extent(%u) should be equal", |
727 | donor_off, | 727 | donor_off, |
728 | le32_to_cpu(tmp_dext.ee_block)); | 728 | le32_to_cpu(tmp_dext.ee_block)); |
729 | err = -EIO; | 729 | *err = -EIO; |
730 | goto out; | 730 | goto out; |
731 | } | 731 | } |
732 | 732 | ||
733 | /* Set donor extent to orig extent */ | 733 | /* Set donor extent to orig extent */ |
734 | err = mext_leaf_block(handle, orig_inode, | 734 | *err = mext_leaf_block(handle, orig_inode, |
735 | orig_path, &tmp_dext, &orig_off); | 735 | orig_path, &tmp_dext, &orig_off); |
736 | if (err < 0) | 736 | if (*err) |
737 | goto out; | 737 | goto out; |
738 | 738 | ||
739 | /* Set orig extent to donor extent */ | 739 | /* Set orig extent to donor extent */ |
740 | err = mext_leaf_block(handle, donor_inode, | 740 | *err = mext_leaf_block(handle, donor_inode, |
741 | donor_path, &tmp_oext, &donor_off); | 741 | donor_path, &tmp_oext, &donor_off); |
742 | if (err < 0) | 742 | if (*err) |
743 | goto out; | 743 | goto out; |
744 | 744 | ||
745 | dext_alen = ext4_ext_get_actual_len(&tmp_dext); | 745 | dext_alen = ext4_ext_get_actual_len(&tmp_dext); |
@@ -753,35 +753,25 @@ mext_replace_branches(handle_t *handle, struct inode *orig_inode, | |||
753 | 753 | ||
754 | if (orig_path) | 754 | if (orig_path) |
755 | ext4_ext_drop_refs(orig_path); | 755 | ext4_ext_drop_refs(orig_path); |
756 | err = get_ext_path(orig_inode, orig_off, &orig_path); | 756 | *err = get_ext_path(orig_inode, orig_off, &orig_path); |
757 | if (err) | 757 | if (*err) |
758 | goto out; | 758 | goto out; |
759 | depth = ext_depth(orig_inode); | 759 | depth = ext_depth(orig_inode); |
760 | oext = orig_path[depth].p_ext; | 760 | oext = orig_path[depth].p_ext; |
761 | if (le32_to_cpu(oext->ee_block) + | ||
762 | ext4_ext_get_actual_len(oext) <= orig_off) { | ||
763 | err = 0; | ||
764 | goto out; | ||
765 | } | ||
766 | tmp_oext = *oext; | 761 | tmp_oext = *oext; |
767 | 762 | ||
768 | if (donor_path) | 763 | if (donor_path) |
769 | ext4_ext_drop_refs(donor_path); | 764 | ext4_ext_drop_refs(donor_path); |
770 | err = get_ext_path(donor_inode, donor_off, &donor_path); | 765 | *err = get_ext_path(donor_inode, donor_off, &donor_path); |
771 | if (err) | 766 | if (*err) |
772 | goto out; | 767 | goto out; |
773 | depth = ext_depth(donor_inode); | 768 | depth = ext_depth(donor_inode); |
774 | dext = donor_path[depth].p_ext; | 769 | dext = donor_path[depth].p_ext; |
775 | if (le32_to_cpu(dext->ee_block) + | ||
776 | ext4_ext_get_actual_len(dext) <= donor_off) { | ||
777 | err = 0; | ||
778 | goto out; | ||
779 | } | ||
780 | tmp_dext = *dext; | 770 | tmp_dext = *dext; |
781 | 771 | ||
782 | err = mext_calc_swap_extents(&tmp_dext, &tmp_oext, orig_off, | 772 | *err = mext_calc_swap_extents(&tmp_dext, &tmp_oext, orig_off, |
783 | donor_off, count - replaced_count); | 773 | donor_off, count - replaced_count); |
784 | if (err) | 774 | if (*err) |
785 | goto out; | 775 | goto out; |
786 | } | 776 | } |
787 | 777 | ||
@@ -796,7 +786,7 @@ out: | |||
796 | } | 786 | } |
797 | 787 | ||
798 | mext_double_up_write(orig_inode, donor_inode); | 788 | mext_double_up_write(orig_inode, donor_inode); |
799 | return err; | 789 | return replaced_count; |
800 | } | 790 | } |
801 | 791 | ||
802 | /** | 792 | /** |
@@ -808,16 +798,17 @@ out: | |||
808 | * @data_offset_in_page: block index where data swapping starts | 798 | * @data_offset_in_page: block index where data swapping starts |
809 | * @block_len_in_page: the number of blocks to be swapped | 799 | * @block_len_in_page: the number of blocks to be swapped |
810 | * @uninit: orig extent is uninitialized or not | 800 | * @uninit: orig extent is uninitialized or not |
801 | * @err: pointer to save return value | ||
811 | * | 802 | * |
812 | * Save the data in original inode blocks and replace original inode extents | 803 | * Save the data in original inode blocks and replace original inode extents |
813 | * with donor inode extents by calling mext_replace_branches(). | 804 | * with donor inode extents by calling mext_replace_branches(). |
814 | * Finally, write out the saved data in new original inode blocks. Return 0 | 805 | * Finally, write out the saved data in new original inode blocks. Return |
815 | * on success, or a negative error value on failure. | 806 | * replaced block count. |
816 | */ | 807 | */ |
817 | static int | 808 | static int |
818 | move_extent_per_page(struct file *o_filp, struct inode *donor_inode, | 809 | move_extent_per_page(struct file *o_filp, struct inode *donor_inode, |
819 | pgoff_t orig_page_offset, int data_offset_in_page, | 810 | pgoff_t orig_page_offset, int data_offset_in_page, |
820 | int block_len_in_page, int uninit) | 811 | int block_len_in_page, int uninit, int *err) |
821 | { | 812 | { |
822 | struct inode *orig_inode = o_filp->f_dentry->d_inode; | 813 | struct inode *orig_inode = o_filp->f_dentry->d_inode; |
823 | struct address_space *mapping = orig_inode->i_mapping; | 814 | struct address_space *mapping = orig_inode->i_mapping; |
@@ -829,9 +820,11 @@ move_extent_per_page(struct file *o_filp, struct inode *donor_inode, | |||
829 | long long offs = orig_page_offset << PAGE_CACHE_SHIFT; | 820 | long long offs = orig_page_offset << PAGE_CACHE_SHIFT; |
830 | unsigned long blocksize = orig_inode->i_sb->s_blocksize; | 821 | unsigned long blocksize = orig_inode->i_sb->s_blocksize; |
831 | unsigned int w_flags = 0; | 822 | unsigned int w_flags = 0; |
832 | unsigned int tmp_data_len, data_len; | 823 | unsigned int tmp_data_size, data_size, replaced_size; |
833 | void *fsdata; | 824 | void *fsdata; |
834 | int ret, i, jblocks; | 825 | int i, jblocks; |
826 | int err2 = 0; | ||
827 | int replaced_count = 0; | ||
835 | int blocks_per_page = PAGE_CACHE_SIZE >> orig_inode->i_blkbits; | 828 | int blocks_per_page = PAGE_CACHE_SIZE >> orig_inode->i_blkbits; |
836 | 829 | ||
837 | /* | 830 | /* |
@@ -841,8 +834,8 @@ move_extent_per_page(struct file *o_filp, struct inode *donor_inode, | |||
841 | jblocks = ext4_writepage_trans_blocks(orig_inode) * 2; | 834 | jblocks = ext4_writepage_trans_blocks(orig_inode) * 2; |
842 | handle = ext4_journal_start(orig_inode, jblocks); | 835 | handle = ext4_journal_start(orig_inode, jblocks); |
843 | if (IS_ERR(handle)) { | 836 | if (IS_ERR(handle)) { |
844 | ret = PTR_ERR(handle); | 837 | *err = PTR_ERR(handle); |
845 | return ret; | 838 | return 0; |
846 | } | 839 | } |
847 | 840 | ||
848 | if (segment_eq(get_fs(), KERNEL_DS)) | 841 | if (segment_eq(get_fs(), KERNEL_DS)) |
@@ -858,9 +851,9 @@ move_extent_per_page(struct file *o_filp, struct inode *donor_inode, | |||
858 | * Just swap data blocks between orig and donor. | 851 | * Just swap data blocks between orig and donor. |
859 | */ | 852 | */ |
860 | if (uninit) { | 853 | if (uninit) { |
861 | ret = mext_replace_branches(handle, orig_inode, | 854 | replaced_count = mext_replace_branches(handle, orig_inode, |
862 | donor_inode, orig_blk_offset, | 855 | donor_inode, orig_blk_offset, |
863 | block_len_in_page); | 856 | block_len_in_page, err); |
864 | 857 | ||
865 | /* Clear the inode cache not to refer to the old data */ | 858 | /* Clear the inode cache not to refer to the old data */ |
866 | ext4_ext_invalidate_cache(orig_inode); | 859 | ext4_ext_invalidate_cache(orig_inode); |
@@ -870,27 +863,28 @@ move_extent_per_page(struct file *o_filp, struct inode *donor_inode, | |||
870 | 863 | ||
871 | offs = (long long)orig_blk_offset << orig_inode->i_blkbits; | 864 | offs = (long long)orig_blk_offset << orig_inode->i_blkbits; |
872 | 865 | ||
873 | /* Calculate data_len */ | 866 | /* Calculate data_size */ |
874 | if ((orig_blk_offset + block_len_in_page - 1) == | 867 | if ((orig_blk_offset + block_len_in_page - 1) == |
875 | ((orig_inode->i_size - 1) >> orig_inode->i_blkbits)) { | 868 | ((orig_inode->i_size - 1) >> orig_inode->i_blkbits)) { |
876 | /* Replace the last block */ | 869 | /* Replace the last block */ |
877 | tmp_data_len = orig_inode->i_size & (blocksize - 1); | 870 | tmp_data_size = orig_inode->i_size & (blocksize - 1); |
878 | /* | 871 | /* |
879 | * If data_len equal zero, it shows data_len is multiples of | 872 | * If data_size equal zero, it shows data_size is multiples of |
880 | * blocksize. So we set appropriate value. | 873 | * blocksize. So we set appropriate value. |
881 | */ | 874 | */ |
882 | if (tmp_data_len == 0) | 875 | if (tmp_data_size == 0) |
883 | tmp_data_len = blocksize; | 876 | tmp_data_size = blocksize; |
884 | 877 | ||
885 | data_len = tmp_data_len + | 878 | data_size = tmp_data_size + |
886 | ((block_len_in_page - 1) << orig_inode->i_blkbits); | 879 | ((block_len_in_page - 1) << orig_inode->i_blkbits); |
887 | } else { | 880 | } else |
888 | data_len = block_len_in_page << orig_inode->i_blkbits; | 881 | data_size = block_len_in_page << orig_inode->i_blkbits; |
889 | } | 882 | |
883 | replaced_size = data_size; | ||
890 | 884 | ||
891 | ret = a_ops->write_begin(o_filp, mapping, offs, data_len, w_flags, | 885 | *err = a_ops->write_begin(o_filp, mapping, offs, data_size, w_flags, |
892 | &page, &fsdata); | 886 | &page, &fsdata); |
893 | if (unlikely(ret < 0)) | 887 | if (unlikely(*err < 0)) |
894 | goto out; | 888 | goto out; |
895 | 889 | ||
896 | if (!PageUptodate(page)) { | 890 | if (!PageUptodate(page)) { |
@@ -911,10 +905,17 @@ move_extent_per_page(struct file *o_filp, struct inode *donor_inode, | |||
911 | /* Release old bh and drop refs */ | 905 | /* Release old bh and drop refs */ |
912 | try_to_release_page(page, 0); | 906 | try_to_release_page(page, 0); |
913 | 907 | ||
914 | ret = mext_replace_branches(handle, orig_inode, donor_inode, | 908 | replaced_count = mext_replace_branches(handle, orig_inode, donor_inode, |
915 | orig_blk_offset, block_len_in_page); | 909 | orig_blk_offset, block_len_in_page, |
916 | if (ret < 0) | 910 | &err2); |
917 | goto out; | 911 | if (err2) { |
912 | if (replaced_count) { | ||
913 | block_len_in_page = replaced_count; | ||
914 | replaced_size = | ||
915 | block_len_in_page << orig_inode->i_blkbits; | ||
916 | } else | ||
917 | goto out; | ||
918 | } | ||
918 | 919 | ||
919 | /* Clear the inode cache not to refer to the old data */ | 920 | /* Clear the inode cache not to refer to the old data */ |
920 | ext4_ext_invalidate_cache(orig_inode); | 921 | ext4_ext_invalidate_cache(orig_inode); |
@@ -928,16 +929,16 @@ move_extent_per_page(struct file *o_filp, struct inode *donor_inode, | |||
928 | bh = bh->b_this_page; | 929 | bh = bh->b_this_page; |
929 | 930 | ||
930 | for (i = 0; i < block_len_in_page; i++) { | 931 | for (i = 0; i < block_len_in_page; i++) { |
931 | ret = ext4_get_block(orig_inode, | 932 | *err = ext4_get_block(orig_inode, |
932 | (sector_t)(orig_blk_offset + i), bh, 0); | 933 | (sector_t)(orig_blk_offset + i), bh, 0); |
933 | if (ret < 0) | 934 | if (*err < 0) |
934 | goto out; | 935 | goto out; |
935 | 936 | ||
936 | if (bh->b_this_page != NULL) | 937 | if (bh->b_this_page != NULL) |
937 | bh = bh->b_this_page; | 938 | bh = bh->b_this_page; |
938 | } | 939 | } |
939 | 940 | ||
940 | ret = a_ops->write_end(o_filp, mapping, offs, data_len, data_len, | 941 | *err = a_ops->write_end(o_filp, mapping, offs, data_size, replaced_size, |
941 | page, fsdata); | 942 | page, fsdata); |
942 | page = NULL; | 943 | page = NULL; |
943 | 944 | ||
@@ -951,7 +952,10 @@ out: | |||
951 | out2: | 952 | out2: |
952 | ext4_journal_stop(handle); | 953 | ext4_journal_stop(handle); |
953 | 954 | ||
954 | return ret < 0 ? ret : 0; | 955 | if (err2) |
956 | *err = err2; | ||
957 | |||
958 | return replaced_count; | ||
955 | } | 959 | } |
956 | 960 | ||
957 | /** | 961 | /** |
@@ -1367,15 +1371,17 @@ ext4_move_extents(struct file *o_filp, struct file *d_filp, | |||
1367 | while (orig_page_offset <= seq_end_page) { | 1371 | while (orig_page_offset <= seq_end_page) { |
1368 | 1372 | ||
1369 | /* Swap original branches with new branches */ | 1373 | /* Swap original branches with new branches */ |
1370 | ret1 = move_extent_per_page(o_filp, donor_inode, | 1374 | block_len_in_page = move_extent_per_page( |
1375 | o_filp, donor_inode, | ||
1371 | orig_page_offset, | 1376 | orig_page_offset, |
1372 | data_offset_in_page, | 1377 | data_offset_in_page, |
1373 | block_len_in_page, uninit); | 1378 | block_len_in_page, uninit, |
1374 | if (ret1 < 0) | 1379 | &ret1); |
1375 | goto out; | 1380 | |
1376 | orig_page_offset++; | ||
1377 | /* Count how many blocks we have exchanged */ | 1381 | /* Count how many blocks we have exchanged */ |
1378 | *moved_len += block_len_in_page; | 1382 | *moved_len += block_len_in_page; |
1383 | if (ret1 < 0) | ||
1384 | goto out; | ||
1379 | if (*moved_len > len) { | 1385 | if (*moved_len > len) { |
1380 | ext4_error(orig_inode->i_sb, __func__, | 1386 | ext4_error(orig_inode->i_sb, __func__, |
1381 | "We replaced blocks too much! " | 1387 | "We replaced blocks too much! " |
@@ -1385,6 +1391,7 @@ ext4_move_extents(struct file *o_filp, struct file *d_filp, | |||
1385 | goto out; | 1391 | goto out; |
1386 | } | 1392 | } |
1387 | 1393 | ||
1394 | orig_page_offset++; | ||
1388 | data_offset_in_page = 0; | 1395 | data_offset_in_page = 0; |
1389 | rest_blocks -= block_len_in_page; | 1396 | rest_blocks -= block_len_in_page; |
1390 | if (rest_blocks > blocks_per_page) | 1397 | if (rest_blocks > blocks_per_page) |