提交 c4d8b023 编写于 作者: T Tao Ma 提交者: Theodore Ts'o

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: NZach Brown <zab@redhat.com>
Signed-off-by: NTao Ma <boyu.mt@taobao.com>
Signed-off-by: N"Theodore Ts'o" <tytso@mit.edu>
上级 8af0f082
...@@ -1396,6 +1396,14 @@ int htree_inlinedir_to_tree(struct file *dir_file, ...@@ -1396,6 +1396,14 @@ int htree_inlinedir_to_tree(struct file *dir_file,
return ret; return ret;
} }
/*
* So this function is called when the volume is mkfsed with
* dir_index disabled. In order to keep f_pos persistent
* after we convert from an inlined dir to a blocked based,
* we just pretend that we are a normal dir and return the
* offset as if '.' and '..' really take place.
*
*/
int ext4_read_inline_dir(struct file *filp, int ext4_read_inline_dir(struct file *filp,
void *dirent, filldir_t filldir, void *dirent, filldir_t filldir,
int *has_inline_data) int *has_inline_data)
...@@ -1409,6 +1417,7 @@ int ext4_read_inline_dir(struct file *filp, ...@@ -1409,6 +1417,7 @@ int ext4_read_inline_dir(struct file *filp,
int ret, inline_size = 0; int ret, inline_size = 0;
struct ext4_iloc iloc; struct ext4_iloc iloc;
void *dir_buf = NULL; void *dir_buf = NULL;
int dotdot_offset, dotdot_size, extra_offset, extra_size;
ret = ext4_get_inode_loc(inode, &iloc); ret = ext4_get_inode_loc(inode, &iloc);
if (ret) if (ret)
...@@ -1437,8 +1446,21 @@ int ext4_read_inline_dir(struct file *filp, ...@@ -1437,8 +1446,21 @@ int ext4_read_inline_dir(struct file *filp,
sb = inode->i_sb; sb = inode->i_sb;
stored = 0; stored = 0;
parent_ino = le32_to_cpu(((struct ext4_dir_entry_2 *)dir_buf)->inode); parent_ino = le32_to_cpu(((struct ext4_dir_entry_2 *)dir_buf)->inode);
offset = filp->f_pos;
/*
* dotdot_offset and dotdot_size is the real offset and
* size for ".." and "." if the dir is block based while
* the real size for them are only EXT4_INLINE_DOTDOT_SIZE.
* So we will use extra_offset and extra_size to indicate them
* during the inline dir iteration.
*/
dotdot_offset = EXT4_DIR_REC_LEN(1);
dotdot_size = dotdot_offset + EXT4_DIR_REC_LEN(2);
extra_offset = dotdot_size - EXT4_INLINE_DOTDOT_SIZE;
extra_size = extra_offset + inline_size;
while (!error && !stored && filp->f_pos < inode->i_size) { while (!error && !stored && filp->f_pos < extra_size) {
revalidate: revalidate:
/* /*
* If the version has changed since the last call to * If the version has changed since the last call to
...@@ -1447,15 +1469,23 @@ int ext4_read_inline_dir(struct file *filp, ...@@ -1447,15 +1469,23 @@ int ext4_read_inline_dir(struct file *filp,
* dir to make sure. * dir to make sure.
*/ */
if (filp->f_version != inode->i_version) { if (filp->f_version != inode->i_version) {
for (i = 0; for (i = 0; i < extra_size && i < offset;) {
i < inode->i_size && i < offset;) { /*
* "." is with offset 0 and
* ".." is dotdot_offset.
*/
if (!i) { if (!i) {
/* skip "." and ".." if needed. */ i = dotdot_offset;
i += EXT4_INLINE_DOTDOT_SIZE; continue;
} else if (i == dotdot_offset) {
i = dotdot_size;
continue; continue;
} }
/* for other entry, the real offset in
* the buf has to be tuned accordingly.
*/
de = (struct ext4_dir_entry_2 *) de = (struct ext4_dir_entry_2 *)
(dir_buf + i); (dir_buf + i - extra_offset);
/* It's too expensive to do a full /* It's too expensive to do a full
* dirent test each time round this * dirent test each time round this
* loop, but we do have to test at * loop, but we do have to test at
...@@ -1463,43 +1493,47 @@ int ext4_read_inline_dir(struct file *filp, ...@@ -1463,43 +1493,47 @@ int ext4_read_inline_dir(struct file *filp,
* failure will be detected in the * failure will be detected in the
* dirent test below. */ * dirent test below. */
if (ext4_rec_len_from_disk(de->rec_len, if (ext4_rec_len_from_disk(de->rec_len,
inline_size) < EXT4_DIR_REC_LEN(1)) extra_size) < EXT4_DIR_REC_LEN(1))
break; break;
i += ext4_rec_len_from_disk(de->rec_len, i += ext4_rec_len_from_disk(de->rec_len,
inline_size); extra_size);
} }
offset = i; offset = i;
filp->f_pos = offset; filp->f_pos = offset;
filp->f_version = inode->i_version; filp->f_version = inode->i_version;
} }
while (!error && filp->f_pos < inode->i_size) { while (!error && filp->f_pos < extra_size) {
if (filp->f_pos == 0) { if (filp->f_pos == 0) {
error = filldir(dirent, ".", 1, 0, inode->i_ino, error = filldir(dirent, ".", 1, 0, inode->i_ino,
DT_DIR); DT_DIR);
if (error) if (error)
break; break;
stored++; stored++;
filp->f_pos = dotdot_offset;
continue;
}
error = filldir(dirent, "..", 2, 0, parent_ino, if (filp->f_pos == dotdot_offset) {
DT_DIR); error = filldir(dirent, "..", 2,
dotdot_offset,
parent_ino, DT_DIR);
if (error) if (error)
break; break;
stored++; stored++;
filp->f_pos = offset = EXT4_INLINE_DOTDOT_SIZE; filp->f_pos = dotdot_size;
continue; continue;
} }
de = (struct ext4_dir_entry_2 *)(dir_buf + offset); de = (struct ext4_dir_entry_2 *)
(dir_buf + filp->f_pos - extra_offset);
if (ext4_check_dir_entry(inode, filp, de, if (ext4_check_dir_entry(inode, filp, de,
iloc.bh, dir_buf, iloc.bh, dir_buf,
inline_size, offset)) { extra_size, filp->f_pos)) {
ret = stored; ret = stored;
goto out; goto out;
} }
offset += ext4_rec_len_from_disk(de->rec_len,
inline_size);
if (le32_to_cpu(de->inode)) { if (le32_to_cpu(de->inode)) {
/* We might block in the next section /* We might block in the next section
* if the data destination is * if the data destination is
...@@ -1522,9 +1556,8 @@ int ext4_read_inline_dir(struct file *filp, ...@@ -1522,9 +1556,8 @@ int ext4_read_inline_dir(struct file *filp,
stored++; stored++;
} }
filp->f_pos += ext4_rec_len_from_disk(de->rec_len, filp->f_pos += ext4_rec_len_from_disk(de->rec_len,
inline_size); extra_size);
} }
offset = 0;
} }
out: out:
kfree(dir_buf); kfree(dir_buf);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册