/* * nfs_follow_mountpoint - handle crossing a mountpoint on the server * @dentry - dentry of mountpoint * @nd - nameidata info * * When we encounter a mountpoint on the server, we want to set up * a mountpoint on the client too, to prevent inode numbers from * colliding, and to allow "df" to work properly. * On NFSv4, we also want to allow for the fact that different * filesystems may be migrated to different servers in a failover * situation, and that different filesystems may want to use * different security flavours. */ static void * nfs_follow_mountpoint(struct dentry *dentry, struct nameidata *nd) { struct vfsmount *mnt; struct nfs_server *server = NFS_SERVER(dentry->d_inode); struct dentry *parent; struct nfs_fh fh; struct nfs_fattr fattr; int err; dprintk("--> nfs_follow_mountpoint()\n"); BUG_ON(IS_ROOT(dentry)); dprintk("%s: enter\n", __FUNCTION__); dput(nd->dentry); nd->dentry = dget(dentry); /* Look it up again */ parent = dget_parent(nd->dentry); err = server->nfs_client->rpc_ops->lookup(parent->d_inode, &nd->dentry->d_name, &fh, &fattr); dput(parent); if (err != 0) goto out_err; if (fattr.valid & NFS_ATTR_FATTR_V4_REFERRAL) mnt = nfs_do_refmount(nd->mnt, nd->dentry); else mnt = nfs_do_submount(nd->mnt, nd->dentry, &fh, &fattr); err = PTR_ERR(mnt); if (IS_ERR(mnt)) goto out_err; mntget(mnt); err = do_add_mount(mnt, nd, nd->mnt->mnt_flags|MNT_SHRINKABLE, &nfs_automount_list); if (err < 0) { mntput(mnt); if (err == -EBUSY) goto out_follow; goto out_err; } mntput(nd->mnt); dput(nd->dentry); nd->mnt = mnt; nd->dentry = dget(mnt->mnt_root); schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout); out: dprintk("%s: done, returned %d\n", __FUNCTION__, err); dprintk("<-- nfs_follow_mountpoint() = %d\n", err); return ERR_PTR(err); out_err: path_release(nd); goto out; out_follow: while(d_mountpoint(nd->dentry) && follow_down(&nd->mnt, &nd->dentry)) ; err = 0; goto out; }
/* * nfs_d_automount - Handle crossing a mountpoint on the server * @path - The mountpoint * * When we encounter a mountpoint on the server, we want to set up * a mountpoint on the client too, to prevent inode numbers from * colliding, and to allow "df" to work properly. * On NFSv4, we also want to allow for the fact that different * filesystems may be migrated to different servers in a failover * situation, and that different filesystems may want to use * different security flavours. */ struct vfsmount *nfs_d_automount(struct path *path) { struct vfsmount *mnt; struct nfs_server *server = NFS_SERVER(path->dentry->d_inode); struct dentry *parent; struct nfs_fh *fh = NULL; struct nfs_fattr *fattr = NULL; int err; rpc_authflavor_t flavor = RPC_AUTH_UNIX; dprintk("--> nfs_d_automount()\n"); mnt = ERR_PTR(-ESTALE); if (IS_ROOT(path->dentry)) goto out_nofree; mnt = ERR_PTR(-ENOMEM); fh = nfs_alloc_fhandle(); fattr = nfs_alloc_fattr(); if (fh == NULL || fattr == NULL) goto out; dprintk("%s: enter\n", __func__); /* Look it up again to get its attributes */ parent = dget_parent(path->dentry); err = server->nfs_client->rpc_ops->lookup(server->client, parent->d_inode, &path->dentry->d_name, fh, fattr); if (err == -EPERM && NFS_PROTO(parent->d_inode)->secinfo != NULL) err = nfs_lookup_with_sec(server, parent, path->dentry, path, fh, fattr, &flavor); dput(parent); if (err != 0) { mnt = ERR_PTR(err); goto out; } if (fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL) mnt = nfs_do_refmount(path->dentry); else mnt = nfs_do_submount(path->dentry, fh, fattr, flavor); if (IS_ERR(mnt)) goto out; dprintk("%s: done, success\n", __func__); mntget(mnt); /* prevent immediate expiration */ mnt_set_expiry(mnt, &nfs_automount_list); schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout); out: nfs_free_fattr(fattr); nfs_free_fhandle(fh); out_nofree: dprintk("<-- nfs_follow_mountpoint() = %p\n", mnt); return mnt; }
struct vfsmount *nfs_d_automount(struct path *path) { struct vfsmount *mnt; struct dentry *parent; struct nfs_fh *fh = NULL; struct nfs_fattr *fattr = NULL; struct rpc_clnt *client; dprintk("--> nfs_d_automount()\n"); mnt = ERR_PTR(-ESTALE); if (IS_ROOT(path->dentry)) goto out_nofree; mnt = ERR_PTR(-ENOMEM); fh = nfs_alloc_fhandle(); fattr = nfs_alloc_fattr(); if (fh == NULL || fattr == NULL) goto out; dprintk("%s: enter\n", __func__); parent = dget_parent(path->dentry); client = nfs_lookup_mountpoint(parent->d_inode, &path->dentry->d_name, fh, fattr); dput(parent); if (IS_ERR(client)) { mnt = ERR_CAST(client); goto out; } if (fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL) mnt = nfs_do_refmount(client, path->dentry); else mnt = nfs_do_submount(path->dentry, fh, fattr, client->cl_auth->au_flavor); rpc_shutdown_client(client); if (IS_ERR(mnt)) goto out; dprintk("%s: done, success\n", __func__); mntget(mnt); mnt_set_expiry(mnt, &nfs_automount_list); schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout); out: nfs_free_fattr(fattr); nfs_free_fhandle(fh); out_nofree: if (IS_ERR(mnt)) dprintk("<-- %s(): error %ld\n", __func__, PTR_ERR(mnt)); else dprintk("<-- %s() = %p\n", __func__, mnt); return mnt; }
/** * nfs_follow_referral - set up mountpoint when hitting a referral on moved error * @dentry - parent directory * @locations - array of NFSv4 server location information * */ static struct vfsmount *nfs_follow_referral(struct dentry *dentry, const struct nfs4_fs_locations *locations) { struct vfsmount *mnt = ERR_PTR(-ENOENT); struct nfs_clone_mount mountdata = { .sb = dentry->d_sb, .dentry = dentry, .authflavor = NFS_SB(dentry->d_sb)->client->cl_auth->au_flavor, }; char *page = NULL, *page2 = NULL; int loc, error; if (locations == NULL || locations->nlocations <= 0) goto out; dprintk("%s: referral at %s/%s\n", __func__, dentry->d_parent->d_name.name, dentry->d_name.name); page = (char *) __get_free_page(GFP_USER); if (!page) goto out; page2 = (char *) __get_free_page(GFP_USER); if (!page2) goto out; /* Ensure fs path is a prefix of current dentry path */ error = nfs4_validate_fspath(dentry, locations, page, page2); if (error < 0) { mnt = ERR_PTR(error); goto out; } for (loc = 0; loc < locations->nlocations; loc++) { const struct nfs4_fs_location *location = &locations->locations[loc]; if (location == NULL || location->nservers <= 0 || location->rootpath.ncomponents == 0) continue; mnt = try_location(&mountdata, page, page2, location); if (!IS_ERR(mnt)) break; } out: free_page((unsigned long) page); free_page((unsigned long) page2); dprintk("%s: done\n", __func__); return mnt; } /* * nfs_do_refmount - handle crossing a referral on server * @dentry - dentry of referral * */ static struct vfsmount *nfs_do_refmount(struct rpc_clnt *client, struct dentry *dentry) { struct vfsmount *mnt = ERR_PTR(-ENOMEM); struct dentry *parent; struct nfs4_fs_locations *fs_locations = NULL; struct page *page; int err; /* BUG_ON(IS_ROOT(dentry)); */ dprintk("%s: enter\n", __func__); page = alloc_page(GFP_KERNEL); if (page == NULL) goto out; fs_locations = kmalloc(sizeof(struct nfs4_fs_locations), GFP_KERNEL); if (fs_locations == NULL) goto out_free; /* Get locations */ mnt = ERR_PTR(-ENOENT); parent = dget_parent(dentry); dprintk("%s: getting locations for %s/%s\n", __func__, parent->d_name.name, dentry->d_name.name); err = nfs4_proc_fs_locations(client, parent->d_inode, &dentry->d_name, fs_locations, page); dput(parent); if (err != 0 || fs_locations->nlocations <= 0 || fs_locations->fs_path.ncomponents <= 0) goto out_free; mnt = nfs_follow_referral(dentry, fs_locations); out_free: __free_page(page); kfree(fs_locations); out: dprintk("%s: done\n", __func__); return mnt; } struct vfsmount *nfs4_submount(struct nfs_server *server, struct dentry *dentry, struct nfs_fh *fh, struct nfs_fattr *fattr) { struct dentry *parent = dget_parent(dentry); struct rpc_clnt *client; struct vfsmount *mnt; /* Look it up again to get its attributes and sec flavor */ client = nfs4_proc_lookup_mountpoint(parent->d_inode, &dentry->d_name, fh, fattr); dput(parent); if (IS_ERR(client)) return ERR_CAST(client); if (fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL) mnt = nfs_do_refmount(client, dentry); else mnt = nfs_do_submount(dentry, fh, fattr, client->cl_auth->au_flavor); rpc_shutdown_client(client); return mnt; }
static void * nfs_follow_mountpoint(struct dentry *dentry, struct nameidata *nd) { struct vfsmount *mnt; struct nfs_server *server = NFS_SERVER(dentry->d_inode); struct dentry *parent; struct nfs_fh *fh = NULL; struct nfs_fattr *fattr = NULL; int err; dprintk("--> nfs_follow_mountpoint()\n"); err = -ESTALE; if (IS_ROOT(dentry)) goto out_err; err = -ENOMEM; fh = nfs_alloc_fhandle(); fattr = nfs_alloc_fattr(); if (fh == NULL || fattr == NULL) goto out_err; dprintk("%s: enter\n", __func__); dput(nd->path.dentry); nd->path.dentry = dget(dentry); /* Look it up again */ parent = dget_parent(nd->path.dentry); err = server->nfs_client->rpc_ops->lookup(parent->d_inode, &nd->path.dentry->d_name, fh, fattr); dput(parent); if (err != 0) goto out_err; if (fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL) mnt = nfs_do_refmount(nd->path.mnt, nd->path.dentry); else mnt = nfs_do_submount(nd->path.mnt, nd->path.dentry, fh, fattr); err = PTR_ERR(mnt); if (IS_ERR(mnt)) goto out_err; mntget(mnt); err = do_add_mount(mnt, &nd->path, nd->path.mnt->mnt_flags|MNT_SHRINKABLE, &nfs_automount_list); if (err < 0) { mntput(mnt); if (err == -EBUSY) goto out_follow; goto out_err; } path_put(&nd->path); nd->path.mnt = mnt; nd->path.dentry = dget(mnt->mnt_root); schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout); out: nfs_free_fattr(fattr); nfs_free_fhandle(fh); dprintk("%s: done, returned %d\n", __func__, err); dprintk("<-- nfs_follow_mountpoint() = %d\n", err); return ERR_PTR(err); out_err: path_put(&nd->path); goto out; out_follow: while (d_mountpoint(nd->path.dentry) && follow_down(&nd->path)) ; err = 0; goto out; }