aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ext4
diff options
context:
space:
mode:
authorTao Ma <boyu.mt@taobao.com>2013-04-19 17:55:33 -0400
committerTheodore Ts'o <tytso@mit.edu>2013-04-19 17:55:33 -0400
commitc4d8b0235aa98f8c26bf94d308be3fdd24154572 (patch)
tree9499f186e1e65df32a822d19cdcd92addb33e4a6 /fs/ext4
parent8af0f08227977079f8f227e74d27c59db2ab84f6 (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.c69
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 */
1399int ext4_read_inline_dir(struct file *filp, 1407int 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) {
1442revalidate: 1464revalidate:
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 }
1529out: 1562out:
1530 kfree(dir_buf); 1563 kfree(dir_buf);