Skip to content

Commit

Permalink
NFSv4: Replace nfs4_path_walk() with VFS path lookup in a private nam…
Browse files Browse the repository at this point in the history
…espace

As noted in the previous patch, the NFSv4 client mount code currently
has several limitations. If the mount path contains symlinks, or
referrals, or even if it just contains a '..', then the client code in
nfs4_path_walk() will fail with an error.

This patch replaces the nfs4_path_walk()-based lookup with a helper
function that sets up a private namespace to represent the namespace on the
server, then uses the ordinary VFS and NFS path lookup code to walk down the
mount path in that namespace.

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
Trond Myklebust authored and Linus Torvalds committed Jun 23, 2009
1 parent cf8d2c1 commit c02d7ad
Showing 1 changed file with 157 additions and 21 deletions.
178 changes: 157 additions & 21 deletions fs/nfs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
#include <linux/smp_lock.h>
#include <linux/seq_file.h>
#include <linux/mount.h>
#include <linux/mnt_namespace.h>
#include <linux/namei.h>
#include <linux/nfs_idmap.h>
#include <linux/vfs.h>
#include <linux/inet.h>
Expand Down Expand Up @@ -272,10 +274,14 @@ static const struct super_operations nfs_sops = {
#ifdef CONFIG_NFS_V4
static int nfs4_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt);
static int nfs4_remote_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt);
static int nfs4_xdev_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt);
static int nfs4_referral_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt);
static int nfs4_remote_referral_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt);
static void nfs4_kill_super(struct super_block *sb);

