65f27f3844
Pass the work_struct pointer to the work function rather than context data. The work function can use container_of() to work out the data. For the cases where the container of the work_struct may go away the moment the pending bit is cleared, it is made possible to defer the release of the structure by deferring the clearing of the pending bit. To make this work, an extra flag is introduced into the management side of the work_struct. This governs auto-release of the structure upon execution. Ordinarily, the work queue executor would release the work_struct for further scheduling or deallocation by clearing the pending bit prior to jumping to the work function. This means that, unless the driver makes some guarantee itself that the work_struct won't go away, the work function may not access anything else in the work_struct or its container lest they be deallocated.. This is a problem if the auxiliary data is taken away (as done by the last patch). However, if the pending bit is *not* cleared before jumping to the work function, then the work function *may* access the work_struct and its container with no problems. But then the work function must itself release the work_struct by calling work_release(). In most cases, automatic release is fine, so this is the default. Special initiators exist for the non-auto-release case (ending in _NAR). Signed-Off-By: David Howells <dhowells@redhat.com>
250 lines
6.4 KiB
C
250 lines
6.4 KiB
C
/*
|
|
* linux/fs/nfs/namespace.c
|
|
*
|
|
* Copyright (C) 2005 Trond Myklebust <Trond.Myklebust@netapp.com>
|
|
* - Modified by David Howells <dhowells@redhat.com>
|
|
*
|
|
* NFS namespace
|
|
*/
|
|
|
|
#include <linux/dcache.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/nfs_fs.h>
|
|
#include <linux/string.h>
|
|
#include <linux/sunrpc/clnt.h>
|
|
#include <linux/vfs.h>
|
|
#include "internal.h"
|
|
|
|
#define NFSDBG_FACILITY NFSDBG_VFS
|
|
|
|
static void nfs_expire_automounts(struct work_struct *work);
|
|
|
|
LIST_HEAD(nfs_automount_list);
|
|
static DECLARE_DELAYED_WORK(nfs_automount_task, nfs_expire_automounts);
|
|
int nfs_mountpoint_expiry_timeout = 500 * HZ;
|
|
|
|
static struct vfsmount *nfs_do_submount(const struct vfsmount *mnt_parent,
|
|
const struct dentry *dentry,
|
|
struct nfs_fh *fh,
|
|
struct nfs_fattr *fattr);
|
|
|
|
/*
|
|
* nfs_path - reconstruct the path given an arbitrary dentry
|
|
* @base - arbitrary string to prepend to the path
|
|
* @droot - pointer to root dentry for mountpoint
|
|
* @dentry - pointer to dentry
|
|
* @buffer - result buffer
|
|
* @buflen - length of buffer
|
|
*
|
|
* Helper function for constructing the path from the
|
|
* root dentry to an arbitrary hashed dentry.
|
|
*
|
|
* This is mainly for use in figuring out the path on the
|
|
* server side when automounting on top of an existing partition.
|
|
*/
|
|
char *nfs_path(const char *base,
|
|
const struct dentry *droot,
|
|
const struct dentry *dentry,
|
|
char *buffer, ssize_t buflen)
|
|
{
|
|
char *end = buffer+buflen;
|
|
int namelen;
|
|
|
|
*--end = '\0';
|
|
buflen--;
|
|
spin_lock(&dcache_lock);
|
|
while (!IS_ROOT(dentry) && dentry != droot) {
|
|
namelen = dentry->d_name.len;
|
|
buflen -= namelen + 1;
|
|
if (buflen < 0)
|
|
goto Elong_unlock;
|
|
end -= namelen;
|
|
memcpy(end, dentry->d_name.name, namelen);
|
|
*--end = '/';
|
|
dentry = dentry->d_parent;
|
|
}
|
|
spin_unlock(&dcache_lock);
|
|
namelen = strlen(base);
|
|
/* Strip off excess slashes in base string */
|
|
while (namelen > 0 && base[namelen - 1] == '/')
|
|
namelen--;
|
|
buflen -= namelen;
|
|
if (buflen < 0)
|
|
goto Elong;
|
|
end -= namelen;
|
|
memcpy(end, base, namelen);
|
|
return end;
|
|
Elong_unlock:
|
|
spin_unlock(&dcache_lock);
|
|
Elong:
|
|
return ERR_PTR(-ENAMETOOLONG);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
struct inode_operations nfs_mountpoint_inode_operations = {
|
|
.follow_link = nfs_follow_mountpoint,
|
|
.getattr = nfs_getattr,
|
|
};
|
|
|
|
struct inode_operations nfs_referral_inode_operations = {
|
|
.follow_link = nfs_follow_mountpoint,
|
|
};
|
|
|
|
static void nfs_expire_automounts(struct work_struct *work)
|
|
{
|
|
struct list_head *list = &nfs_automount_list;
|
|
|
|
mark_mounts_for_expiry(list);
|
|
if (!list_empty(list))
|
|
schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout);
|
|
}
|
|
|
|
void nfs_release_automount_timer(void)
|
|
{
|
|
if (list_empty(&nfs_automount_list)) {
|
|
cancel_delayed_work(&nfs_automount_task);
|
|
flush_scheduled_work();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clone a mountpoint of the appropriate type
|
|
*/
|
|
static struct vfsmount *nfs_do_clone_mount(struct nfs_server *server,
|
|
const char *devname,
|
|
struct nfs_clone_mount *mountdata)
|
|
{
|
|
#ifdef CONFIG_NFS_V4
|
|
struct vfsmount *mnt = NULL;
|
|
switch (server->nfs_client->cl_nfsversion) {
|
|
case 2:
|
|
case 3:
|
|
mnt = vfs_kern_mount(&nfs_xdev_fs_type, 0, devname, mountdata);
|
|
break;
|
|
case 4:
|
|
mnt = vfs_kern_mount(&nfs4_xdev_fs_type, 0, devname, mountdata);
|
|
}
|
|
return mnt;
|
|
#else
|
|
return vfs_kern_mount(&nfs_xdev_fs_type, 0, devname, mountdata);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* nfs_do_submount - set up mountpoint when crossing a filesystem boundary
|
|
* @mnt_parent - mountpoint of parent directory
|
|
* @dentry - parent directory
|
|
* @fh - filehandle for new root dentry
|
|
* @fattr - attributes for new root inode
|
|
*
|
|
*/
|
|
static struct vfsmount *nfs_do_submount(const struct vfsmount *mnt_parent,
|
|
const struct dentry *dentry,
|
|
struct nfs_fh *fh,
|
|
struct nfs_fattr *fattr)
|
|
{
|
|
struct nfs_clone_mount mountdata = {
|
|
.sb = mnt_parent->mnt_sb,
|
|
.dentry = dentry,
|
|
.fh = fh,
|
|
.fattr = fattr,
|
|
};
|
|
struct vfsmount *mnt = ERR_PTR(-ENOMEM);
|
|
char *page = (char *) __get_free_page(GFP_USER);
|
|
char *devname;
|
|
|
|
dprintk("--> nfs_do_submount()\n");
|
|
|
|
dprintk("%s: submounting on %s/%s\n", __FUNCTION__,
|
|
dentry->d_parent->d_name.name,
|
|
dentry->d_name.name);
|
|
if (page == NULL)
|
|
goto out;
|
|
devname = nfs_devname(mnt_parent, dentry, page, PAGE_SIZE);
|
|
mnt = (struct vfsmount *)devname;
|
|
if (IS_ERR(devname))
|
|
goto free_page;
|
|
mnt = nfs_do_clone_mount(NFS_SB(mnt_parent->mnt_sb), devname, &mountdata);
|
|
free_page:
|
|
free_page((unsigned long)page);
|
|
out:
|
|
dprintk("%s: done\n", __FUNCTION__);
|
|
|
|
dprintk("<-- nfs_do_submount() = %p\n", mnt);
|
|
return mnt;
|
|
}
|