Commit d3304cad authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for-linus-4.9' of git://git.kernel.org/pub/scm/linux/kernel/git/mason/linux-btrfs

Pull btrfs fixes from Chris Mason:
 "Some fixes from Omar and Dave Sterba for our new free space tree.

  This isn't heavily used yet, but as we move toward making it the new
  default we wanted to nail down an endian bug"

* 'for-linus-4.9' of git://git.kernel.org/pub/scm/linux/kernel/git/mason/linux-btrfs:
  btrfs: tests: uninline member definitions in free_space_extent
  btrfs: tests: constify free space extent specs
  Btrfs: expand free space tree sanity tests to catch endianness bug
  Btrfs: fix extent buffer bitmap tests on big-endian systems
  Btrfs: catch invalid free space trees
  Btrfs: fix mount -o clear_cache,space_cache=v2
  Btrfs: fix free space tree bitmaps on big-endian systems
parents 1a892b48 d9ed71e5
...@@ -252,7 +252,8 @@ struct btrfs_super_block { ...@@ -252,7 +252,8 @@ struct btrfs_super_block {
#define BTRFS_FEATURE_COMPAT_SAFE_CLEAR 0ULL #define BTRFS_FEATURE_COMPAT_SAFE_CLEAR 0ULL
#define BTRFS_FEATURE_COMPAT_RO_SUPP \ #define BTRFS_FEATURE_COMPAT_RO_SUPP \
(BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE) (BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE | \
BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID)
#define BTRFS_FEATURE_COMPAT_RO_SAFE_SET 0ULL #define BTRFS_FEATURE_COMPAT_RO_SAFE_SET 0ULL
#define BTRFS_FEATURE_COMPAT_RO_SAFE_CLEAR 0ULL #define BTRFS_FEATURE_COMPAT_RO_SAFE_CLEAR 0ULL
......
...@@ -2586,6 +2586,7 @@ int open_ctree(struct super_block *sb, ...@@ -2586,6 +2586,7 @@ int open_ctree(struct super_block *sb,
int num_backups_tried = 0; int num_backups_tried = 0;
int backup_index = 0; int backup_index = 0;
int max_active; int max_active;
int clear_free_space_tree = 0;
tree_root = fs_info->tree_root = btrfs_alloc_root(fs_info, GFP_KERNEL); tree_root = fs_info->tree_root = btrfs_alloc_root(fs_info, GFP_KERNEL);
chunk_root = fs_info->chunk_root = btrfs_alloc_root(fs_info, GFP_KERNEL); chunk_root = fs_info->chunk_root = btrfs_alloc_root(fs_info, GFP_KERNEL);
...@@ -3148,6 +3149,26 @@ int open_ctree(struct super_block *sb, ...@@ -3148,6 +3149,26 @@ int open_ctree(struct super_block *sb,
if (sb->s_flags & MS_RDONLY) if (sb->s_flags & MS_RDONLY)
return 0; return 0;
if (btrfs_test_opt(fs_info, CLEAR_CACHE) &&
btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE)) {
clear_free_space_tree = 1;
} else if (btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE) &&
!btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE_VALID)) {
btrfs_warn(fs_info, "free space tree is invalid");
clear_free_space_tree = 1;
}
if (clear_free_space_tree) {
btrfs_info(fs_info, "clearing free space tree");
ret = btrfs_clear_free_space_tree(fs_info);
if (ret) {
btrfs_warn(fs_info,
"failed to clear free space tree: %d", ret);
close_ctree(tree_root);
return ret;
}
}
if (btrfs_test_opt(tree_root->fs_info, FREE_SPACE_TREE) && if (btrfs_test_opt(tree_root->fs_info, FREE_SPACE_TREE) &&
!btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE)) { !btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE)) {
btrfs_info(fs_info, "creating free space tree"); btrfs_info(fs_info, "creating free space tree");
...@@ -3185,18 +3206,6 @@ int open_ctree(struct super_block *sb, ...@@ -3185,18 +3206,6 @@ int open_ctree(struct super_block *sb,
btrfs_qgroup_rescan_resume(fs_info); btrfs_qgroup_rescan_resume(fs_info);
if (btrfs_test_opt(tree_root->fs_info, CLEAR_CACHE) &&
btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE)) {
btrfs_info(fs_info, "clearing free space tree");
ret = btrfs_clear_free_space_tree(fs_info);
if (ret) {
btrfs_warn(fs_info,
"failed to clear free space tree: %d", ret);
close_ctree(tree_root);
return ret;
}
}
if (!fs_info->uuid_root) { if (!fs_info->uuid_root) {
btrfs_info(fs_info, "creating UUID tree"); btrfs_info(fs_info, "creating UUID tree");
ret = btrfs_create_uuid_tree(fs_info); ret = btrfs_create_uuid_tree(fs_info);
......
...@@ -5558,17 +5558,45 @@ void copy_extent_buffer(struct extent_buffer *dst, struct extent_buffer *src, ...@@ -5558,17 +5558,45 @@ void copy_extent_buffer(struct extent_buffer *dst, struct extent_buffer *src,
} }
} }
/* void le_bitmap_set(u8 *map, unsigned int start, int len)
* 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 u8 *p = map + BIT_BYTE(start);
* single word in a bitmap may straddle two pages in the extent buffer. const unsigned int size = start + len;
*/ int bits_to_set = BITS_PER_BYTE - (start % BITS_PER_BYTE);
#define BIT_BYTE(nr) ((nr) / BITS_PER_BYTE) u8 mask_to_set = BITMAP_FIRST_BYTE_MASK(start);
#define BYTE_MASK ((1 << BITS_PER_BYTE) - 1)
#define BITMAP_FIRST_BYTE_MASK(start) \ while (len - bits_to_set >= 0) {
((BYTE_MASK << ((start) & (BITS_PER_BYTE - 1))) & BYTE_MASK) *p |= mask_to_set;
#define BITMAP_LAST_BYTE_MASK(nbits) \ len -= bits_to_set;
(BYTE_MASK >> (-(nbits) & (BITS_PER_BYTE - 1))) 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 * eb_bitmap_offset() - calculate the page and offset of the byte containing the
...@@ -5612,7 +5640,7 @@ static inline void eb_bitmap_offset(struct extent_buffer *eb, ...@@ -5612,7 +5640,7 @@ static inline void eb_bitmap_offset(struct extent_buffer *eb,
int extent_buffer_test_bit(struct extent_buffer *eb, unsigned long start, int extent_buffer_test_bit(struct extent_buffer *eb, unsigned long start,
unsigned long nr) unsigned long nr)
{ {
char *kaddr; u8 *kaddr;
struct page *page; struct page *page;
unsigned long i; unsigned long i;
size_t offset; size_t offset;
...@@ -5634,13 +5662,13 @@ int extent_buffer_test_bit(struct extent_buffer *eb, unsigned long start, ...@@ -5634,13 +5662,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, void extent_buffer_bitmap_set(struct extent_buffer *eb, unsigned long start,
unsigned long pos, unsigned long len) unsigned long pos, unsigned long len)
{ {
char *kaddr; u8 *kaddr;
struct page *page; struct page *page;
unsigned long i; unsigned long i;
size_t offset; size_t offset;
const unsigned int size = pos + len; const unsigned int size = pos + len;
int bits_to_set = BITS_PER_BYTE - (pos % BITS_PER_BYTE); 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); eb_bitmap_offset(eb, start, pos, &i, &offset);
page = eb->pages[i]; page = eb->pages[i];
...@@ -5651,7 +5679,7 @@ void extent_buffer_bitmap_set(struct extent_buffer *eb, unsigned long start, ...@@ -5651,7 +5679,7 @@ void extent_buffer_bitmap_set(struct extent_buffer *eb, unsigned long start,
kaddr[offset] |= mask_to_set; kaddr[offset] |= mask_to_set;
len -= bits_to_set; len -= bits_to_set;
bits_to_set = BITS_PER_BYTE; bits_to_set = BITS_PER_BYTE;
mask_to_set = ~0U; mask_to_set = ~(u8)0;
if (++offset >= PAGE_SIZE && len > 0) { if (++offset >= PAGE_SIZE && len > 0) {
offset = 0; offset = 0;
page = eb->pages[++i]; page = eb->pages[++i];
...@@ -5676,13 +5704,13 @@ void extent_buffer_bitmap_set(struct extent_buffer *eb, unsigned long start, ...@@ -5676,13 +5704,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, void extent_buffer_bitmap_clear(struct extent_buffer *eb, unsigned long start,
unsigned long pos, unsigned long len) unsigned long pos, unsigned long len)
{ {
char *kaddr; u8 *kaddr;
struct page *page; struct page *page;
unsigned long i; unsigned long i;
size_t offset; size_t offset;
const unsigned int size = pos + len; const unsigned int size = pos + len;
int bits_to_clear = BITS_PER_BYTE - (pos % BITS_PER_BYTE); 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); eb_bitmap_offset(eb, start, pos, &i, &offset);
page = eb->pages[i]; page = eb->pages[i];
...@@ -5693,7 +5721,7 @@ void extent_buffer_bitmap_clear(struct extent_buffer *eb, unsigned long start, ...@@ -5693,7 +5721,7 @@ void extent_buffer_bitmap_clear(struct extent_buffer *eb, unsigned long start,
kaddr[offset] &= ~mask_to_clear; kaddr[offset] &= ~mask_to_clear;
len -= bits_to_clear; len -= bits_to_clear;
bits_to_clear = BITS_PER_BYTE; bits_to_clear = BITS_PER_BYTE;
mask_to_clear = ~0U; mask_to_clear = ~(u8)0;
if (++offset >= PAGE_SIZE && len > 0) { if (++offset >= PAGE_SIZE && len > 0) {
offset = 0; offset = 0;
page = eb->pages[++i]; page = eb->pages[++i];
......
...@@ -59,6 +59,28 @@ ...@@ -59,6 +59,28 @@
*/ */
#define EXTENT_PAGE_PRIVATE 1 #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 extent_state;
struct btrfs_root; struct btrfs_root;
struct btrfs_io_bio; struct btrfs_io_bio;
......
...@@ -151,7 +151,7 @@ static inline u32 free_space_bitmap_size(u64 size, u32 sectorsize) ...@@ -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); 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; void *mem;
...@@ -180,8 +180,7 @@ int convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans, ...@@ -180,8 +180,7 @@ int convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans,
struct btrfs_free_space_info *info; struct btrfs_free_space_info *info;
struct btrfs_key key, found_key; struct btrfs_key key, found_key;
struct extent_buffer *leaf; struct extent_buffer *leaf;
unsigned long *bitmap; u8 *bitmap, *bitmap_cursor;
char *bitmap_cursor;
u64 start, end; u64 start, end;
u64 bitmap_range, i; u64 bitmap_range, i;
u32 bitmap_size, flags, expected_extent_count; u32 bitmap_size, flags, expected_extent_count;
...@@ -231,7 +230,7 @@ int convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans, ...@@ -231,7 +230,7 @@ int convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans,
block_group->sectorsize); block_group->sectorsize);
last = div_u64(found_key.objectid + found_key.offset - start, last = div_u64(found_key.objectid + found_key.offset - start,
block_group->sectorsize); block_group->sectorsize);
bitmap_set(bitmap, first, last - first); le_bitmap_set(bitmap, first, last - first);
extent_count++; extent_count++;
nr++; nr++;
...@@ -270,7 +269,7 @@ int convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans, ...@@ -270,7 +269,7 @@ int convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans,
goto out; goto out;
} }
bitmap_cursor = (char *)bitmap; bitmap_cursor = bitmap;
bitmap_range = block_group->sectorsize * BTRFS_FREE_SPACE_BITMAP_BITS; bitmap_range = block_group->sectorsize * BTRFS_FREE_SPACE_BITMAP_BITS;
i = start; i = start;
while (i < end) { while (i < end) {
...@@ -319,7 +318,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans, ...@@ -319,7 +318,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans,
struct btrfs_free_space_info *info; struct btrfs_free_space_info *info;
struct btrfs_key key, found_key; struct btrfs_key key, found_key;
struct extent_buffer *leaf; struct extent_buffer *leaf;
unsigned long *bitmap; u8 *bitmap;
u64 start, end; u64 start, end;
/* Initialize to silence GCC. */ /* Initialize to silence GCC. */
u64 extent_start = 0; u64 extent_start = 0;
...@@ -363,7 +362,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans, ...@@ -363,7 +362,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans,
break; break;
} else if (found_key.type == BTRFS_FREE_SPACE_BITMAP_KEY) { } else if (found_key.type == BTRFS_FREE_SPACE_BITMAP_KEY) {
unsigned long ptr; unsigned long ptr;
char *bitmap_cursor; u8 *bitmap_cursor;
u32 bitmap_pos, data_size; u32 bitmap_pos, data_size;
ASSERT(found_key.objectid >= start); ASSERT(found_key.objectid >= start);
...@@ -373,7 +372,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans, ...@@ -373,7 +372,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans,
bitmap_pos = div_u64(found_key.objectid - start, bitmap_pos = div_u64(found_key.objectid - start,
block_group->sectorsize * block_group->sectorsize *
BITS_PER_BYTE); BITS_PER_BYTE);
bitmap_cursor = ((char *)bitmap) + bitmap_pos; bitmap_cursor = bitmap + bitmap_pos;
data_size = free_space_bitmap_size(found_key.offset, data_size = free_space_bitmap_size(found_key.offset,
block_group->sectorsize); block_group->sectorsize);
...@@ -410,7 +409,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans, ...@@ -410,7 +409,7 @@ int convert_free_space_to_extents(struct btrfs_trans_handle *trans,
offset = start; offset = start;
bitnr = 0; bitnr = 0;
while (offset < end) { while (offset < end) {
bit = !!test_bit(bitnr, bitmap); bit = !!le_test_bit(bitnr, bitmap);
if (prev_bit == 0 && bit == 1) { if (prev_bit == 0 && bit == 1) {
extent_start = offset; extent_start = offset;
} else if (prev_bit == 1 && bit == 0) { } else if (prev_bit == 1 && bit == 0) {
...@@ -1185,6 +1184,7 @@ int btrfs_create_free_space_tree(struct btrfs_fs_info *fs_info) ...@@ -1185,6 +1184,7 @@ int btrfs_create_free_space_tree(struct btrfs_fs_info *fs_info)
} }
btrfs_set_fs_compat_ro(fs_info, FREE_SPACE_TREE); btrfs_set_fs_compat_ro(fs_info, FREE_SPACE_TREE);
btrfs_set_fs_compat_ro(fs_info, FREE_SPACE_TREE_VALID);
clear_bit(BTRFS_FS_CREATING_FREE_SPACE_TREE, &fs_info->flags); clear_bit(BTRFS_FS_CREATING_FREE_SPACE_TREE, &fs_info->flags);
ret = btrfs_commit_transaction(trans, tree_root); ret = btrfs_commit_transaction(trans, tree_root);
...@@ -1253,6 +1253,7 @@ int btrfs_clear_free_space_tree(struct btrfs_fs_info *fs_info) ...@@ -1253,6 +1253,7 @@ int btrfs_clear_free_space_tree(struct btrfs_fs_info *fs_info)
return PTR_ERR(trans); return PTR_ERR(trans);
btrfs_clear_fs_compat_ro(fs_info, FREE_SPACE_TREE); btrfs_clear_fs_compat_ro(fs_info, FREE_SPACE_TREE);
btrfs_clear_fs_compat_ro(fs_info, FREE_SPACE_TREE_VALID);
fs_info->free_space_root = NULL; fs_info->free_space_root = NULL;
ret = clear_free_space_tree(trans, free_space_root); ret = clear_free_space_tree(trans, free_space_root);
......
...@@ -273,20 +273,37 @@ static int test_find_delalloc(u32 sectorsize) ...@@ -273,20 +273,37 @@ static int test_find_delalloc(u32 sectorsize)
return ret; return ret;
} }
/** static int check_eb_bitmap(unsigned long *bitmap, struct extent_buffer *eb,
* test_bit_in_byte - Determine whether a bit is set in a byte unsigned long len)
* @nr: bit number to test
* @addr: Address to start counting from
*/
static inline int test_bit_in_byte(int nr, const u8 *addr)
{ {
return 1UL & (addr[nr / BITS_PER_BYTE] >> (nr & (BITS_PER_BYTE - 1))); unsigned long i;
for (i = 0; i < len * BITS_PER_BYTE; i++) {
int bit, bit1;
bit = !!test_bit(i, bitmap);
bit1 = !!extent_buffer_test_bit(eb, 0, i);
if (bit1 != bit) {
test_msg("Bits do not match\n");
return -EINVAL;
}
bit1 = !!extent_buffer_test_bit(eb, i / BITS_PER_BYTE,
i % BITS_PER_BYTE);
if (bit1 != bit) {
test_msg("Offset bits do not match\n");
return -EINVAL;
}
}
return 0;
} }
static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb, static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb,
unsigned long len) unsigned long len)
{ {
unsigned long i, x; unsigned long i, j;
u32 x;
int ret;
memset(bitmap, 0, len); memset(bitmap, 0, len);
memset_extent_buffer(eb, 0, 0, len); memset_extent_buffer(eb, 0, 0, len);
...@@ -297,16 +314,18 @@ static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb, ...@@ -297,16 +314,18 @@ static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb,
bitmap_set(bitmap, 0, len * BITS_PER_BYTE); bitmap_set(bitmap, 0, len * BITS_PER_BYTE);
extent_buffer_bitmap_set(eb, 0, 0, len * BITS_PER_BYTE); extent_buffer_bitmap_set(eb, 0, 0, len * BITS_PER_BYTE);
if (memcmp_extent_buffer(eb, bitmap, 0, len) != 0) { ret = check_eb_bitmap(bitmap, eb, len);
if (ret) {
test_msg("Setting all bits failed\n"); test_msg("Setting all bits failed\n");
return -EINVAL; return ret;
} }
bitmap_clear(bitmap, 0, len * BITS_PER_BYTE); bitmap_clear(bitmap, 0, len * BITS_PER_BYTE);
extent_buffer_bitmap_clear(eb, 0, 0, len * BITS_PER_BYTE); extent_buffer_bitmap_clear(eb, 0, 0, len * BITS_PER_BYTE);
if (memcmp_extent_buffer(eb, bitmap, 0, len) != 0) { ret = check_eb_bitmap(bitmap, eb, len);
if (ret) {
test_msg("Clearing all bits failed\n"); test_msg("Clearing all bits failed\n");
return -EINVAL; return ret;
} }
/* Straddling pages test */ /* Straddling pages test */
...@@ -316,9 +335,10 @@ static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb, ...@@ -316,9 +335,10 @@ static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb,
sizeof(long) * BITS_PER_BYTE); sizeof(long) * BITS_PER_BYTE);
extent_buffer_bitmap_set(eb, PAGE_SIZE - sizeof(long) / 2, 0, extent_buffer_bitmap_set(eb, PAGE_SIZE - sizeof(long) / 2, 0,
sizeof(long) * BITS_PER_BYTE); sizeof(long) * BITS_PER_BYTE);
if (memcmp_extent_buffer(eb, bitmap, 0, len) != 0) { ret = check_eb_bitmap(bitmap, eb, len);
if (ret) {
test_msg("Setting straddling pages failed\n"); test_msg("Setting straddling pages failed\n");
return -EINVAL; return ret;
} }
bitmap_set(bitmap, 0, len * BITS_PER_BYTE); bitmap_set(bitmap, 0, len * BITS_PER_BYTE);
...@@ -328,9 +348,10 @@ static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb, ...@@ -328,9 +348,10 @@ static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb,
extent_buffer_bitmap_set(eb, 0, 0, len * BITS_PER_BYTE); extent_buffer_bitmap_set(eb, 0, 0, len * BITS_PER_BYTE);
extent_buffer_bitmap_clear(eb, PAGE_SIZE - sizeof(long) / 2, 0, extent_buffer_bitmap_clear(eb, PAGE_SIZE - sizeof(long) / 2, 0,
sizeof(long) * BITS_PER_BYTE); sizeof(long) * BITS_PER_BYTE);
if (memcmp_extent_buffer(eb, bitmap, 0, len) != 0) { ret = check_eb_bitmap(bitmap, eb, len);
if (ret) {
test_msg("Clearing straddling pages failed\n"); test_msg("Clearing straddling pages failed\n");
return -EINVAL; return ret;
} }
} }
...@@ -339,28 +360,22 @@ static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb, ...@@ -339,28 +360,22 @@ static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb,
* something repetitive that could miss some hypothetical off-by-n bug. * something repetitive that could miss some hypothetical off-by-n bug.
*/ */
x = 0; x = 0;
for (i = 0; i < len / sizeof(long); i++) { bitmap_clear(bitmap, 0, len * BITS_PER_BYTE);
x = (0x19660dULL * (u64)x + 0x3c6ef35fULL) & 0xffffffffUL; extent_buffer_bitmap_clear(eb, 0, 0, len * BITS_PER_BYTE);
bitmap[i] = x; for (i = 0; i < len * BITS_PER_BYTE / 32; i++) {
} x = (0x19660dULL * (u64)x + 0x3c6ef35fULL) & 0xffffffffU;
write_extent_buffer(eb, bitmap, 0, len); for (j = 0; j < 32; j++) {
if (x & (1U << j)) {
for (i = 0; i < len * BITS_PER_BYTE; i++) { bitmap_set(bitmap, i * 32 + j, 1);
int bit, bit1; extent_buffer_bitmap_set(eb, 0, i * 32 + j, 1);
}
bit = !!test_bit_in_byte(i, (u8 *)bitmap);
bit1 = !!extent_buffer_test_bit(eb, 0, i);
if (bit1 != bit) {
test_msg("Testing bit pattern failed\n");
return -EINVAL;
} }
}
bit1 = !!extent_buffer_test_bit(eb, i / BITS_PER_BYTE, ret = check_eb_bitmap(bitmap, eb, len);
i % BITS_PER_BYTE); if (ret) {
if (bit1 != bit) { test_msg("Random bit pattern failed\n");
test_msg("Testing bit pattern with offset failed\n"); return ret;
return -EINVAL;
}
} }
return 0; return 0;
......
...@@ -24,20 +24,15 @@ ...@@ -24,20 +24,15 @@
#include "../transaction.h" #include "../transaction.h"
struct free_space_extent { struct free_space_extent {
u64 start, length; u64 start;
u64 length;
}; };
/*
* The test cases align their operations to this in order to hit some of the
* edge cases in the bitmap code.
*/
#define BITMAP_RANGE (BTRFS_FREE_SPACE_BITMAP_BITS * PAGE_SIZE)
static int __check_free_space_extents(struct btrfs_trans_handle *trans, static int __check_free_space_extents(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path, struct btrfs_path *path,
struct free_space_extent *extents, const struct free_space_extent * const extents,
unsigned int num_extents) unsigned int num_extents)
{ {
struct btrfs_free_space_info *info; struct btrfs_free_space_info *info;
...@@ -126,7 +121,7 @@ static int check_free_space_extents(struct btrfs_trans_handle *trans, ...@@ -126,7 +121,7 @@ static int check_free_space_extents(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path, struct btrfs_path *path,
struct free_space_extent *extents, const struct free_space_extent * const extents,
unsigned int num_extents) unsigned int num_extents)
{ {
struct btrfs_free_space_info *info; struct btrfs_free_space_info *info;
...@@ -168,9 +163,10 @@ static int check_free_space_extents(struct btrfs_trans_handle *trans, ...@@ -168,9 +163,10 @@ static int check_free_space_extents(struct btrfs_trans_handle *trans,
static int test_empty_block_group(struct btrfs_trans_handle *trans, static int test_empty_block_group(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path) struct btrfs_path *path,
u32 alignment)
{ {
struct free_space_extent extents[] = { const struct free_space_extent extents[] = {
{cache->key.objectid, cache->key.offset}, {cache->key.objectid, cache->key.offset},
}; };
...@@ -181,9 +177,10 @@ static int test_empty_block_group(struct btrfs_trans_handle *trans, ...@@ -181,9 +177,10 @@ static int test_empty_block_group(struct btrfs_trans_handle *trans,
static int test_remove_all(struct btrfs_trans_handle *trans, static int test_remove_all(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path) struct btrfs_path *path,
u32 alignment)
{ {
struct free_space_extent extents[] = {}; const struct free_space_extent extents[] = {};
int ret; int ret;
ret = __remove_from_free_space_tree(trans, fs_info, cache, path, ret = __remove_from_free_space_tree(trans, fs_info, cache, path,
...@@ -201,16 +198,17 @@ static int test_remove_all(struct btrfs_trans_handle *trans, ...@@ -201,16 +198,17 @@ static int test_remove_all(struct btrfs_trans_handle *trans,
static int test_remove_beginning(struct btrfs_trans_handle *trans, static int test_remove_beginning(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path) struct btrfs_path *path,
u32 alignment)
{ {
struct free_space_extent extents[] = { const struct free_space_extent extents[] = {
{cache->key.objectid + BITMAP_RANGE, {cache->key.objectid + alignment,
cache->key.offset - BITMAP_RANGE}, cache->key.offset - alignment},
}; };
int ret; int ret;
ret = __remove_from_free_space_tree(trans, fs_info, cache, path, ret = __remove_from_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid, BITMAP_RANGE); cache->key.objectid, alignment);
if (ret) { if (ret) {
test_msg("Could not remove free space\n"); test_msg("Could not remove free space\n");
return ret; return ret;
...@@ -224,17 +222,18 @@ static int test_remove_beginning(struct btrfs_trans_handle *trans, ...@@ -224,17 +222,18 @@ static int test_remove_beginning(struct btrfs_trans_handle *trans,
static int test_remove_end(struct btrfs_trans_handle *trans, static int test_remove_end(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path) struct btrfs_path *path,
u32 alignment)
{ {
struct free_space_extent extents[] = { const struct free_space_extent extents[] = {
{cache->key.objectid, cache->key.offset - BITMAP_RANGE}, {cache->key.objectid, cache->key.offset - alignment},
}; };
int ret; int ret;
ret = __remove_from_free_space_tree(trans, fs_info, cache, path, ret = __remove_from_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid + cache->key.objectid +
cache->key.offset - BITMAP_RANGE, cache->key.offset - alignment,
BITMAP_RANGE); alignment);
if (ret) { if (ret) {
test_msg("Could not remove free space\n"); test_msg("Could not remove free space\n");
return ret; return ret;
...@@ -247,18 +246,19 @@ static int test_remove_end(struct btrfs_trans_handle *trans, ...@@ -247,18 +246,19 @@ static int test_remove_end(struct btrfs_trans_handle *trans,
static int test_remove_middle(struct btrfs_trans_handle *trans, static int test_remove_middle(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path) struct btrfs_path *path,
u32 alignment)
{ {
struct free_space_extent extents[] = { const struct free_space_extent extents[] = {
{cache->key.objectid, BITMAP_RANGE}, {cache->key.objectid, alignment},
{cache->key.objectid + 2 * BITMAP_RANGE, {cache->key.objectid + 2 * alignment,
cache->key.offset - 2 * BITMAP_RANGE}, cache->key.offset - 2 * alignment},
}; };
int ret; int ret;
ret = __remove_from_free_space_tree(trans, fs_info, cache, path, ret = __remove_from_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid + BITMAP_RANGE, cache->key.objectid + alignment,
BITMAP_RANGE); alignment);
if (ret) { if (ret) {
test_msg("Could not remove free space\n"); test_msg("Could not remove free space\n");
return ret; return ret;
...@@ -271,10 +271,11 @@ static int test_remove_middle(struct btrfs_trans_handle *trans, ...@@ -271,10 +271,11 @@ static int test_remove_middle(struct btrfs_trans_handle *trans,
static int test_merge_left(struct btrfs_trans_handle *trans, static int test_merge_left(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path) struct btrfs_path *path,
u32 alignment)
{ {
struct free_space_extent extents[] = { const struct free_space_extent extents[] = {
{cache->key.objectid, 2 * BITMAP_RANGE}, {cache->key.objectid, 2 * alignment},
}; };
int ret; int ret;
...@@ -287,15 +288,15 @@ static int test_merge_left(struct btrfs_trans_handle *trans, ...@@ -287,15 +288,15 @@ static int test_merge_left(struct btrfs_trans_handle *trans,
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid, BITMAP_RANGE); cache->key.objectid, alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid + BITMAP_RANGE, cache->key.objectid + alignment,
BITMAP_RANGE); alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
...@@ -308,10 +309,11 @@ static int test_merge_left(struct btrfs_trans_handle *trans, ...@@ -308,10 +309,11 @@ static int test_merge_left(struct btrfs_trans_handle *trans,
static int test_merge_right(struct btrfs_trans_handle *trans, static int test_merge_right(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path) struct btrfs_path *path,
u32 alignment)
{ {
struct free_space_extent extents[] = { const struct free_space_extent extents[] = {
{cache->key.objectid + BITMAP_RANGE, 2 * BITMAP_RANGE}, {cache->key.objectid + alignment, 2 * alignment},
}; };
int ret; int ret;
...@@ -324,16 +326,16 @@ static int test_merge_right(struct btrfs_trans_handle *trans, ...@@ -324,16 +326,16 @@ static int test_merge_right(struct btrfs_trans_handle *trans,
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid + 2 * BITMAP_RANGE, cache->key.objectid + 2 * alignment,
BITMAP_RANGE); alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid + BITMAP_RANGE, cache->key.objectid + alignment,
BITMAP_RANGE); alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
...@@ -346,10 +348,11 @@ static int test_merge_right(struct btrfs_trans_handle *trans, ...@@ -346,10 +348,11 @@ static int test_merge_right(struct btrfs_trans_handle *trans,
static int test_merge_both(struct btrfs_trans_handle *trans, static int test_merge_both(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path) struct btrfs_path *path,
u32 alignment)
{ {
struct free_space_extent extents[] = { const struct free_space_extent extents[] = {
{cache->key.objectid, 3 * BITMAP_RANGE}, {cache->key.objectid, 3 * alignment},
}; };
int ret; int ret;
...@@ -362,23 +365,23 @@ static int test_merge_both(struct btrfs_trans_handle *trans, ...@@ -362,23 +365,23 @@ static int test_merge_both(struct btrfs_trans_handle *trans,
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid, BITMAP_RANGE); cache->key.objectid, alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid + 2 * BITMAP_RANGE, cache->key.objectid + 2 * alignment,
BITMAP_RANGE); alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid + BITMAP_RANGE, cache->key.objectid + alignment,
BITMAP_RANGE); alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
...@@ -391,12 +394,13 @@ static int test_merge_both(struct btrfs_trans_handle *trans, ...@@ -391,12 +394,13 @@ static int test_merge_both(struct btrfs_trans_handle *trans,
static int test_merge_none(struct btrfs_trans_handle *trans, static int test_merge_none(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info, struct btrfs_fs_info *fs_info,
struct btrfs_block_group_cache *cache, struct btrfs_block_group_cache *cache,
struct btrfs_path *path) struct btrfs_path *path,
u32 alignment)
{ {
struct free_space_extent extents[] = { const struct free_space_extent extents[] = {
{cache->key.objectid, BITMAP_RANGE}, {cache->key.objectid, alignment},
{cache->key.objectid + 2 * BITMAP_RANGE, BITMAP_RANGE}, {cache->key.objectid + 2 * alignment, alignment},
{cache->key.objectid + 4 * BITMAP_RANGE, BITMAP_RANGE}, {cache->key.objectid + 4 * alignment, alignment},
}; };
int ret; int ret;
...@@ -409,23 +413,23 @@ static int test_merge_none(struct btrfs_trans_handle *trans, ...@@ -409,23 +413,23 @@ static int test_merge_none(struct btrfs_trans_handle *trans,
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid, BITMAP_RANGE); cache->key.objectid, alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid + 4 * BITMAP_RANGE, cache->key.objectid + 4 * alignment,
BITMAP_RANGE); alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
} }
ret = __add_to_free_space_tree(trans, fs_info, cache, path, ret = __add_to_free_space_tree(trans, fs_info, cache, path,
cache->key.objectid + 2 * BITMAP_RANGE, cache->key.objectid + 2 * alignment,
BITMAP_RANGE); alignment);
if (ret) { if (ret) {
test_msg("Could not add free space\n"); test_msg("Could not add free space\n");
return ret; return ret;
...@@ -438,10 +442,11 @@ static int test_merge_none(struct btrfs_trans_handle *trans, ...@@ -438,10 +442,11 @@ static int test_merge_none(struct btrfs_trans_handle *trans,
typedef int (*test_func_t)(struct btrfs_trans_handle *, typedef int (*test_func_t)(struct btrfs_trans_handle *,
struct btrfs_fs_info *, struct btrfs_fs_info *,
struct btrfs_block_group_cache *, struct btrfs_block_group_cache *,
struct btrfs_path *); struct btrfs_path *,
u32 alignment);
static int run_test(test_func_t test_func, int bitmaps, static int run_test(test_func_t test_func, int bitmaps, u32 sectorsize,
u32 sectorsize, u32 nodesize) u32 nodesize, u32 alignment)
{ {
struct btrfs_fs_info *fs_info; struct btrfs_fs_info *fs_info;
struct btrfs_root *root = NULL; struct btrfs_root *root = NULL;
...@@ -480,7 +485,7 @@ static int run_test(test_func_t test_func, int bitmaps, ...@@ -480,7 +485,7 @@ static int run_test(test_func_t test_func, int bitmaps,
btrfs_set_header_nritems(root->node, 0); btrfs_set_header_nritems(root->node, 0);
root->alloc_bytenr += 2 * nodesize; root->alloc_bytenr += 2 * nodesize;
cache = btrfs_alloc_dummy_block_group(8 * BITMAP_RANGE, sectorsize); cache = btrfs_alloc_dummy_block_group(8 * alignment, sectorsize);
if (!cache) { if (!cache) {
test_msg("Couldn't allocate dummy block group cache\n"); test_msg("Couldn't allocate dummy block group cache\n");
ret = -ENOMEM; ret = -ENOMEM;
...@@ -514,7 +519,7 @@ static int run_test(test_func_t test_func, int bitmaps, ...@@ -514,7 +519,7 @@ static int run_test(test_func_t test_func, int bitmaps,
} }
} }
ret = test_func(&trans, root->fs_info, cache, path); ret = test_func(&trans, root->fs_info, cache, path, alignment);
if (ret) if (ret)
goto out; goto out;
...@@ -539,15 +544,27 @@ static int run_test(test_func_t test_func, int bitmaps, ...@@ -539,15 +544,27 @@ static int run_test(test_func_t test_func, int bitmaps,
return ret; return ret;
} }
static int run_test_both_formats(test_func_t test_func, static int run_test_both_formats(test_func_t test_func, u32 sectorsize,
u32 sectorsize, u32 nodesize) u32 nodesize, u32 alignment)
{ {
int test_ret = 0;
int ret; int ret;
ret = run_test(test_func, 0, sectorsize, nodesize); ret = run_test(test_func, 0, sectorsize, nodesize, alignment);
if (ret) if (ret) {
return ret; test_msg("%pf failed with extents, sectorsize=%u, nodesize=%u, alignment=%u\n",
return run_test(test_func, 1, sectorsize, nodesize); test_func, sectorsize, nodesize, alignment);
test_ret = ret;
}
ret = run_test(test_func, 1, sectorsize, nodesize, alignment);
if (ret) {
test_msg("%pf failed with bitmaps, sectorsize=%u, nodesize=%u, alignment=%u\n",
test_func, sectorsize, nodesize, alignment);
test_ret = ret;
}
return test_ret;
} }
int btrfs_test_free_space_tree(u32 sectorsize, u32 nodesize) int btrfs_test_free_space_tree(u32 sectorsize, u32 nodesize)
...@@ -563,18 +580,30 @@ int btrfs_test_free_space_tree(u32 sectorsize, u32 nodesize) ...@@ -563,18 +580,30 @@ int btrfs_test_free_space_tree(u32 sectorsize, u32 nodesize)
test_merge_both, test_merge_both,
test_merge_none, test_merge_none,
}; };
u32 bitmap_alignment;
int test_ret = 0;
int i; int i;
/*
* Align some operations to a page to flush out bugs in the extent
* buffer bitmap handling of highmem.
*/
bitmap_alignment = BTRFS_FREE_SPACE_BITMAP_BITS * PAGE_SIZE;
test_msg("Running free space tree tests\n"); test_msg("Running free space tree tests\n");
for (i = 0; i < ARRAY_SIZE(tests); i++) { for (i = 0; i < ARRAY_SIZE(tests); i++) {
int ret = run_test_both_formats(tests[i], sectorsize, int ret;
nodesize);
if (ret) { ret = run_test_both_formats(tests[i], sectorsize, nodesize,
test_msg("%pf : sectorsize %u failed\n", sectorsize);
tests[i], sectorsize); if (ret)
return ret; test_ret = ret;
}
ret = run_test_both_formats(tests[i], sectorsize, nodesize,
bitmap_alignment);
if (ret)
test_ret = ret;
} }
return 0; return test_ret;
} }
...@@ -239,7 +239,17 @@ struct btrfs_ioctl_fs_info_args { ...@@ -239,7 +239,17 @@ struct btrfs_ioctl_fs_info_args {
* Used by: * Used by:
* struct btrfs_ioctl_feature_flags * struct btrfs_ioctl_feature_flags
*/ */
#define BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE (1ULL << 0) #define BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE (1ULL << 0)
/*
* Older kernels (< 4.9) on big-endian systems produced broken free space tree
* bitmaps, and btrfs-progs also used to corrupt the free space tree (versions
* < 4.7.3). If this bit is clear, then the free space tree cannot be trusted.
* btrfs-progs can also intentionally clear this bit to ask the kernel to
* rebuild the free space tree, however this might not work on older kernels
* that do not know about this bit. If not sure, clear the cache manually on
* first mount when booting older kernel versions.
*/
#define BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID (1ULL << 1)
#define BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF (1ULL << 0) #define BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF (1ULL << 0)
#define BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL (1ULL << 1) #define BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL (1ULL << 1)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment