aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ext4/inline.c
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 /fs/ext4/inline.c
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>
Diffstat (limited to 'fs/ext4/inline.c')
-rw-r--r--fs/ext4/inline.c109
1 files changed, 108 insertions, 1 deletions
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)