diff options
author | Tao Ma <boyu.mt@taobao.com> | 2013-04-19 17:53:09 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2013-04-19 17:53:09 -0400 |
commit | 8af0f08227977079f8f227e74d27c59db2ab84f6 (patch) | |
tree | 8db354c62e7d529bf4b5cdb0f2800a065cc1cd20 | |
parent | 28daf4fae8693d4a285123494899fe01950cba50 (diff) |
ext4: fix readdir error in the case of inline_data+dir_index
Zach reported a problem that if inline data is enabled, we don't
tell the difference between the offset of '.' and '..'. And a
getdents will fail if the user only want to get '.' and what's worse,
if there is a conversion happens when the user calls getdents
many times, he/she may get the same entry twice.
In theory, a dir block would also fail if it is converted to a
hashed-index based dir since f_pos will become a hash value, not the
real one, but it doesn't happen. And a deep investigation shows that
we uses a hash based solution even for a normal dir if the dir_index
feature is enabled.
So this patch just adds a new htree_inlinedir_to_tree for inline dir,
and if we find that the hash index is supported, we will do like what
we do for a dir block.
Reported-by: Zach Brown <zab@redhat.com>
Signed-off-by: Tao Ma <boyu.mt@taobao.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
-rw-r--r-- | fs/ext4/dir.c | 20 | ||||
-rw-r--r-- | fs/ext4/ext4.h | 23 | ||||
-rw-r--r-- | fs/ext4/inline.c | 109 | ||||
-rw-r--r-- | fs/ext4/namei.c | 29 |
4 files changed, 153 insertions, 28 deletions
diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c index d8cd1f0f4661..f8d56e4254e0 100644 --- a/fs/ext4/dir.c +++ b/fs/ext4/dir.c | |||
@@ -46,7 +46,8 @@ static int is_dx_dir(struct inode *inode) | |||
46 | if (EXT4_HAS_COMPAT_FEATURE(inode->i_sb, | 46 | if (EXT4_HAS_COMPAT_FEATURE(inode->i_sb, |
47 | EXT4_FEATURE_COMPAT_DIR_INDEX) && | 47 | EXT4_FEATURE_COMPAT_DIR_INDEX) && |
48 | ((ext4_test_inode_flag(inode, EXT4_INODE_INDEX)) || | 48 | ((ext4_test_inode_flag(inode, EXT4_INODE_INDEX)) || |
49 | ((inode->i_size >> sb->s_blocksize_bits) == 1))) | 49 | ((inode->i_size >> sb->s_blocksize_bits) == 1) || |
50 | ext4_has_inline_data(inode))) | ||
50 | return 1; | 51 | return 1; |
51 | 52 | ||
52 | return 0; | 53 | return 0; |
@@ -115,14 +116,6 @@ static int ext4_readdir(struct file *filp, | |||
115 | int ret = 0; | 116 | int ret = 0; |
116 | int dir_has_error = 0; | 117 | int dir_has_error = 0; |
117 | 118 | ||
118 | if (ext4_has_inline_data(inode)) { | ||
119 | int has_inline_data = 1; | ||
120 | ret = ext4_read_inline_dir(filp, dirent, filldir, | ||
121 | &has_inline_data); | ||
122 | if (has_inline_data) | ||
123 | return ret; | ||
124 | } | ||
125 | |||
126 | if (is_dx_dir(inode)) { | 119 | if (is_dx_dir(inode)) { |
127 | err = ext4_dx_readdir(filp, dirent, filldir); | 120 | err = ext4_dx_readdir(filp, dirent, filldir); |
128 | if (err != ERR_BAD_DX_DIR) { | 121 | if (err != ERR_BAD_DX_DIR) { |
@@ -136,6 +129,15 @@ static int ext4_readdir(struct file *filp, | |||
136 | ext4_clear_inode_flag(file_inode(filp), | 129 | ext4_clear_inode_flag(file_inode(filp), |
137 | EXT4_INODE_INDEX); | 130 | EXT4_INODE_INDEX); |
138 | } | 131 | } |
132 | |||
133 | if (ext4_has_inline_data(inode)) { | ||
134 | int has_inline_data = 1; | ||
135 | ret = ext4_read_inline_dir(filp, dirent, filldir, | ||
136 | &has_inline_data); | ||
137 | if (has_inline_data) | ||
138 | return ret; | ||
139 | } | ||
140 | |||
139 | stored = 0; | 141 | stored = 0; |
140 | offset = filp->f_pos & (sb->s_blocksize - 1); | 142 | offset = filp->f_pos & (sb->s_blocksize - 1); |
141 | 143 | ||
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 779d26b7beff..0aabb344b02e 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h | |||
@@ -2518,6 +2518,11 @@ extern int ext4_try_create_inline_dir(handle_t *handle, | |||
2518 | extern int ext4_read_inline_dir(struct file *filp, | 2518 | extern int ext4_read_inline_dir(struct file *filp, |
2519 | void *dirent, filldir_t filldir, | 2519 | void *dirent, filldir_t filldir, |
2520 | int *has_inline_data); | 2520 | int *has_inline_data); |
2521 | extern int htree_inlinedir_to_tree(struct file *dir_file, | ||
2522 | struct inode *dir, ext4_lblk_t block, | ||
2523 | struct dx_hash_info *hinfo, | ||
2524 | __u32 start_hash, __u32 start_minor_hash, | ||
2525 | int *has_inline_data); | ||
2521 | extern struct buffer_head *ext4_find_inline_entry(struct inode *dir, | 2526 | extern struct buffer_head *ext4_find_inline_entry(struct inode *dir, |
2522 | const struct qstr *d_name, | 2527 | const struct qstr *d_name, |
2523 | struct ext4_dir_entry_2 **res_dir, | 2528 | struct ext4_dir_entry_2 **res_dir, |
@@ -2554,6 +2559,24 @@ extern void initialize_dirent_tail(struct ext4_dir_entry_tail *t, | |||
2554 | extern int ext4_handle_dirty_dirent_node(handle_t *handle, | 2559 | extern int ext4_handle_dirty_dirent_node(handle_t *handle, |
2555 | struct inode *inode, | 2560 | struct inode *inode, |
2556 | struct buffer_head *bh); | 2561 | struct buffer_head *bh); |
2562 | #define S_SHIFT 12 | ||
2563 | static unsigned char ext4_type_by_mode[S_IFMT >> S_SHIFT] = { | ||
2564 | [S_IFREG >> S_SHIFT] = EXT4_FT_REG_FILE, | ||
2565 | [S_IFDIR >> S_SHIFT] = EXT4_FT_DIR, | ||
2566 | [S_IFCHR >> S_SHIFT] = EXT4_FT_CHRDEV, | ||
2567 | [S_IFBLK >> S_SHIFT] = EXT4_FT_BLKDEV, | ||
2568 | [S_IFIFO >> S_SHIFT] = EXT4_FT_FIFO, | ||
2569 | [S_IFSOCK >> S_SHIFT] = EXT4_FT_SOCK, | ||
2570 | [S_IFLNK >> S_SHIFT] = EXT4_FT_SYMLINK, | ||
2571 | }; | ||
2572 | |||
2573 | static inline void ext4_set_de_type(struct super_block *sb, | ||
2574 | struct ext4_dir_entry_2 *de, | ||
2575 | umode_t mode) { | ||
2576 | if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_FILETYPE)) | ||
2577 | de->file_type = ext4_type_by_mode[(mode & S_IFMT)>>S_SHIFT]; | ||
2578 | } | ||
2579 | |||
2557 | 2580 | ||
2558 | /* symlink.c */ | 2581 | /* symlink.c */ |
2559 | extern const struct inode_operations ext4_symlink_inode_operations; | 2582 | extern const struct inode_operations ext4_symlink_inode_operations; |
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index c0fd1a123f7d..abf8b6278c3a 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c | |||
@@ -19,7 +19,8 @@ | |||
19 | 19 | ||
20 | #define EXT4_XATTR_SYSTEM_DATA "data" | 20 | #define EXT4_XATTR_SYSTEM_DATA "data" |
21 | #define EXT4_MIN_INLINE_DATA_SIZE ((sizeof(__le32) * EXT4_N_BLOCKS)) | 21 | #define EXT4_MIN_INLINE_DATA_SIZE ((sizeof(__le32) * EXT4_N_BLOCKS)) |
22 | #define EXT4_INLINE_DOTDOT_SIZE 4 | 22 | #define EXT4_INLINE_DOTDOT_OFFSET 2 |
23 | #define EXT4_INLINE_DOTDOT_SIZE 4 | ||
23 | 24 | ||
24 | int ext4_get_inline_size(struct inode *inode) | 25 | int ext4_get_inline_size(struct inode *inode) |
25 | { | 26 | { |
@@ -1289,6 +1290,112 @@ out: | |||
1289 | return ret; | 1290 | return ret; |
1290 | } | 1291 | } |
1291 | 1292 | ||
1293 | /* | ||
1294 | * This function fills a red-black tree with information from an | ||
1295 | * inlined dir. It returns the number directory entries loaded | ||
1296 | * into the tree. If there is an error it is returned in err. | ||
1297 | */ | ||
1298 | int htree_inlinedir_to_tree(struct file *dir_file, | ||
1299 | struct inode *dir, ext4_lblk_t block, | ||
1300 | struct dx_hash_info *hinfo, | ||
1301 | __u32 start_hash, __u32 start_minor_hash, | ||
1302 | int *has_inline_data) | ||
1303 | { | ||
1304 | int err = 0, count = 0; | ||
1305 | unsigned int parent_ino; | ||
1306 | int pos; | ||
1307 | struct ext4_dir_entry_2 *de; | ||
1308 | struct inode *inode = file_inode(dir_file); | ||
1309 | int ret, inline_size = 0; | ||
1310 | struct ext4_iloc iloc; | ||
1311 | void *dir_buf = NULL; | ||
1312 | struct ext4_dir_entry_2 fake; | ||
1313 | |||
1314 | ret = ext4_get_inode_loc(inode, &iloc); | ||
1315 | if (ret) | ||
1316 | return ret; | ||
1317 | |||
1318 | down_read(&EXT4_I(inode)->xattr_sem); | ||
1319 | if (!ext4_has_inline_data(inode)) { | ||
1320 | up_read(&EXT4_I(inode)->xattr_sem); | ||
1321 | *has_inline_data = 0; | ||
1322 | goto out; | ||
1323 | } | ||
1324 | |||
1325 | inline_size = ext4_get_inline_size(inode); | ||
1326 | dir_buf = kmalloc(inline_size, GFP_NOFS); | ||
1327 | if (!dir_buf) { | ||
1328 | ret = -ENOMEM; | ||
1329 | up_read(&EXT4_I(inode)->xattr_sem); | ||
1330 | goto out; | ||
1331 | } | ||
1332 | |||
1333 | ret = ext4_read_inline_data(inode, dir_buf, inline_size, &iloc); | ||
1334 | up_read(&EXT4_I(inode)->xattr_sem); | ||
1335 | if (ret < 0) | ||
1336 | goto out; | ||
1337 | |||
1338 | pos = 0; | ||
1339 | parent_ino = le32_to_cpu(((struct ext4_dir_entry_2 *)dir_buf)->inode); | ||
1340 | while (pos < inline_size) { | ||
1341 | /* | ||
1342 | * As inlined dir doesn't store any information about '.' and | ||
1343 | * only the inode number of '..' is stored, we have to handle | ||
1344 | * them differently. | ||
1345 | */ | ||
1346 | if (pos == 0) { | ||
1347 | fake.inode = cpu_to_le32(inode->i_ino); | ||
1348 | fake.name_len = 1; | ||
1349 | strcpy(fake.name, "."); | ||
1350 | fake.rec_len = ext4_rec_len_to_disk( | ||
1351 | EXT4_DIR_REC_LEN(fake.name_len), | ||
1352 | inline_size); | ||
1353 | ext4_set_de_type(inode->i_sb, &fake, S_IFDIR); | ||
1354 | de = &fake; | ||
1355 | pos = EXT4_INLINE_DOTDOT_OFFSET; | ||
1356 | } else if (pos == EXT4_INLINE_DOTDOT_OFFSET) { | ||
1357 | fake.inode = cpu_to_le32(parent_ino); | ||
1358 | fake.name_len = 2; | ||
1359 | strcpy(fake.name, ".."); | ||
1360 | fake.rec_len = ext4_rec_len_to_disk( | ||
1361 | EXT4_DIR_REC_LEN(fake.name_len), | ||
1362 | inline_size); | ||
1363 | ext4_set_de_type(inode->i_sb, &fake, S_IFDIR); | ||
1364 | de = &fake; | ||
1365 | pos = EXT4_INLINE_DOTDOT_SIZE; | ||
1366 | } else { | ||
1367 | de = (struct ext4_dir_entry_2 *)(dir_buf + pos); | ||
1368 | pos += ext4_rec_len_from_disk(de->rec_len, inline_size); | ||
1369 | if (ext4_check_dir_entry(inode, dir_file, de, | ||
1370 | iloc.bh, dir_buf, | ||
1371 | inline_size, pos)) { | ||
1372 | ret = count; | ||
1373 | goto out; | ||
1374 | } | ||
1375 | } | ||
1376 | |||
1377 | ext4fs_dirhash(de->name, de->name_len, hinfo); | ||
1378 | if ((hinfo->hash < start_hash) || | ||
1379 | ((hinfo->hash == start_hash) && | ||
1380 | (hinfo->minor_hash < start_minor_hash))) | ||
1381 | continue; | ||
1382 | if (de->inode == 0) | ||
1383 | continue; | ||
1384 | err = ext4_htree_store_dirent(dir_file, | ||
1385 | hinfo->hash, hinfo->minor_hash, de); | ||
1386 | if (err) { | ||
1387 | count = err; | ||
1388 | goto out; | ||
1389 | } | ||
1390 | count++; | ||
1391 | } | ||
1392 | ret = count; | ||
1393 | out: | ||
1394 | kfree(dir_buf); | ||
1395 | brelse(iloc.bh); | ||
1396 | return ret; | ||
1397 | } | ||
1398 | |||
1292 | int ext4_read_inline_dir(struct file *filp, | 1399 | int ext4_read_inline_dir(struct file *filp, |
1293 | void *dirent, filldir_t filldir, | 1400 | void *dirent, filldir_t filldir, |
1294 | int *has_inline_data) | 1401 | int *has_inline_data) |
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 955c907fc980..6653fc35ecb7 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c | |||
@@ -972,6 +972,17 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash, | |||
972 | hinfo.hash_version += | 972 | hinfo.hash_version += |
973 | EXT4_SB(dir->i_sb)->s_hash_unsigned; | 973 | EXT4_SB(dir->i_sb)->s_hash_unsigned; |
974 | hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed; | 974 | hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed; |
975 | if (ext4_has_inline_data(dir)) { | ||
976 | int has_inline_data = 1; | ||
977 | count = htree_inlinedir_to_tree(dir_file, dir, 0, | ||
978 | &hinfo, start_hash, | ||
979 | start_minor_hash, | ||
980 | &has_inline_data); | ||
981 | if (has_inline_data) { | ||
982 | *next_hash = ~0; | ||
983 | return count; | ||
984 | } | ||
985 | } | ||
975 | count = htree_dirblock_to_tree(dir_file, dir, 0, &hinfo, | 986 | count = htree_dirblock_to_tree(dir_file, dir, 0, &hinfo, |
976 | start_hash, start_minor_hash); | 987 | start_hash, start_minor_hash); |
977 | *next_hash = ~0; | 988 | *next_hash = ~0; |
@@ -1456,24 +1467,6 @@ struct dentry *ext4_get_parent(struct dentry *child) | |||
1456 | return d_obtain_alias(ext4_iget(child->d_inode->i_sb, ino)); | 1467 | return d_obtain_alias(ext4_iget(child->d_inode->i_sb, ino)); |
1457 | } | 1468 | } |
1458 | 1469 | ||
1459 | #define S_SHIFT 12 | ||
1460 | static unsigned char ext4_type_by_mode[S_IFMT >> S_SHIFT] = { | ||
1461 | [S_IFREG >> S_SHIFT] = EXT4_FT_REG_FILE, | ||
1462 | [S_IFDIR >> S_SHIFT] = EXT4_FT_DIR, | ||
1463 | [S_IFCHR >> S_SHIFT] = EXT4_FT_CHRDEV, | ||
1464 | [S_IFBLK >> S_SHIFT] = EXT4_FT_BLKDEV, | ||
1465 | [S_IFIFO >> S_SHIFT] = EXT4_FT_FIFO, | ||
1466 | [S_IFSOCK >> S_SHIFT] = EXT4_FT_SOCK, | ||
1467 | [S_IFLNK >> S_SHIFT] = EXT4_FT_SYMLINK, | ||
1468 | }; | ||
1469 | |||
1470 | static inline void ext4_set_de_type(struct super_block *sb, | ||
1471 | struct ext4_dir_entry_2 *de, | ||
1472 | umode_t mode) { | ||
1473 | if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_FILETYPE)) | ||
1474 | de->file_type = ext4_type_by_mode[(mode & S_IFMT)>>S_SHIFT]; | ||
1475 | } | ||
1476 | |||
1477 | /* | 1470 | /* |
1478 | * Move count entries from end of map between two memory locations. | 1471 | * Move count entries from end of map between two memory locations. |
1479 | * Returns pointer to last entry moved. | 1472 | * Returns pointer to last entry moved. |