Skip to content

Commit 07785e5

Browse files
adam900710kdave
authored andcommitted
btrfs-progs: convert: fix inline extent size for symlink
[BUG] Sometimes test case btrfs/012 fails randomly, with the failure to read a symlink: QA output created by 012 Checking converted btrfs against the original one: -OK +readlink: Structure needs cleaning Checking saved ext2 image against the original one: OK Furthermore, this will trigger a kernel error message: BTRFS critical (device dm-2): regular/prealloc extent found for non-regular inode 133081 [CAUSE] For that specific inode 133081, the tree dump looks like this: item 127 key (133081 INODE_ITEM 0) itemoff 40984 itemsize 160 generation 1 transid 1 size 4095 nbytes 4096 block group 0 mode 120777 links 1 uid 0 gid 0 rdev 0 sequence 0 flags 0x0(none) item 128 key (133081 INODE_REF 133080) itemoff 40972 itemsize 12 index 2 namelen 2 name: l3 item 129 key (133081 EXTENT_DATA 0) itemoff 40919 itemsize 53 generation 4 type 1 (regular) extent data disk byte 2147483648 nr 38080512 extent data offset 37974016 nr 4096 ram 38080512 extent compression 0 (none) Note that, the symlink inode size is 4095 at the max size (PATH_MAX, removing the terminating NUL). But the nbytes is 4096, exactly matching the sector size of the btrfs. Thus it results the creation of a regular extent, but for btrfs we do not accept a symlink with a regular/preallocated extent, thus kernel rejects such read and failed the readlink call. The root cause is in the convert code, where for symlinks we always create a data extent with its size + 1, causing the above problem. I guess the original code is to handle the terminating NUL, but in btrfs we never need to store the terminating NUL for inline extents nor file names. Thus this pitfall in btrfs-convert leads to the above invalid data extent and fail the test case. [FIX] - Fix the ext2 and reiserfs symbolic link creation code To remove the terminating NUL. - Add extra checks for the size of a symbolic link Btrfs has extra limits on the size of a symbolic link, as btrfs must store symbolic link targets as inlined extents. This means for 4K node sized btrfs, the size limit is smaller than the usual PATH_MAX - 1 (only around 4000 bytes instead of 4095). So for certain nodesize, some filesystems can not be converted to btrfs. (this should be rare, because the default nodesize is 16K already) - Split the symbolic link and inline data extent size checks For symbolic links the real limit is PATH_MAX - 1 (removing the terminating NUL), but for inline data extents the limit is sectorsize - 1, which can be different from 4096 - 1 (e.g. 64K sector size). Pull-request: #884 Signed-off-by: Qu Wenruo <[email protected]>
1 parent e42ce38 commit 07785e5

File tree

4 files changed

+55
-8
lines changed

4 files changed

+55
-8
lines changed

Diff for: convert/source-ext2.c

+23-6
Original file line numberDiff line numberDiff line change
@@ -390,15 +390,28 @@ static int ext2_create_file_extents(struct btrfs_trans_handle *trans,
390390
ext2_filsys ext2_fs, ext2_ino_t ext2_ino,
391391
u32 convert_flags)
392392
{
393+
struct btrfs_fs_info *fs_info = trans->fs_info;
393394
int ret;
394395
char *buffer = NULL;
395396
errcode_t err;
396397
struct ext2_inode ext2_inode = { 0 };
397398
u32 last_block;
398399
u32 sectorsize = root->fs_info->sectorsize;
399400
u64 inode_size = btrfs_stack_inode_size(btrfs_inode);
401+
bool meet_inline_size_limit;
400402
struct blk_iterate_data data;
401403

404+
if (S_ISLNK(btrfs_stack_inode_mode(btrfs_inode))) {
405+
meet_inline_size_limit = inode_size <= btrfs_symlink_max_size(fs_info);
406+
if (!meet_inline_size_limit) {
407+
error("symlink too large for ext2 inode %u, has %llu max %u",
408+
ext2_ino, inode_size, btrfs_symlink_max_size(fs_info));
409+
return -ENAMETOOLONG;
410+
}
411+
} else {
412+
meet_inline_size_limit = inode_size <= btrfs_data_inline_max_size(fs_info);
413+
}
414+
402415
init_blk_iterate_data(&data, trans, root, btrfs_inode, objectid,
403416
convert_flags & CONVERT_FLAG_DATACSUM);
404417

@@ -430,8 +443,7 @@ static int ext2_create_file_extents(struct btrfs_trans_handle *trans,
430443
if (ret)
431444
goto fail;
432445
if ((convert_flags & CONVERT_FLAG_INLINE_DATA) && data.first_block == 0
433-
&& data.num_blocks > 0 && inode_size < sectorsize
434-
&& inode_size <= BTRFS_MAX_INLINE_DATA_SIZE(root->fs_info)) {
446+
&& data.num_blocks > 0 && meet_inline_size_limit) {
435447
u64 num_bytes = data.num_blocks * sectorsize;
436448
u64 disk_bytenr = data.disk_block * sectorsize;
437449
u64 nbytes;
@@ -476,21 +488,26 @@ static int ext2_create_symlink(struct btrfs_trans_handle *trans,
476488
int ret;
477489
char *pathname;
478490
u64 inode_size = btrfs_stack_inode_size(btrfs_inode);
491+
479492
if (ext2fs_inode_data_blocks2(ext2_fs, ext2_inode)) {
480-
btrfs_set_stack_inode_size(btrfs_inode, inode_size + 1);
493+
if (inode_size > btrfs_symlink_max_size(trans->fs_info)) {
494+
error("symlink too large for ext2 inode %u, has %llu max %u",
495+
ext2_ino, inode_size,
496+
btrfs_symlink_max_size(trans->fs_info));
497+
return -ENAMETOOLONG;
498+
}
481499
ret = ext2_create_file_extents(trans, root, objectid,
482500
btrfs_inode, ext2_fs, ext2_ino,
483501
CONVERT_FLAG_DATACSUM |
484502
CONVERT_FLAG_INLINE_DATA);
485-
btrfs_set_stack_inode_size(btrfs_inode, inode_size);
486503
return ret;
487504
}
488505

489506
pathname = (char *)&(ext2_inode->i_block[0]);
490507
BUG_ON(pathname[inode_size] != 0);
491508
ret = btrfs_insert_inline_extent(trans, root, objectid, 0,
492-
pathname, inode_size + 1);
493-
btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size + 1);
509+
pathname, inode_size);
510+
btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size);
494511
return ret;
495512
}
496513

