|
@@ -514,17 +514,141 @@ struct dentry *exportfs_decode_fh(struct vfsmount *mnt, struct fid *fid,
|
|
|
int (*acceptable)(void *, struct dentry *), void *context)
|
|
|
{
|
|
|
struct export_operations *nop = mnt->mnt_sb->s_export_op;
|
|
|
- struct dentry *result;
|
|
|
+ struct dentry *result, *alias;
|
|
|
+ int err;
|
|
|
|
|
|
- if (nop->decode_fh) {
|
|
|
- result = nop->decode_fh(mnt->mnt_sb, fid->raw, fh_len,
|
|
|
+ /*
|
|
|
+ * Old way of doing things. Will go away soon.
|
|
|
+ */
|
|
|
+ if (!nop->fh_to_dentry) {
|
|
|
+ if (nop->decode_fh) {
|
|
|
+ return nop->decode_fh(mnt->mnt_sb, fid->raw, fh_len,
|
|
|
fileid_type, acceptable, context);
|
|
|
+ } else {
|
|
|
+ return export_decode_fh(mnt->mnt_sb, fid->raw, fh_len,
|
|
|
+ fileid_type, acceptable, context);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Try to get any dentry for the given file handle from the filesystem.
|
|
|
+ */
|
|
|
+ result = nop->fh_to_dentry(mnt->mnt_sb, fid, fh_len, fileid_type);
|
|
|
+ if (!result)
|
|
|
+ result = ERR_PTR(-ESTALE);
|
|
|
+ if (IS_ERR(result))
|
|
|
+ return result;
|
|
|
+
|
|
|
+ if (S_ISDIR(result->d_inode->i_mode)) {
|
|
|
+ /*
|
|
|
+ * This request is for a directory.
|
|
|
+ *
|
|
|
+ * On the positive side there is only one dentry for each
|
|
|
+ * directory inode. On the negative side this implies that we
|
|
|
+ * to ensure our dentry is connected all the way up to the
|
|
|
+ * filesystem root.
|
|
|
+ */
|
|
|
+ if (result->d_flags & DCACHE_DISCONNECTED) {
|
|
|
+ err = reconnect_path(mnt->mnt_sb, result);
|
|
|
+ if (err)
|
|
|
+ goto err_result;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!acceptable(context, result)) {
|
|
|
+ err = -EACCES;
|
|
|
+ goto err_result;
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
} else {
|
|
|
- result = export_decode_fh(mnt->mnt_sb, fid->raw, fh_len,
|
|
|
- fileid_type, acceptable, context);
|
|
|
+ /*
|
|
|
+ * It's not a directory. Life is a little more complicated.
|
|
|
+ */
|
|
|
+ struct dentry *target_dir, *nresult;
|
|
|
+ char nbuf[NAME_MAX+1];
|
|
|
+
|
|
|
+ /*
|
|
|
+ * See if either the dentry we just got from the filesystem
|
|
|
+ * or any alias for it is acceptable. This is always true
|
|
|
+ * if this filesystem is exported without the subtreecheck
|
|
|
+ * option. If the filesystem is exported with the subtree
|
|
|
+ * check option there's a fair chance we need to look at
|
|
|
+ * the parent directory in the file handle and make sure
|
|
|
+ * it's connected to the filesystem root.
|
|
|
+ */
|
|
|
+ alias = find_acceptable_alias(result, acceptable, context);
|
|
|
+ if (alias)
|
|
|
+ return alias;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Try to extract a dentry for the parent directory from the
|
|
|
+ * file handle. If this fails we'll have to give up.
|
|
|
+ */
|
|
|
+ err = -ESTALE;
|
|
|
+ if (!nop->fh_to_parent)
|
|
|
+ goto err_result;
|
|
|
+
|
|
|
+ target_dir = nop->fh_to_parent(mnt->mnt_sb, fid,
|
|
|
+ fh_len, fileid_type);
|
|
|
+ if (!target_dir)
|
|
|
+ goto err_result;
|
|
|
+ err = PTR_ERR(target_dir);
|
|
|
+ if (IS_ERR(target_dir))
|
|
|
+ goto err_result;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * And as usual we need to make sure the parent directory is
|
|
|
+ * connected to the filesystem root. The VFS really doesn't
|
|
|
+ * like disconnected directories..
|
|
|
+ */
|
|
|
+ err = reconnect_path(mnt->mnt_sb, target_dir);
|
|
|
+ if (err) {
|
|
|
+ dput(target_dir);
|
|
|
+ goto err_result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Now that we've got both a well-connected parent and a
|
|
|
+ * dentry for the inode we're after, make sure that our
|
|
|
+ * inode is actually connected to the parent.
|
|
|
+ */
|
|
|
+ err = exportfs_get_name(target_dir, nbuf, result);
|
|
|
+ if (!err) {
|
|
|
+ mutex_lock(&target_dir->d_inode->i_mutex);
|
|
|
+ nresult = lookup_one_len(nbuf, target_dir,
|
|
|
+ strlen(nbuf));
|
|
|
+ mutex_unlock(&target_dir->d_inode->i_mutex);
|
|
|
+ if (!IS_ERR(nresult)) {
|
|
|
+ if (nresult->d_inode) {
|
|
|
+ dput(result);
|
|
|
+ result = nresult;
|
|
|
+ } else
|
|
|
+ dput(nresult);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * At this point we are done with the parent, but it's pinned
|
|
|
+ * by the child dentry anyway.
|
|
|
+ */
|
|
|
+ dput(target_dir);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * And finally make sure the dentry is actually acceptable
|
|
|
+ * to NFSD.
|
|
|
+ */
|
|
|
+ alias = find_acceptable_alias(result, acceptable, context);
|
|
|
+ if (!alias) {
|
|
|
+ err = -EACCES;
|
|
|
+ goto err_result;
|
|
|
+ }
|
|
|
+
|
|
|
+ return alias;
|
|
|
}
|
|
|
|
|
|
- return result;
|
|
|
+ err_result:
|
|
|
+ dput(result);
|
|
|
+ return ERR_PTR(err);
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(exportfs_decode_fh);
|
|
|
|