diff options
Diffstat (limited to 'fs/exportfs')
-rw-r--r-- | fs/exportfs/expfs.c | 136 |
1 files changed, 130 insertions, 6 deletions
diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index 813011aad70..99294a23cd5 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -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); |