diff options
author | Theodore Ts'o <tytso@mit.edu> | 2019-08-11 16:32:41 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2019-08-11 16:32:41 -0400 |
commit | bb5835edcdf8bf78bbe51cff13e332c439bc0567 (patch) | |
tree | 74e9498c0cb0f42a3158d0a892181316e5a8a243 | |
parent | 1ad3ea6e0a694b0486eb2cbe60378ad0fbf23642 (diff) |
ext4: add new ioctl EXT4_IOC_GET_ES_CACHE
For debugging reasons, it's useful to know the contents of the extent
cache. Since the extent cache contains much of what is in the fiemap
ioctl, use an fiemap-style interface to return this information.
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
-rw-r--r-- | fs/ext4/ext4.h | 10 | ||||
-rw-r--r-- | fs/ext4/extents.c | 94 | ||||
-rw-r--r-- | fs/ext4/extents_status.c | 10 | ||||
-rw-r--r-- | fs/ext4/extents_status.h | 1 | ||||
-rw-r--r-- | fs/ext4/inode.c | 6 | ||||
-rw-r--r-- | fs/ext4/ioctl.c | 72 |
6 files changed, 182 insertions, 11 deletions
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index ee296797bcd2..e2d8ad27f4d1 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h | |||
@@ -652,6 +652,7 @@ enum { | |||
652 | /* ioctl codes 19--39 are reserved for fscrypt */ | 652 | /* ioctl codes 19--39 are reserved for fscrypt */ |
653 | #define EXT4_IOC_CLEAR_ES_CACHE _IO('f', 40) | 653 | #define EXT4_IOC_CLEAR_ES_CACHE _IO('f', 40) |
654 | #define EXT4_IOC_GETSTATE _IOW('f', 41, __u32) | 654 | #define EXT4_IOC_GETSTATE _IOW('f', 41, __u32) |
655 | #define EXT4_IOC_GET_ES_CACHE _IOWR('f', 42, struct fiemap) | ||
655 | 656 | ||
656 | #define EXT4_IOC_FSGETXATTR FS_IOC_FSGETXATTR | 657 | #define EXT4_IOC_FSGETXATTR FS_IOC_FSGETXATTR |
657 | #define EXT4_IOC_FSSETXATTR FS_IOC_FSSETXATTR | 658 | #define EXT4_IOC_FSSETXATTR FS_IOC_FSSETXATTR |
@@ -692,6 +693,12 @@ enum { | |||
692 | #define EXT4_IOC32_SETVERSION_OLD FS_IOC32_SETVERSION | 693 | #define EXT4_IOC32_SETVERSION_OLD FS_IOC32_SETVERSION |
693 | #endif | 694 | #endif |
694 | 695 | ||
696 | /* | ||
697 | * Returned by EXT4_IOC_GET_ES_CACHE as an additional possible flag. | ||
698 | * It indicates that the entry in extent status cache is for a hole. | ||
699 | */ | ||
700 | #define EXT4_FIEMAP_EXTENT_HOLE 0x08000000 | ||
701 | |||
695 | /* Max physical block we can address w/o extents */ | 702 | /* Max physical block we can address w/o extents */ |
696 | #define EXT4_MAX_BLOCK_FILE_PHYS 0xFFFFFFFF | 703 | #define EXT4_MAX_BLOCK_FILE_PHYS 0xFFFFFFFF |
697 | 704 | ||
@@ -3258,6 +3265,9 @@ extern int ext4_ext_check_inode(struct inode *inode); | |||
3258 | extern ext4_lblk_t ext4_ext_next_allocated_block(struct ext4_ext_path *path); | 3265 | extern ext4_lblk_t ext4_ext_next_allocated_block(struct ext4_ext_path *path); |
3259 | extern int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, | 3266 | extern int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, |
3260 | __u64 start, __u64 len); | 3267 | __u64 start, __u64 len); |
3268 | extern int ext4_get_es_cache(struct inode *inode, | ||
3269 | struct fiemap_extent_info *fieinfo, | ||
3270 | __u64 start, __u64 len); | ||
3261 | extern int ext4_ext_precache(struct inode *inode); | 3271 | extern int ext4_ext_precache(struct inode *inode); |
3262 | extern int ext4_collapse_range(struct inode *inode, loff_t offset, loff_t len); | 3272 | extern int ext4_collapse_range(struct inode *inode, loff_t offset, loff_t len); |
3263 | extern int ext4_insert_range(struct inode *inode, loff_t offset, loff_t len); | 3273 | extern int ext4_insert_range(struct inode *inode, loff_t offset, loff_t len); |
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 92266a2da7d6..0620d495fd8a 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c | |||
@@ -2315,6 +2315,52 @@ static int ext4_fill_fiemap_extents(struct inode *inode, | |||
2315 | return err; | 2315 | return err; |
2316 | } | 2316 | } |
2317 | 2317 | ||
2318 | static int ext4_fill_es_cache_info(struct inode *inode, | ||
2319 | ext4_lblk_t block, ext4_lblk_t num, | ||
2320 | struct fiemap_extent_info *fieinfo) | ||
2321 | { | ||
2322 | ext4_lblk_t next, end = block + num - 1; | ||
2323 | struct extent_status es; | ||
2324 | unsigned char blksize_bits = inode->i_sb->s_blocksize_bits; | ||
2325 | unsigned int flags; | ||
2326 | int err; | ||
2327 | |||
2328 | while (block <= end) { | ||
2329 | next = 0; | ||
2330 | flags = 0; | ||
2331 | if (!ext4_es_lookup_extent(inode, block, &next, &es)) | ||
2332 | break; | ||
2333 | if (ext4_es_is_unwritten(&es)) | ||
2334 | flags |= FIEMAP_EXTENT_UNWRITTEN; | ||
2335 | if (ext4_es_is_delayed(&es)) | ||
2336 | flags |= (FIEMAP_EXTENT_DELALLOC | | ||
2337 | FIEMAP_EXTENT_UNKNOWN); | ||
2338 | if (ext4_es_is_hole(&es)) | ||
2339 | flags |= EXT4_FIEMAP_EXTENT_HOLE; | ||
2340 | if (next == 0) | ||
2341 | flags |= FIEMAP_EXTENT_LAST; | ||
2342 | if (flags & (FIEMAP_EXTENT_DELALLOC| | ||
2343 | EXT4_FIEMAP_EXTENT_HOLE)) | ||
2344 | es.es_pblk = 0; | ||
2345 | else | ||
2346 | es.es_pblk = ext4_es_pblock(&es); | ||
2347 | err = fiemap_fill_next_extent(fieinfo, | ||
2348 | (__u64)es.es_lblk << blksize_bits, | ||
2349 | (__u64)es.es_pblk << blksize_bits, | ||
2350 | (__u64)es.es_len << blksize_bits, | ||
2351 | flags); | ||
2352 | if (next == 0) | ||
2353 | break; | ||
2354 | block = next; | ||
2355 | if (err < 0) | ||
2356 | return err; | ||
2357 | if (err == 1) | ||
2358 | return 0; | ||
2359 | } | ||
2360 | return 0; | ||
2361 | } | ||
2362 | |||
2363 | |||
2318 | /* | 2364 | /* |
2319 | * ext4_ext_determine_hole - determine hole around given block | 2365 | * ext4_ext_determine_hole - determine hole around given block |
2320 | * @inode: inode we lookup in | 2366 | * @inode: inode we lookup in |
@@ -5017,8 +5063,6 @@ static int ext4_find_delayed_extent(struct inode *inode, | |||
5017 | 5063 | ||
5018 | return next_del; | 5064 | return next_del; |
5019 | } | 5065 | } |
5020 | /* fiemap flags we can handle specified here */ | ||
5021 | #define EXT4_FIEMAP_FLAGS (FIEMAP_FLAG_SYNC|FIEMAP_FLAG_XATTR) | ||
5022 | 5066 | ||
5023 | static int ext4_xattr_fiemap(struct inode *inode, | 5067 | static int ext4_xattr_fiemap(struct inode *inode, |
5024 | struct fiemap_extent_info *fieinfo) | 5068 | struct fiemap_extent_info *fieinfo) |
@@ -5055,10 +5099,16 @@ static int ext4_xattr_fiemap(struct inode *inode, | |||
5055 | return (error < 0 ? error : 0); | 5099 | return (error < 0 ? error : 0); |
5056 | } | 5100 | } |
5057 | 5101 | ||
5058 | int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, | 5102 | static int _ext4_fiemap(struct inode *inode, |
5059 | __u64 start, __u64 len) | 5103 | struct fiemap_extent_info *fieinfo, |
5104 | __u64 start, __u64 len, | ||
5105 | int (*fill)(struct inode *, ext4_lblk_t, | ||
5106 | ext4_lblk_t, | ||
5107 | struct fiemap_extent_info *)) | ||
5060 | { | 5108 | { |
5061 | ext4_lblk_t start_blk; | 5109 | ext4_lblk_t start_blk; |
5110 | u32 ext4_fiemap_flags = FIEMAP_FLAG_SYNC|FIEMAP_FLAG_XATTR; | ||
5111 | |||
5062 | int error = 0; | 5112 | int error = 0; |
5063 | 5113 | ||
5064 | if (ext4_has_inline_data(inode)) { | 5114 | if (ext4_has_inline_data(inode)) { |
@@ -5075,14 +5125,18 @@ int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, | |||
5075 | error = ext4_ext_precache(inode); | 5125 | error = ext4_ext_precache(inode); |
5076 | if (error) | 5126 | if (error) |
5077 | return error; | 5127 | return error; |
5128 | fieinfo->fi_flags &= ~FIEMAP_FLAG_CACHE; | ||
5078 | } | 5129 | } |
5079 | 5130 | ||
5080 | /* fallback to generic here if not in extents fmt */ | 5131 | /* fallback to generic here if not in extents fmt */ |
5081 | if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) | 5132 | if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) && |
5133 | fill == ext4_fill_fiemap_extents) | ||
5082 | return generic_block_fiemap(inode, fieinfo, start, len, | 5134 | return generic_block_fiemap(inode, fieinfo, start, len, |
5083 | ext4_get_block); | 5135 | ext4_get_block); |
5084 | 5136 | ||
5085 | if (fiemap_check_flags(fieinfo, EXT4_FIEMAP_FLAGS)) | 5137 | if (fill == ext4_fill_es_cache_info) |
5138 | ext4_fiemap_flags &= FIEMAP_FLAG_XATTR; | ||
5139 | if (fiemap_check_flags(fieinfo, ext4_fiemap_flags)) | ||
5086 | return -EBADR; | 5140 | return -EBADR; |
5087 | 5141 | ||
5088 | if (fieinfo->fi_flags & FIEMAP_FLAG_XATTR) { | 5142 | if (fieinfo->fi_flags & FIEMAP_FLAG_XATTR) { |
@@ -5101,12 +5155,36 @@ int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, | |||
5101 | * Walk the extent tree gathering extent information | 5155 | * Walk the extent tree gathering extent information |
5102 | * and pushing extents back to the user. | 5156 | * and pushing extents back to the user. |
5103 | */ | 5157 | */ |
5104 | error = ext4_fill_fiemap_extents(inode, start_blk, | 5158 | error = fill(inode, start_blk, len_blks, fieinfo); |
5105 | len_blks, fieinfo); | ||
5106 | } | 5159 | } |
5107 | return error; | 5160 | return error; |
5108 | } | 5161 | } |
5109 | 5162 | ||
5163 | int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, | ||
5164 | __u64 start, __u64 len) | ||
5165 | { | ||
5166 | return _ext4_fiemap(inode, fieinfo, start, len, | ||
5167 | ext4_fill_fiemap_extents); | ||
5168 | } | ||
5169 | |||
5170 | int ext4_get_es_cache(struct inode *inode, struct fiemap_extent_info *fieinfo, | ||
5171 | __u64 start, __u64 len) | ||
5172 | { | ||
5173 | if (ext4_has_inline_data(inode)) { | ||
5174 | int has_inline; | ||
5175 | |||
5176 | down_read(&EXT4_I(inode)->xattr_sem); | ||
5177 | has_inline = ext4_has_inline_data(inode); | ||
5178 | up_read(&EXT4_I(inode)->xattr_sem); | ||
5179 | if (has_inline) | ||
5180 | return 0; | ||
5181 | } | ||
5182 | |||
5183 | return _ext4_fiemap(inode, fieinfo, start, len, | ||
5184 | ext4_fill_es_cache_info); | ||
5185 | } | ||
5186 | |||
5187 | |||
5110 | /* | 5188 | /* |
5111 | * ext4_access_path: | 5189 | * ext4_access_path: |
5112 | * Function to access the path buffer for marking it dirty. | 5190 | * Function to access the path buffer for marking it dirty. |
diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c index 02cc8eb3eb0e..a959adc59bcd 100644 --- a/fs/ext4/extents_status.c +++ b/fs/ext4/extents_status.c | |||
@@ -899,6 +899,7 @@ void ext4_es_cache_extent(struct inode *inode, ext4_lblk_t lblk, | |||
899 | * Return: 1 on found, 0 on not | 899 | * Return: 1 on found, 0 on not |
900 | */ | 900 | */ |
901 | int ext4_es_lookup_extent(struct inode *inode, ext4_lblk_t lblk, | 901 | int ext4_es_lookup_extent(struct inode *inode, ext4_lblk_t lblk, |
902 | ext4_lblk_t *next_lblk, | ||
902 | struct extent_status *es) | 903 | struct extent_status *es) |
903 | { | 904 | { |
904 | struct ext4_es_tree *tree; | 905 | struct ext4_es_tree *tree; |
@@ -948,6 +949,15 @@ out: | |||
948 | if (!ext4_es_is_referenced(es1)) | 949 | if (!ext4_es_is_referenced(es1)) |
949 | ext4_es_set_referenced(es1); | 950 | ext4_es_set_referenced(es1); |
950 | stats->es_stats_cache_hits++; | 951 | stats->es_stats_cache_hits++; |
952 | if (next_lblk) { | ||
953 | node = rb_next(&es1->rb_node); | ||
954 | if (node) { | ||
955 | es1 = rb_entry(node, struct extent_status, | ||
956 | rb_node); | ||
957 | *next_lblk = es1->es_lblk; | ||
958 | } else | ||
959 | *next_lblk = 0; | ||
960 | } | ||
951 | } else { | 961 | } else { |
952 | stats->es_stats_cache_misses++; | 962 | stats->es_stats_cache_misses++; |
953 | } | 963 | } |
diff --git a/fs/ext4/extents_status.h b/fs/ext4/extents_status.h index e16785f431e7..eb56a1289031 100644 --- a/fs/ext4/extents_status.h +++ b/fs/ext4/extents_status.h | |||
@@ -140,6 +140,7 @@ extern void ext4_es_find_extent_range(struct inode *inode, | |||
140 | ext4_lblk_t lblk, ext4_lblk_t end, | 140 | ext4_lblk_t lblk, ext4_lblk_t end, |
141 | struct extent_status *es); | 141 | struct extent_status *es); |
142 | extern int ext4_es_lookup_extent(struct inode *inode, ext4_lblk_t lblk, | 142 | extern int ext4_es_lookup_extent(struct inode *inode, ext4_lblk_t lblk, |
143 | ext4_lblk_t *next_lblk, | ||
143 | struct extent_status *es); | 144 | struct extent_status *es); |
144 | extern bool ext4_es_scan_range(struct inode *inode, | 145 | extern bool ext4_es_scan_range(struct inode *inode, |
145 | int (*matching_fn)(struct extent_status *es), | 146 | int (*matching_fn)(struct extent_status *es), |
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index a6523516d681..4b92c7603907 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c | |||
@@ -527,7 +527,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode, | |||
527 | return -EFSCORRUPTED; | 527 | return -EFSCORRUPTED; |
528 | 528 | ||
529 | /* Lookup extent status tree firstly */ | 529 | /* Lookup extent status tree firstly */ |
530 | if (ext4_es_lookup_extent(inode, map->m_lblk, &es)) { | 530 | if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es)) { |
531 | if (ext4_es_is_written(&es) || ext4_es_is_unwritten(&es)) { | 531 | if (ext4_es_is_written(&es) || ext4_es_is_unwritten(&es)) { |
532 | map->m_pblk = ext4_es_pblock(&es) + | 532 | map->m_pblk = ext4_es_pblock(&es) + |
533 | map->m_lblk - es.es_lblk; | 533 | map->m_lblk - es.es_lblk; |
@@ -695,7 +695,7 @@ found: | |||
695 | * extent status tree. | 695 | * extent status tree. |
696 | */ | 696 | */ |
697 | if ((flags & EXT4_GET_BLOCKS_PRE_IO) && | 697 | if ((flags & EXT4_GET_BLOCKS_PRE_IO) && |
698 | ext4_es_lookup_extent(inode, map->m_lblk, &es)) { | 698 | ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es)) { |
699 | if (ext4_es_is_written(&es)) | 699 | if (ext4_es_is_written(&es)) |
700 | goto out_sem; | 700 | goto out_sem; |
701 | } | 701 | } |
@@ -1868,7 +1868,7 @@ static int ext4_da_map_blocks(struct inode *inode, sector_t iblock, | |||
1868 | (unsigned long) map->m_lblk); | 1868 | (unsigned long) map->m_lblk); |
1869 | 1869 | ||
1870 | /* Lookup extent status tree firstly */ | 1870 | /* Lookup extent status tree firstly */ |
1871 | if (ext4_es_lookup_extent(inode, iblock, &es)) { | 1871 | if (ext4_es_lookup_extent(inode, iblock, NULL, &es)) { |
1872 | if (ext4_es_is_hole(&es)) { | 1872 | if (ext4_es_is_hole(&es)) { |
1873 | retval = 0; | 1873 | retval = 0; |
1874 | down_read(&EXT4_I(inode)->i_data_sem); | 1874 | down_read(&EXT4_I(inode)->i_data_sem); |
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index ffb7bde4900d..d6242b7b8718 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c | |||
@@ -745,6 +745,74 @@ static void ext4_fill_fsxattr(struct inode *inode, struct fsxattr *fa) | |||
745 | fa->fsx_projid = from_kprojid(&init_user_ns, ei->i_projid); | 745 | fa->fsx_projid = from_kprojid(&init_user_ns, ei->i_projid); |
746 | } | 746 | } |
747 | 747 | ||
748 | /* copied from fs/ioctl.c */ | ||
749 | static int fiemap_check_ranges(struct super_block *sb, | ||
750 | u64 start, u64 len, u64 *new_len) | ||
751 | { | ||
752 | u64 maxbytes = (u64) sb->s_maxbytes; | ||
753 | |||
754 | *new_len = len; | ||
755 | |||
756 | if (len == 0) | ||
757 | return -EINVAL; | ||
758 | |||
759 | if (start > maxbytes) | ||
760 | return -EFBIG; | ||
761 | |||
762 | /* | ||
763 | * Shrink request scope to what the fs can actually handle. | ||
764 | */ | ||
765 | if (len > maxbytes || (maxbytes - len) < start) | ||
766 | *new_len = maxbytes - start; | ||
767 | |||
768 | return 0; | ||
769 | } | ||
770 | |||
771 | /* So that the fiemap access checks can't overflow on 32 bit machines. */ | ||
772 | #define FIEMAP_MAX_EXTENTS (UINT_MAX / sizeof(struct fiemap_extent)) | ||
773 | |||
774 | static int ext4_ioctl_get_es_cache(struct file *filp, unsigned long arg) | ||
775 | { | ||
776 | struct fiemap fiemap; | ||
777 | struct fiemap __user *ufiemap = (struct fiemap __user *) arg; | ||
778 | struct fiemap_extent_info fieinfo = { 0, }; | ||
779 | struct inode *inode = file_inode(filp); | ||
780 | struct super_block *sb = inode->i_sb; | ||
781 | u64 len; | ||
782 | int error; | ||
783 | |||
784 | if (copy_from_user(&fiemap, ufiemap, sizeof(fiemap))) | ||
785 | return -EFAULT; | ||
786 | |||
787 | if (fiemap.fm_extent_count > FIEMAP_MAX_EXTENTS) | ||
788 | return -EINVAL; | ||
789 | |||
790 | error = fiemap_check_ranges(sb, fiemap.fm_start, fiemap.fm_length, | ||
791 | &len); | ||
792 | if (error) | ||
793 | return error; | ||
794 | |||
795 | fieinfo.fi_flags = fiemap.fm_flags; | ||
796 | fieinfo.fi_extents_max = fiemap.fm_extent_count; | ||
797 | fieinfo.fi_extents_start = ufiemap->fm_extents; | ||
798 | |||
799 | if (fiemap.fm_extent_count != 0 && | ||
800 | !access_ok(fieinfo.fi_extents_start, | ||
801 | fieinfo.fi_extents_max * sizeof(struct fiemap_extent))) | ||
802 | return -EFAULT; | ||
803 | |||
804 | if (fieinfo.fi_flags & FIEMAP_FLAG_SYNC) | ||
805 | filemap_write_and_wait(inode->i_mapping); | ||
806 | |||
807 | error = ext4_get_es_cache(inode, &fieinfo, fiemap.fm_start, len); | ||
808 | fiemap.fm_flags = fieinfo.fi_flags; | ||
809 | fiemap.fm_mapped_extents = fieinfo.fi_extents_mapped; | ||
810 | if (copy_to_user(ufiemap, &fiemap, sizeof(fiemap))) | ||
811 | error = -EFAULT; | ||
812 | |||
813 | return error; | ||
814 | } | ||
815 | |||
748 | long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | 816 | long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
749 | { | 817 | { |
750 | struct inode *inode = file_inode(filp); | 818 | struct inode *inode = file_inode(filp); |
@@ -1139,6 +1207,9 @@ resizefs_out: | |||
1139 | return put_user(state, (__u32 __user *) arg); | 1207 | return put_user(state, (__u32 __user *) arg); |
1140 | } | 1208 | } |
1141 | 1209 | ||
1210 | case EXT4_IOC_GET_ES_CACHE: | ||
1211 | return ext4_ioctl_get_es_cache(filp, arg); | ||
1212 | |||
1142 | case EXT4_IOC_FSGETXATTR: | 1213 | case EXT4_IOC_FSGETXATTR: |
1143 | { | 1214 | { |
1144 | struct fsxattr fa; | 1215 | struct fsxattr fa; |
@@ -1259,6 +1330,7 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |||
1259 | case FS_IOC_GETFSMAP: | 1330 | case FS_IOC_GETFSMAP: |
1260 | case EXT4_IOC_CLEAR_ES_CACHE: | 1331 | case EXT4_IOC_CLEAR_ES_CACHE: |
1261 | case EXT4_IOC_GETSTATE: | 1332 | case EXT4_IOC_GETSTATE: |
1333 | case EXT4_IOC_GET_ES_CACHE: | ||
1262 | break; | 1334 | break; |
1263 | default: | 1335 | default: |
1264 | return -ENOIOCTLCMD; | 1336 | return -ENOIOCTLCMD; |