aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTao Ma <boyu.mt@taobao.com>2013-04-19 17:53:09 -0400
committerTheodore Ts'o <tytso@mit.edu>2013-04-19 17:53:09 -0400
commit8af0f08227977079f8f227e74d27c59db2ab84f6 (patch)
tree8db354c62e7d529bf4b5cdb0f2800a065cc1cd20
parent28daf4fae8693d4a285123494899fe01950cba50 (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.c20
-rw-r--r--fs/ext4/ext4.h23
-rw-r--r--fs/ext4/inline.c109
-rw-r--r--fs/ext4/namei.c29
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,
2518extern int ext4_read_inline_dir(struct file *filp, 2518extern 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);
2521extern 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);
2521extern struct buffer_head *ext4_find_inline_entry(struct inode *dir, 2526extern 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,
2554extern int ext4_handle_dirty_dirent_node(handle_t *handle, 2559extern 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
2563static 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
2573static 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 */
2559extern const struct inode_operations ext4_symlink_inode_operations; 2582extern 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
24int ext4_get_inline_size(struct inode *inode) 25int 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 */
1298int 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;
1393out:
1394 kfree(dir_buf);
1395 brelse(iloc.bh);
1396 return ret;
1397}
1398
1292int ext4_read_inline_dir(struct file *filp, 1399int 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
1460static 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
1470static 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.