Diff for: convert/source-reiserfs.c

+8-2
Original file line numberDiff line numberDiff line change
@@ -537,9 +537,15 @@ static int reiserfs_copy_symlink(struct btrfs_trans_handle *trans,
537537
symlink = tp_item_body(&path);
538538
len = get_ih_item_len(tp_item_head(&path));
539539

540+
if (len > btrfs_symlink_max_size(trans->fs_info)) {
541+
error("symlink too large, has %u max %u",
542+
len, btrfs_symlink_max_size(trans->fs_info));
543+
ret = -ENAMETOOLONG;
544+
goto fail;
545+
}
540546
ret = btrfs_insert_inline_extent(trans, root, objectid, 0,
541-
symlink, len + 1);
542-
btrfs_set_stack_inode_nbytes(btrfs_inode, len + 1);
547+
symlink, len);
548+
btrfs_set_stack_inode_nbytes(btrfs_inode, len);
543549
fail:
544550
pathrelse(&path);
545551
return ret;

Diff for: kernel-shared/file-item.c

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "kernel-shared/extent_io.h"
2727
#include "kernel-shared/uapi/btrfs.h"
2828
#include "common/internal.h"
29+
#include "common/messages.h"
2930

3031
#define MAX_CSUM_ITEMS(r, size) ((((BTRFS_LEAF_DATA_SIZE(r->fs_info) - \
3132
sizeof(struct btrfs_item) * 2) / \
@@ -88,6 +89,7 @@ int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans,
8889
struct btrfs_root *root, u64 objectid,
8990
u64 offset, const char *buffer, size_t size)
9091
{
92+
struct btrfs_fs_info *fs_info = trans->fs_info;
9193
struct btrfs_key key;
9294
struct btrfs_path *path;
9395
struct extent_buffer *leaf;
@@ -97,6 +99,10 @@ int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans,
9799
int err = 0;
98100
int ret;
99101

102+
if (size > max(btrfs_symlink_max_size(fs_info),
103+
btrfs_data_inline_max_size(fs_info)))
104+
return -EUCLEAN;
105+
100106
path = btrfs_alloc_path();
101107
if (!path)
102108
return -ENOMEM;

Diff for: kernel-shared/file-item.h

+18
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,23 @@ int btrfs_csum_file_block(struct btrfs_trans_handle *trans, u64 logical,
9292
int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans,
9393
struct btrfs_root *root, u64 objectid,
9494
u64 offset, const char *buffer, size_t size);
95+
/*
96+
* For symlink we allow up to PATH_MAX - 1 (PATH_MAX includes the terminating NUL,
97+
* but fs doesn't store that terminating NUL).
98+
*
99+
* But for inlined data extents, the up limit is sectorsize - 1 (inclusive), or a
100+
* regular extent should be created instead.
101+
*/
102+
static inline u32 btrfs_symlink_max_size(struct btrfs_fs_info *fs_info)
103+
{
104+
return min_t(u32, BTRFS_MAX_INLINE_DATA_SIZE(fs_info),
105+
PATH_MAX - 1);
106+
}
107+
108+
static inline u32 btrfs_data_inline_max_size(struct btrfs_fs_info *fs_info)
109+
{
110+
return min_t(u32, BTRFS_MAX_INLINE_DATA_SIZE(fs_info),
111+
fs_info->sectorsize - 1);
112+
}
95113

96114
#endif

0 commit comments

Comments
 (0)