[PATCH] Support for dx directories in ext3_get_parent (NFSD)

From: Henrik Grubbström
Date: Mon May 09 2005 - 03:59:23 EST


The 2.6.10 ext3_get_parent attempts to use ext3_find_entry to look up the
entry "..", which fails for dx directories since ".." is not present in
the directory hash table. The patch below solves this by looking up the
dotdot entry in the dx_root block.

Typical symptoms of the above bug are intermittent claims by nfsd that
files or directories are missing on exported ext3 filesystems.

cf https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=150759
and https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=144556

Signed-off-by: Henrik Grubbström <grubba@xxxxxxxxxx>

--- linux/fs/ext3/namei.c.orig 2005-05-06 17:08:31.000000000 +0200
+++ linux/fs/ext3/namei.c 2005-05-07 17:57:24.000000000 +0200
@@ -999,24 +999,90 @@ static struct dentry *ext3_lookup(struct

struct dentry *ext3_get_parent(struct dentry *child)
{
- unsigned long ino;
+ unsigned long ino = 0;
struct dentry *parent;
struct inode *inode;
- struct dentry dotdot;
- struct ext3_dir_entry_2 * de;
struct buffer_head *bh;
+ int err;
+ struct inode *dir = child->d_inode;
+ struct super_block *sb = dir->i_sb;

- dotdot.d_name.name = "..";
- dotdot.d_name.len = 2;
- dotdot.d_parent = child; /* confusing, isn't it! */
+ /* In all cases the ".." entry is in block #0, so read it. */
+ bh = ext3_bread(NULL, dir, 0, 0, &err);
+ if (!bh) return ERR_PTR(err);
+
+#ifdef CONFIG_EXT3_INDEX
+ if (is_dx(dir)) {
+ /* Block #0 is a dx_root. */
+ struct dx_root *root = (struct dx_root *)bh->b_data;
+ if (root->info.hash_version == DX_HASH_TEA ||
+ root->info.hash_version == DX_HASH_HALF_MD4 ||
+ root->info.hash_version == DX_HASH_LEGACY) {
+ /* Is this paranoia actually needed? */
+ ino = le32_to_cpu(root->dotdot.inode);
+ } else {
+ ext3_error(sb, __FUNCTION__,
+ "unsupported dx hash_version (%d) for #%lu\n",
+ root->info.hash_version, dir->i_ino);
+ }
+ }
+ if (!ino)
+#endif
+ {
+ /* Block #0 is a sequence of ext3_dir_entry_2.
+ * ".." is the second entry.
+ */
+ struct ext3_dir_entry_2 *de =
+ (struct ext3_dir_entry_2 *)bh->b_data;
+ int len = le16_to_cpu(de->rec_len);
+ if (len > 0) {
+ /* The first entry should be ".". */
+ de = (struct ext3_dir_entry_2 *)((char *)de + len);
+ if ((de->name_len == 2) &&
+ (de->name[0] == '.') &&
+ (de->name[1] == '.')) {
+ /* The second entry should be "..". */
+ ino = le32_to_cpu(de->inode);
+ } else {
+ ext3_warning(sb, __FUNCTION__,
+ "second entry of #%lu not parent",
+ dir->i_ino);
+ }
+ } else {
+ ext3_error(sb, __FUNCTION__,
+ "first entry of #%lu has bad rec_len: %d",
+ dir->i_ino, len);
+ }
+#if 0
+ /* Fallback code to enable if the ".." entry ever can
+ * be somewhere else than in the second entry of the
+ * first block.
+ */
+ if (!ino) {
+ struct dentry dotdot;
+
+ ext3_warning(sb, __FUNCTION__,
+ "falling back to ext3_find_entry for #%lu\n",
+ dir->i_ino);
+ brelse(bh);
+ dotdot.d_name.name = "..";
+ dotdot.d_name.len = 2;
+ dotdot.d_parent = child; /* confusing, isn't it! */
+
+ bh = ext3_find_entry(&dotdot, &de);
+ if (!bh)
+ return ERR_PTR(-ENOENT);
+ ino = le32_to_cpu(de->inode);
+ }
+#endif /* 0 */
+ }

- bh = ext3_find_entry(&dotdot, &de);
- inode = NULL;
- if (!bh)
- return ERR_PTR(-ENOENT);
- ino = le32_to_cpu(de->inode);
brelse(bh);
- inode = iget(child->d_inode->i_sb, ino);
+
+ if (!ino)
+ return ERR_PTR(-ENOENT);
+
+ inode = iget(sb, ino);

if (!inode)
return ERR_PTR(-EACCES);

--
Henrik Grubbström grubba@xxxxxxxxxx
Roxen Internet Software AB grubba@xxxxxxxxx

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/