From 062c3f93b5d5f950562de8ace6befee9ab304fd9 Mon Sep 17 00:00:00 2001 From: Rachita Kothiyal Date: Sun, 16 Dec 2007 03:08:41 -0500 Subject: [PATCH] Unionfs ODF2: Fixes to make unionfs exportable Signed-off-by: Rachita Kothiyal --- fs/unionfs/commonfops.c | 8 +- fs/unionfs/dentry.c | 11 +- fs/unionfs/export.c | 257 ++++++++++++++++++++++++++-------------- 3 files changed, 172 insertions(+), 104 deletions(-) diff --git a/fs/unionfs/commonfops.c b/fs/unionfs/commonfops.c index bd55ec611f..516ea5256c 100644 --- a/fs/unionfs/commonfops.c +++ b/fs/unionfs/commonfops.c @@ -630,11 +630,9 @@ int unionfs_open(struct inode *inode, struct file *file) dentry = file->f_path.dentry; /* - * FIXME: With nfs exporting we can get a disconnected dentry here. - * This can happen if the dcache is dropped on the server while the - * client is working, and export ops decodefh returns ESTALE to the - * user. It would best to catch this by implementing decodefh or - * maybe we could connect the dentry ourselves using getparent? + * With nfs exporting we cannot get a disconnected dentry here, + * since we already connect it in unionfs_get_parent. This check + * can probably be removed after sufficient testing. */ if (dentry->d_flags & DCACHE_DISCONNECTED) { err = -ESTALE; diff --git a/fs/unionfs/dentry.c b/fs/unionfs/dentry.c index 7dccd8bddf..46a6a59d7a 100644 --- a/fs/unionfs/dentry.c +++ b/fs/unionfs/dentry.c @@ -38,13 +38,10 @@ static bool __unionfs_d_revalidate_one(struct dentry *dentry, struct nameidata lowernd; /* TODO: be gentler to the stack */ /* - * FIXME: with nfs exporting, if export ops fails to connect a - * dentry and returns ESTALE to the client, we can end up with an - * inode that has ibstart -1. This fix only stops the oopsing but - * the dentry will become unusable on the client, it will keep - * getting ESTALE on any attempt to fix it. The best way to fix - * this is by implementing decode_fh and making sure no inode gets - * left with -1 ibstart + * This will never be true with nfs exporting, since unionfs_interpose + * will now ensure that no inode is left with ibstart -1. Retaining + * the check for now to be safe. This should go away after sufficient + * testing. */ if (dentry->d_inode && ibstart(dentry->d_inode) < 0) { valid = 0; diff --git a/fs/unionfs/export.c b/fs/unionfs/export.c index f8bd66a340..46d7653e2f 100644 --- a/fs/unionfs/export.c +++ b/fs/unionfs/export.c @@ -93,27 +93,9 @@ static struct dentry *__get_parent(struct super_block *sb, /* our odf root can never be disconnected */ BUG_ON(d == odf_root); - if (odf_root->d_sb->s_export_op->get_name) - res = odf_root->d_sb->s_export_op->get_name( + res = exportfs_get_name(NULL, UNIONFS_D(parent)->odf.dentry, name, d); - else { - res = exportfs_get_name( - NULL, /* NULL mnt is wrong! */ - UNIONFS_D(parent)->odf.dentry, - name, d); - BUG(); - /* - * FIXME: we're passing a null mnt above, - * which is wrong. We need to pass a real - * mnt. But first, we need to change struct - * odf_dentry_info so that it'll record a - * struct path instead of just a dentry. - * That struct path will include both a - * dentry and mnt, which have to be dget/put - * and mntget/put properly. -ezk - */ - } } else strncpy(name, d->d_name.name, d->d_name.len); @@ -185,98 +167,189 @@ out: return res; } -/* - * @dentry: the directory in which to find a name - * @name: a pointer to a %NAME_MAX+1 char buffer to store the name - * @child: the dentry for the child directory. - * - * searches the cached dir in odf to find a dirent with - * the same inode number as the child, and returns that. - */ static int unionfs_get_name(struct dentry *dentry, char *name, struct dentry *child) { - int err; - struct inode *dir = dentry->d_inode; - struct dentry *odf_cache = NULL; - struct file *file = NULL; - /* dirent */ - u64 ino; - char *tmp_name = NULL; - int namelen; - unsigned int d_type; - - err = -ENOTDIR; - if (!dir || !S_ISDIR(dir->i_mode)) - goto out; + int err = 0; + struct odf_sb_info *odf; + struct super_block *odf_sb; + struct inode *odf_child_i, *odf_dentry_i; + struct dentry *odf_child, *odf_dentry; - err = -EINVAL; - if (!dir->i_fop) - goto out; + odf = &UNIONFS_SB(child->d_sb)->odf; + odf_sb = odf->sb->d_sb; - if (!UNIONFS_D(dentry) || !UNIONFS_D(dentry)->odf.dentry) + odf_dentry_i = iget(odf_sb, dentry->d_inode->i_ino); + if (!odf_dentry_i) { + err = -ENOMEM; goto out; + } - odf_cache = odf_ic_cache_dentry(dentry); - if (IS_ERR(odf_cache)) { - err = PTR_ERR(odf_cache); - odf_cache = NULL; + odf_dentry = d_alloc_anon(odf_dentry_i); + if (!odf_dentry) { + iput(odf_dentry_i); + err = -ENOMEM; goto out; } - /* reconstruct the cached dir if necessary */ - if (!odf_cache->d_inode || !i_size_read(odf_cache->d_inode)) { - unionfs_lock_dentry(dentry); - err = odf_cache_dir(dentry, odf_cache, - &dentry->d_inode->i_mtime); - unionfs_unlock_dentry(dentry); - if (err) - goto out; + odf_child_i = iget(odf_sb, child->d_inode->i_ino); + if (!odf_child_i) { + iput(odf_dentry_i); + dput(odf_dentry); + err = -ENOMEM; + goto out; } - dget(odf_cache); - mntget(UNIONFS_SB(dentry->d_sb)->odf.path.mnt); - file = dentry_open(odf_cache, UNIONFS_SB(dentry->d_sb)->odf.path.mnt, - O_RDONLY); - if (IS_ERR(file)) { - err = PTR_ERR(file); - mntput(UNIONFS_SB(dentry->d_sb)->odf.path.mnt); - dput(odf_cache); - file = NULL; + odf_child = d_alloc_anon(odf_child_i); + if (!odf_child) { + iput(odf_dentry_i); + dput(odf_dentry); + iput(odf_child_i); + err = -ENOMEM; goto out; } - /* search for the inode in the dir */ - file->f_pos = 0; - while (file->f_pos < i_size_read(file->f_path.dentry->d_inode)) { - err = odf_read_dirent(file, &tmp_name, &namelen, &ino, - &d_type); - if (err) - break; - if (ino == child->d_inode->i_ino) { - if (namelen >= NAME_MAX) - namelen = NAME_MAX; - strncpy(name, tmp_name, namelen); - name[namelen] = 0; - err = 0; - break; - } - - err = -ENOENT; - kfree(tmp_name); - tmp_name = NULL; - } + err = exportfs_get_name(NULL, odf_dentry, name, odf_child); + dput(odf_child); + dput(odf_dentry); out: - kfree(tmp_name); - tmp_name = NULL; - if (file) - filp_close(file, NULL); - dput(odf_cache); return err; } +static struct dentry *unionfs_export_iget(struct super_block *sb, + unsigned long ino, __u32 generation, + __u32 mode) +{ + struct inode *inode; + struct dentry *result; + int err = 0; + + if (ino == 0 || mode == 0) + return ERR_PTR(-ESTALE); + + inode = iget(sb, ino); + if (inode == NULL) + return ERR_PTR(-ENOMEM); + + /* A generation of 0 means "accept any" */ + if (is_bad_inode(inode) || (generation + && inode->i_generation != generation)) { + /* we didn't find the right inode.. */ + printk(KERN_ERR "fh_verify: Inode %lu, Bad count: %d %d " + "or version %u %u\n", inode->i_ino, inode->i_nlink, + atomic_read(&inode->i_count), inode->i_generation, + generation); + + iput(inode); + return ERR_PTR(-ESTALE); + } + + /* Now find a dentry. If possible, get a well-connected one */ + result = d_alloc_anon(inode); + if (!result) { + iput(inode); + return ERR_PTR(-ENOMEM); + } + + if (!UNIONFS_D(result)) + err = new_dentry_private_data(result); + if (err) { + printk(KERN_INFO "unionfs_export_iget: Not enough memory to " + "allocate dentry's private data\n"); + return ERR_PTR(-ENOMEM); + } + + result->d_inode->i_mode = mode; + return result; +} + +static struct dentry *unionfs_fh_to_dentry(struct super_block *sb, + struct fid *fid, int fh_len, + int fh_type) +{ + __u32 child[3]; + struct dentry *result; + result = ERR_PTR(-EINVAL); + + if (fh_len < 3 || fh_type > FILEID_INO32_GEN_PARENT) + return result; + + child[0] = fid->raw[0]; + child[1] = fid->raw[1]; + child[2] = fid->raw[2]; + + if (fh_type == FILEID_INO32_GEN || fh_type == FILEID_INO32_GEN_PARENT) + result = unionfs_export_iget(sb, child[0], child[1], child[2]); + + return result; +} + +static struct dentry *unionfs_fh_to_parent(struct super_block *sb, + struct fid *fid, int fh_len, + int fh_type) +{ + __u32 parent[3]; + struct dentry *result; + result = ERR_PTR(-EINVAL); + if (fh_len < 3 || fh_type > FILEID_INO32_GEN_PARENT) + return result; + + if (fh_len > 3) + parent[0] = fid->raw[3]; + if (fh_len > 4) + parent[1] = fid->raw[4]; + if (fh_len > 5) + parent[2] = fid->raw[5]; + + if (fh_type == FILEID_INO32_GEN_PARENT) + result = unionfs_export_iget(sb, parent[0], parent[1], + parent[2]); + + return result; +} + +/* + * unionfs_encode_fh: Encode dentry information into the filehandle. + * In addition to inode and generation, we also store the inode->i_mode + * in the filehandle to aid exportfs_decode_fh in identifying the inode. + */ +static int unionfs_encode_fh(struct dentry *dentry, __u32 *raw, int *max_len, + int connectable) +{ + struct inode *inode = dentry->d_inode; + int len = *max_len; + int type = FILEID_INO32_GEN; + + connectable = 1; + + if (len < 3 || (connectable && len < 6)) + return 255; + + len = 3; + raw[0] = inode->i_ino; + raw[1] = inode->i_generation; + raw[2] = inode->i_mode; + if (connectable && !S_ISDIR(inode->i_mode)) { + struct inode *parent; + + spin_lock(&dentry->d_lock); + parent = dentry->d_parent->d_inode; + raw[3] = parent->i_ino; + raw[4] = parent->i_generation; + raw[5] = parent->i_mode; + spin_unlock(&dentry->d_lock); + len = 6; + type = FILEID_INO32_GEN_PARENT; + } + *max_len = len; + return type; +} + struct export_operations unionfs_export_ops = { - .get_parent = unionfs_get_parent, - .get_name = unionfs_get_name + .get_parent = unionfs_get_parent, + .get_name = unionfs_get_name, + .fh_to_dentry = unionfs_fh_to_dentry, + .fh_to_parent = unionfs_fh_to_parent, + .encode_fh = unionfs_encode_fh }; -- 2.34.1