diff options
author | Tao Ma <boyu.mt@taobao.com> | 2013-04-19 17:55:33 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2013-04-19 17:55:33 -0400 |
commit | c4d8b0235aa98f8c26bf94d308be3fdd24154572 (patch) | |
tree | 9499f186e1e65df32a822d19cdcd92addb33e4a6 /fs/ext4 | |
parent | 8af0f08227977079f8f227e74d27c59db2ab84f6 (diff) |
ext4: fix readdir error in case 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, we may meet with duplicate dir entries as the offset
for inline dir and non-inline one is quite different.
This patch just try to resolve this problem if dir_index
is disabled. In this case, f_pos is the real offset with
the dir block, so for inline dir, we just pretend as if
we are a dir block and returns the offset like a norml
dir block does.
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')
-rw-r--r-- | fs/ext4/inline.c | 69 |
1 files changed, 51 insertions, 18 deletions
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index abf8b6278c3a..3e2bf873e8a8 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c | |||
@@ -1396,6 +1396,14 @@ out: | |||
1396 | return ret; | 1396 | return ret; |
1397 | } | 1397 | } |
1398 | 1398 | ||
1399 | /* | ||
1400 | * So this function is called when the volume is mkfsed with | ||
1401 | * dir_index disabled. In order to keep f_pos persistent | ||
1402 | * after we convert from an inlined dir to a blocked based, | ||
1403 | * we just pretend that we are a normal dir and return the | ||
1404 | * offset as if '.' and '..' really take place. | ||
1405 | * | ||
1406 | */ | ||
1399 | int ext4_read_inline_dir(struct file *filp, | 1407 | int ext4_read_inline_dir(struct file *filp, |
1400 | void *dirent, filldir_t filldir, | 1408 | void *dirent, filldir_t filldir, |
1401 | int *has_inline_data) | 1409 | int *has_inline_data) |
@@ -1409,6 +1417,7 @@ int ext4_read_inline_dir(struct file *filp, | |||
1409 | int ret, inline_size = 0; | 1417 | int ret, inline_size = 0; |
1410 | struct ext4_iloc iloc; | 1418 | struct ext4_iloc iloc; |
1411 | void *dir_buf = NULL; | 1419 | void *dir_buf = NULL; |
1420 | int dotdot_offset, dotdot_size, extra_offset, extra_size; | ||
1412 | 1421 | ||
1413 | ret = ext4_get_inode_loc(inode, &iloc); | 1422 | ret = ext4_get_inode_loc(inode, &iloc); |
1414 | if (ret) | 1423 | if (ret) |
@@ -1437,8 +1446,21 @@ int ext4_read_inline_dir(struct file *filp, | |||
1437 | sb = inode->i_sb; | 1446 | sb = inode->i_sb; |
1438 | stored = 0; | 1447 | stored = 0; |
1439 | parent_ino = le32_to_cpu(((struct ext4_dir_entry_2 *)dir_buf)->inode); | 1448 | parent_ino = le32_to_cpu(((struct ext4_dir_entry_2 *)dir_buf)->inode); |
1449 | offset = filp->f_pos; | ||
1450 | |||
1451 | /* | ||
1452 | * dotdot_offset and dotdot_size is the real offset and | ||
1453 | * size for ".." and "." if the dir is block based while | ||
1454 | * the real size for them are only EXT4_INLINE_DOTDOT_SIZE. | ||
1455 | * So we will use extra_offset and extra_size to indicate them | ||
1456 | * during the inline dir iteration. | ||
1457 | */ | ||
1458 | dotdot_offset = EXT4_DIR_REC_LEN(1); | ||
1459 | dotdot_size = dotdot_offset + EXT4_DIR_REC_LEN(2); | ||
1460 | extra_offset = dotdot_size - EXT4_INLINE_DOTDOT_SIZE; | ||
1461 | extra_size = extra_offset + inline_size; | ||
1440 | 1462 | ||
1441 | while (!error && !stored && filp->f_pos < inode->i_size) { | 1463 | while (!error && !stored && filp->f_pos < extra_size) { |
1442 | revalidate: | 1464 | revalidate: |
1443 | /* | 1465 | /* |
1444 | * If the version has changed since the last call to | 1466 | * If the version has changed since the last call to |
@@ -1447,15 +1469,23 @@ revalidate: | |||
1447 | * dir to make sure. | 1469 | * dir to make sure. |
1448 | */ | 1470 | */ |
1449 | if (filp->f_version != inode->i_version) { | 1471 | if (filp->f_version != inode->i_version) { |
1450 | for (i = 0; | 1472 | for (i = 0; i < extra_size && i < offset;) { |
1451 | i < inode->i_size && i < offset;) { | 1473 | /* |
1474 | * "." is with offset 0 and | ||
1475 | * ".." is dotdot_offset. | ||
1476 | */ | ||
1452 | if (!i) { | 1477 | if (!i) { |
1453 | /* skip "." and ".." if needed. */ | 1478 | i = dotdot_offset; |
1454 | i += EXT4_INLINE_DOTDOT_SIZE; | 1479 | continue; |
1480 | } else if (i == dotdot_offset) { | ||
1481 | i = dotdot_size; | ||
1455 | continue; | 1482 | continue; |
1456 | } | 1483 | } |
1484 | /* for other entry, the real offset in | ||
1485 | * the buf has to be tuned accordingly. | ||
1486 | */ | ||
1457 | de = (struct ext4_dir_entry_2 *) | 1487 | de = (struct ext4_dir_entry_2 *) |
1458 | (dir_buf + i); | 1488 | (dir_buf + i - extra_offset); |
1459 | /* It's too expensive to do a full | 1489 | /* It's too expensive to do a full |
1460 | * dirent test each time round this | 1490 | * dirent test each time round this |
1461 | * loop, but we do have to test at | 1491 | * loop, but we do have to test at |
@@ -1463,43 +1493,47 @@ revalidate: | |||
1463 | * failure will be detected in the | 1493 | * failure will be detected in the |
1464 | * dirent test below. */ | 1494 | * dirent test below. */ |
1465 | if (ext4_rec_len_from_disk(de->rec_len, | 1495 | if (ext4_rec_len_from_disk(de->rec_len, |
1466 | inline_size) < EXT4_DIR_REC_LEN(1)) | 1496 | extra_size) < EXT4_DIR_REC_LEN(1)) |
1467 | break; | 1497 | break; |
1468 | i += ext4_rec_len_from_disk(de->rec_len, | 1498 | i += ext4_rec_len_from_disk(de->rec_len, |
1469 | inline_size); | 1499 | extra_size); |
1470 | } | 1500 | } |
1471 | offset = i; | 1501 | offset = i; |
1472 | filp->f_pos = offset; | 1502 | filp->f_pos = offset; |
1473 | filp->f_version = inode->i_version; | 1503 | filp->f_version = inode->i_version; |
1474 | } | 1504 | } |
1475 | 1505 | ||
1476 | while (!error && filp->f_pos < inode->i_size) { | 1506 | while (!error && filp->f_pos < extra_size) { |
1477 | if (filp->f_pos == 0) { | 1507 | if (filp->f_pos == 0) { |
1478 | error = filldir(dirent, ".", 1, 0, inode->i_ino, | 1508 | error = filldir(dirent, ".", 1, 0, inode->i_ino, |
1479 | DT_DIR); | 1509 | DT_DIR); |
1480 | if (error) | 1510 | if (error) |
1481 | break; | 1511 | break; |
1482 | stored++; | 1512 | stored++; |
1513 | filp->f_pos = dotdot_offset; | ||
1514 | continue; | ||
1515 | } | ||
1483 | 1516 | ||
1484 | error = filldir(dirent, "..", 2, 0, parent_ino, | 1517 | if (filp->f_pos == dotdot_offset) { |
1485 | DT_DIR); | 1518 | error = filldir(dirent, "..", 2, |
1519 | dotdot_offset, | ||
1520 | parent_ino, DT_DIR); | ||
1486 | if (error) | 1521 | if (error) |
1487 | break; | 1522 | break; |
1488 | stored++; | 1523 | stored++; |
1489 | 1524 | ||
1490 | filp->f_pos = offset = EXT4_INLINE_DOTDOT_SIZE; | 1525 | filp->f_pos = dotdot_size; |
1491 | continue; | 1526 | continue; |
1492 | } | 1527 | } |
1493 | 1528 | ||
1494 | de = (struct ext4_dir_entry_2 *)(dir_buf + offset); | 1529 | de = (struct ext4_dir_entry_2 *) |
1530 | (dir_buf + filp->f_pos - extra_offset); | ||
1495 | if (ext4_check_dir_entry(inode, filp, de, | 1531 | if (ext4_check_dir_entry(inode, filp, de, |
1496 | iloc.bh, dir_buf, | 1532 | iloc.bh, dir_buf, |
1497 | inline_size, offset)) { | 1533 | extra_size, filp->f_pos)) { |
1498 | ret = stored; | 1534 | ret = stored; |
1499 | goto out; | 1535 | goto out; |
1500 | } | 1536 | } |
1501 | offset += ext4_rec_len_from_disk(de->rec_len, | ||
1502 | inline_size); | ||
1503 | if (le32_to_cpu(de->inode)) { | 1537 | if (le32_to_cpu(de->inode)) { |
1504 | /* We might block in the next section | 1538 | /* We might block in the next section |
1505 | * if the data destination is | 1539 | * if the data destination is |
@@ -1522,9 +1556,8 @@ revalidate: | |||
1522 | stored++; | 1556 | stored++; |
1523 | } | 1557 | } |
1524 | filp->f_pos += ext4_rec_len_from_disk(de->rec_len, | 1558 | filp->f_pos += ext4_rec_len_from_disk(de->rec_len, |
1525 | inline_size); | 1559 | extra_size); |
1526 | } | 1560 | } |
1527 | offset = 0; | ||
1528 | } | 1561 | } |
1529 | out: | 1562 | out: |
1530 | kfree(dir_buf); | 1563 | kfree(dir_buf); |