static struct file_system_type nfs4_fs_type = {
Expand All @@ -286,6 +292,14 @@ static struct file_system_type nfs4_fs_type = {
.fs_flags = FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA,
};

static struct file_system_type nfs4_remote_fs_type = {
.owner = THIS_MODULE,
.name = "nfs4",
.get_sb = nfs4_remote_get_sb,
.kill_sb = nfs4_kill_super,
.fs_flags = FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA,
};

struct file_system_type nfs4_xdev_fs_type = {
.owner = THIS_MODULE,
.name = "nfs4",
Expand All @@ -294,6 +308,14 @@ struct file_system_type nfs4_xdev_fs_type = {
.fs_flags = FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA,
};

static struct file_system_type nfs4_remote_referral_fs_type = {
.owner = THIS_MODULE,
.name = "nfs4",
.get_sb = nfs4_remote_referral_get_sb,
.kill_sb = nfs4_kill_super,
.fs_flags = FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA,
};

struct file_system_type nfs4_referral_fs_type = {
.owner = THIS_MODULE,
.name = "nfs4",
Expand Down Expand Up @@ -2433,12 +2455,12 @@ static int nfs4_validate_mount_data(void *options,
}

/*
* Get the superblock for an NFS4 mountpoint
* Get the superblock for the NFS4 root partition
*/
static int nfs4_get_sb(struct file_system_type *fs_type,
static int nfs4_remote_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt)
{
struct nfs_parsed_mount_data *data;
struct nfs_parsed_mount_data *data = raw_data;
struct super_block *s;
struct nfs_server *server;
struct nfs_fh *mntfh;
Expand All @@ -2449,18 +2471,12 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
};
int error = -ENOMEM;

data = kzalloc(sizeof(*data), GFP_KERNEL);
mntfh = kzalloc(sizeof(*mntfh), GFP_KERNEL);
if (data == NULL || mntfh == NULL)
goto out_free_fh;

security_init_mnt_opts(&data->lsm_opts);

/* Validate the mount data */
error = nfs4_validate_mount_data(raw_data, data, dev_name);
if (error < 0)
goto out;

/* Get a volume representation */
server = nfs4_create_server(data, mntfh);
if (IS_ERR(server)) {
Expand All @@ -2473,7 +2489,7 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
compare_super = NULL;

/* Get a superblock - note that we may end up sharing one that already exists */
s = sget(fs_type, compare_super, nfs_set_super, &sb_mntdata);
s = sget(&nfs4_fs_type, compare_super, nfs_set_super, &sb_mntdata);
if (IS_ERR(s)) {
error = PTR_ERR(s);
goto out_free;
Expand Down Expand Up @@ -2510,14 +2526,9 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
error = 0;

out:
kfree(data->client_address);
kfree(data->nfs_server.export_path);
kfree(data->nfs_server.hostname);
kfree(data->fscache_uniq);
security_free_mnt_opts(&data->lsm_opts);
out_free_fh:
kfree(mntfh);
kfree(data);
return error;

out_free:
Expand All @@ -2531,6 +2542,102 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
goto out;
}

static struct vfsmount *nfs_do_root_mount(struct file_system_type *fs_type,
int flags, void *data, const char *hostname)
{
struct vfsmount *root_mnt;
char *root_devname;
size_t len;

len = strlen(hostname) + 3;
root_devname = kmalloc(len, GFP_KERNEL);
if (root_devname == NULL)
return ERR_PTR(-ENOMEM);
snprintf(root_devname, len, "%s:/", hostname);
root_mnt = vfs_kern_mount(fs_type, flags, root_devname, data);
kfree(root_devname);
return root_mnt;
}

static int nfs_follow_remote_path(struct vfsmount *root_mnt,
const char *export_path, struct vfsmount *mnt_target)
{
struct mnt_namespace *ns_private;
struct nameidata nd;
struct super_block *s;
int ret;

ns_private = create_mnt_ns(root_mnt);
ret = PTR_ERR(ns_private);
if (IS_ERR(ns_private))
goto out_mntput;

ret = vfs_path_lookup(root_mnt->mnt_root, root_mnt,
export_path, LOOKUP_FOLLOW, &nd);

put_mnt_ns(ns_private);

if (ret != 0)
goto out_err;

s = nd.path.mnt->mnt_sb;
atomic_inc(&s->s_active);
mnt_target->mnt_sb = s;
mnt_target->mnt_root = dget(nd.path.dentry);

path_put(&nd.path);
down_write(&s->s_umount);
return 0;
out_mntput:
mntput(root_mnt);
out_err:
return ret;
}

/*
* Get the superblock for an NFS4 mountpoint
*/
static int nfs4_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt)
{
struct nfs_parsed_mount_data *data;
char *export_path;
struct vfsmount *root_mnt;
int error = -ENOMEM;

data = kzalloc(sizeof(*data), GFP_KERNEL);
if (data == NULL)
goto out_free_data;

/* Validate the mount data */
error = nfs4_validate_mount_data(raw_data, data, dev_name);
if (error < 0)
goto out;

export_path = data->nfs_server.export_path;
data->nfs_server.export_path = "/";
root_mnt = nfs_do_root_mount(&nfs4_remote_fs_type, flags, data,
data->nfs_server.hostname);
data->nfs_server.export_path = export_path;

error = PTR_ERR(root_mnt);
if (IS_ERR(root_mnt))
goto out;

error = nfs_follow_remote_path(root_mnt, export_path, mnt);

out:
kfree(data->client_address);
kfree(data->nfs_server.export_path);
kfree(data->nfs_server.hostname);
kfree(data->fscache_uniq);
out_free_data:
kfree(data);
dprintk("<-- nfs4_get_sb() = %d%s\n", error,
error != 0 ? " [error]" : "");
return error;
}

static void nfs4_kill_super(struct super_block *sb)
{
struct nfs_server *server = NFS_SB(sb);
Expand Down Expand Up @@ -2627,12 +2734,9 @@ static int nfs4_xdev_get_sb(struct file_system_type *fs_type, int flags,
return error;
}

/*
* Create an NFS4 server record on referral traversal
*/
static int nfs4_referral_get_sb(struct file_system_type *fs_type, int flags,
const char *dev_name, void *raw_data,
struct vfsmount *mnt)
static int nfs4_remote_referral_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data,
struct vfsmount *mnt)
{
struct nfs_clone_mount *data = raw_data;
struct super_block *s;
Expand Down Expand Up @@ -2711,4 +2815,36 @@ static int nfs4_referral_get_sb(struct file_system_type *fs_type, int flags,
return error;
}

/*
* Create an NFS4 server record on referral traversal
*/
static int nfs4_referral_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data,
struct vfsmount *mnt)
{
struct nfs_clone_mount *data = raw_data;
char *export_path;
struct vfsmount *root_mnt;
int error;

dprintk("--> nfs4_referral_get_sb()\n");

export_path = data->mnt_path;
data->mnt_path = "/";

root_mnt = nfs_do_root_mount(&nfs4_remote_referral_fs_type,
flags, data, data->hostname);
data->mnt_path = export_path;

error = PTR_ERR(root_mnt);
if (IS_ERR(root_mnt))
goto out;

error = nfs_follow_remote_path(root_mnt, export_path, mnt);
out:
dprintk("<-- nfs4_referral_get_sb() = %d%s\n", error,
error != 0 ? " [error]" : "");
return error;
}

#endif /* CONFIG_NFS_V4 */

0 comments on commit c02d7ad

Please sign in to comment.