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 /fs/ext4/inline.c | |
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>
Diffstat (limited to 'fs/ext4/inline.c')
-rw-r--r-- | fs/ext4/inline.c | 109 |
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 | ||
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) |