Btrfs: fix free space tree bitmaps on big-endian systems
In convert_free_space_to_{bitmaps,extents}(), we buffer the free space
bitmaps in memory and copy them directly to/from the extent buffers with
{read,write}_extent_buffer(). The extent buffer bitmap helpers use byte
granularity, which is equivalent to a little-endian bitmap. This means
that on big-endian systems, the in-memory bitmaps will be written to
disk byte-swapped. To fix this, use byte-granularity for the bitmaps in
memory.
Fixes: a5ed918285 ("Btrfs: implement the free space B-tree")
Cc: stable@vger.kernel.org # 4.5+
Tested-by: Holger Hoffstätte <holger@applied-asynchrony.com>
Tested-by: Chandan Rajendra <chandan@linux.vnet.ibm.com>
Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
			
			
This commit is contained in:
		
							parent
							
								
									08895a8b6b
								
							
						
					
					
						commit
						2fe1d55134
					
				| @ -5524,17 +5524,45 @@ void copy_extent_buffer(struct extent_buffer *dst, struct extent_buffer *src, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * The extent buffer bitmap operations are done with byte granularity because | ||||
|  * bitmap items are not guaranteed to be aligned to a word and therefore a | ||||
|  * single word in a bitmap may straddle two pages in the extent buffer. | ||||
|  */ | ||||
| #define BIT_BYTE(nr) ((nr) / BITS_PER_BYTE) | ||||
| #define BYTE_MASK ((1 << BITS_PER_BYTE) - 1) | ||||
| #define BITMAP_FIRST_BYTE_MASK(start) \ | ||||
| 	((BYTE_MASK << ((start) & (BITS_PER_BYTE - 1))) & BYTE_MASK) | ||||
| #define BITMAP_LAST_BYTE_MASK(nbits) \ | ||||
| 	(BYTE_MASK >> (-(nbits) & (BITS_PER_BYTE - 1))) | ||||
| void le_bitmap_set(u8 *map, unsigned int start, int len) | ||||
| { | ||||
| 	u8 *p = map + BIT_BYTE(start); | ||||
| 	const unsigned int size = start + len; | ||||
| 	int bits_to_set = BITS_PER_BYTE - (start % BITS_PER_BYTE); | ||||
| 	u8 mask_to_set = BITMAP_FIRST_BYTE_MASK(start); | ||||
| 
 | ||||
| 	while (len - bits_to_set >= 0) { | ||||
| 		*p |= mask_to_set; | ||||
| 		len -= bits_to_set; | ||||
| 		bits_to_set = BITS_PER_BYTE; | ||||
| 		mask_to_set = ~(u8)0; | ||||
| 		p++; | ||||
| 	} | ||||
| 	if (len) { | ||||
| 		mask_to_set &= BITMAP_LAST_BYTE_MASK(size); | ||||
| 		*p |= mask_to_set; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void le_bitmap_clear(u8 *map, unsigned int start, int len) | ||||
| { | ||||
| 	u8 *p = map + BIT_BYTE(start); | ||||
| 	const unsigned int size = start + len; | ||||
| 	int bits_to_clear = BITS_PER_BYTE - (start % BITS_PER_BYTE); | ||||
| 	u8 mask_to_clear = BITMAP_FIRST_BYTE_MASK(start); | ||||
| 
 | ||||
| 	while (len - bits_to_clear >= 0) { | ||||
| 		*p &= ~mask_to_clear; | ||||
| 		len -= bits_to_clear; | ||||
| 		bits_to_clear = BITS_PER_BYTE; | ||||
| 		mask_to_clear = ~(u8)0; | ||||
| 		p++; | ||||
| 	} | ||||
| 	if (len) { | ||||
| 		mask_to_clear &= BITMAP_LAST_BYTE_MASK(size); | ||||
| 		*p &= ~mask_to_clear; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * eb_bitmap_offset() - calculate the page and offset of the byte containing the | ||||
| @ -5578,7 +5606,7 @@ static inline void eb_bitmap_offset(struct extent_buffer *eb, | ||||
| int extent_buffer_test_bit(struct extent_buffer *eb, unsigned long start, | ||||
| 			   unsigned long nr) | ||||
| { | ||||
| 	char *kaddr; | ||||
| 	u8 *kaddr; | ||||
| 	struct page *page; | ||||
| 	unsigned long i; | ||||
| 	size_t offset; | ||||
| @ -5600,13 +5628,13 @@ int extent_buffer_test_bit(struct extent_buffer *eb, unsigned long start, | ||||
| void extent_buffer_bitmap_set(struct extent_buffer *eb, unsigned long start, | ||||
| 			      unsigned long pos, unsigned long len) | ||||
| { | ||||
| 	char *kaddr; | ||||
| 	u8 *kaddr; | ||||
| 	struct page *page; | ||||
| 	unsigned long i; | ||||
| 	size_t offset; | ||||
| 	const unsigned int size = pos + len; | ||||
| 	int bits_to_set = BITS_PER_BYTE - (pos % BITS_PER_BYTE); | ||||
| 	unsigned int mask_to_set = BITMAP_FIRST_BYTE_MASK(pos); | ||||
| 	u8 mask_to_set = BITMAP_FIRST_BYTE_MASK(pos); | ||||
| 
 | ||||
| 	eb_bitmap_offset(eb, start, pos, &i, &offset); | ||||
| 	page = eb->pages[i]; | ||||
| @ -5617,7 +5645,7 @@ void extent_buffer_bitmap_set(struct extent_buffer *eb, unsigned long start, | ||||
| 		kaddr[offset] |= mask_to_set; | ||||
| 		len -= bits_to_set; | ||||
| 		bits_to_set = BITS_PER_BYTE; | ||||
| 		mask_to_set = ~0U; | ||||
| 		mask_to_set = ~(u8)0; | ||||
| 		if (++offset >= PAGE_SIZE && len > 0) { | ||||
| 			offset = 0; | ||||
| 			page = eb->pages[++i]; | ||||
| @ -5642,13 +5670,13 @@ void extent_buffer_bitmap_set(struct extent_buffer *eb, unsigned long start, | ||||
| void extent_buffer_bitmap_clear(struct extent_buffer *eb, unsigned long start, | ||||
| 				unsigned long pos, unsigned long len) | ||||
| { | ||||
| 	char *kaddr; | ||||
| 	u8 *kaddr; | ||||
| 	struct page *page; | ||||
| 	unsigned long i; | ||||
| 	size_t offset; | ||||
| 	const unsigned int size = pos + len; | ||||
| 	int bits_to_clear = BITS_PER_BYTE - (pos % BITS_PER_BYTE); | ||||
| 	unsigned int mask_to_clear = BITMAP_FIRST_BYTE_MASK(pos); | ||||
| 	u8 mask_to_clear = BITMAP_FIRST_BYTE_MASK(pos); | ||||
| 
 | ||||
| 	eb_bitmap_offset(eb, start, pos, &i, &offset); | ||||
| 	page = eb->pages[i]; | ||||
| @ -5659,7 +5687,7 @@ void extent_buffer_bitmap_clear(struct extent_buffer *eb, unsigned long start, | ||||
| 		kaddr[offset] &= ~mask_to_clear; | ||||
| 		len -= bits_to_clear; | ||||
| 		bits_to_clear = BITS_PER_BYTE; | ||||
| 		mask_to_clear = ~0U; | ||||
| 		mask_to_clear = ~(u8)0; | ||||
| 		if (++offset >= PAGE_SIZE && len > 0) { | ||||
| 			offset = 0; | ||||
| 			page = eb->pages[++i]; | ||||
|  | ||||
| @ -59,6 +59,28 @@ | ||||
|  */ | ||||
| #define EXTENT_PAGE_PRIVATE 1 | ||||
| 
 | ||||
| /*
 | ||||
|  * The extent buffer bitmap operations are done with byte granularity instead of | ||||
|  * word granularity for two reasons: | ||||
|  * 1. The bitmaps must be little-endian on disk. | ||||
|  * 2. Bitmap items are not guaranteed to be aligned to a word and therefore a | ||||
|  *    single word in a bitmap may straddle two pages in the extent buffer. | ||||
|  */ | ||||
| #define BIT_BYTE(nr) ((nr) / BITS_PER_BYTE) | ||||
| #define BYTE_MASK ((1 << BITS_PER_BYTE) - 1) | ||||
| #define BITMAP_FIRST_BYTE_MASK(start) \ | ||||
| 	((BYTE_MASK << ((start) & (BITS_PER_BYTE - 1))) & BYTE_MASK) | ||||
| #define BITMAP_LAST_BYTE_MASK(nbits) \ | ||||
| 	(BYTE_MASK >> (-(nbits) & (BITS_PER_BYTE - 1))) | ||||
| 
 | ||||
| static inline int le_test_bit(int nr, const u8 *addr) | ||||
| { | ||||
| 	return 1U & (addr[BIT_BYTE(nr)] >> (nr & (BITS_PER_BYTE-1))); | ||||
| } | ||||
| 
 | ||||
| extern void le_bitmap_set(u8 *map, unsigned int start, int len); | ||||
| extern void le_bitmap_clear(u8 *map, unsigned int start, int len); | ||||
| 
 | ||||
| struct extent_state; | ||||
| struct btrfs_root; | ||||
| struct btrfs_io_bio; | ||||
|  | ||||
| @ -151,7 +151,7 @@ static inline u32 free_space_bitmap_size(u64 size, u32 sectorsize) | ||||
| 	return DIV_ROUND_UP((u32)div_u64(size, sectorsize), BITS_PER_BYTE); | ||||
| } | ||||
| 
 | ||||
| static unsigned long *alloc_bitmap(u32 bitmap_size) | ||||
| static u8 *alloc_bitmap(u32 bitmap_size) | ||||
| { | ||||
| 	void *mem; | ||||
| 
 | ||||
| @ -180,8 +180,7 @@ int convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans, | ||||
| 	struct btrfs_free_space_info *info; | ||||
| 	struct btrfs_key key, found_key; | ||||
| 	struct extent_buffer *leaf; | ||||
| 	unsigned long *bitmap; | ||||
| 	char *bitmap_cursor; | ||||
| 	u8 *bitmap, *bitmap_cursor; | ||||
| 	u64 start, end; | ||||
| 	u64 bitmap_range, i; | ||||
| 	u32 bitmap_size, flags, expected_extent_count; | ||||
| @ -231,7 +230,7 @@ int convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans, | ||||
| 						block_group->sectorsize); | ||||
| 				last = div_u64(found_key.objectid + found_key.offset - start, | ||||
| 					       block_group->sectorsize); | ||||
| 				bitmap_set(bitmap, first, last - first); | ||||
| 				le_bitmap_set(bitmap, first, last - first); | ||||
| 
 | ||||
| 				extent_count++; | ||||
| 				nr++; | ||||
| @ -269,7 +268,7 @@ int convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans, | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	bitmap_cursor = (char *)bitmap; | ||||
| 	bitmap_cursor = bitmap; | ||||
| 	bitmap_range = block_group->sectorsize * BTRFS_FREE_SPACE_BITMAP_BITS; | ||||
| 	i = start; | ||||
| 	while (i < end) { | ||||
| @ -318,7 +317,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans, | ||||
| 	struct btrfs_free_space_info *info; | ||||
| 	struct btrfs_key key, found_key; | ||||
| 	struct extent_buffer *leaf; | ||||
| 	unsigned long *bitmap; | ||||
| 	u8 *bitmap; | ||||
| 	u64 start, end; | ||||
| 	/* Initialize to silence GCC. */ | ||||
| 	u64 extent_start = 0; | ||||
| @ -362,7 +361,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans, | ||||
| 				break; | ||||
| 			} else if (found_key.type == BTRFS_FREE_SPACE_BITMAP_KEY) { | ||||
| 				unsigned long ptr; | ||||
| 				char *bitmap_cursor; | ||||
| 				u8 *bitmap_cursor; | ||||
| 				u32 bitmap_pos, data_size; | ||||
| 
 | ||||
| 				ASSERT(found_key.objectid >= start); | ||||
| @ -372,7 +371,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans, | ||||
| 				bitmap_pos = div_u64(found_key.objectid - start, | ||||
| 						     block_group->sectorsize * | ||||
| 						     BITS_PER_BYTE); | ||||
| 				bitmap_cursor = ((char *)bitmap) + bitmap_pos; | ||||
| 				bitmap_cursor = bitmap + bitmap_pos; | ||||
| 				data_size = free_space_bitmap_size(found_key.offset, | ||||
| 								   block_group->sectorsize); | ||||
| 
 | ||||
| @ -409,7 +408,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans, | ||||
| 	offset = start; | ||||
| 	bitnr = 0; | ||||
| 	while (offset < end) { | ||||
| 		bit = !!test_bit(bitnr, bitmap); | ||||
| 		bit = !!le_test_bit(bitnr, bitmap); | ||||
| 		if (prev_bit == 0 && bit == 1) { | ||||
| 			extent_start = offset; | ||||
| 		} else if (prev_bit == 1 && bit == 0) { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user