f3f8014862
...and add a mechanism for switching between the "legacy" tracker and the new one. The decision is made by looking to see whether the v4recoverydir exists. If it does, then the legacy client tracker is used. If it's not, then the kernel will create a "cld" pipe in rpc_pipefs. That pipe is used to talk to a daemon for handling the upcall. Most of the data structures for the new client tracker are handled on a per-namespace basis, so this upcall should be essentially ready for containerization. For now however, nfsd just starts it by calling the initialization and exit functions for init_net. I'm making the assumption that at some point in the future we'll be able to determine the net namespace from the nfs4_client. Until then, this patch hardcodes init_net in those places. I've sprinkled some "FIXME" comments around that code to attempt to make it clear where we'll need to fix that up later. Signed-off-by: Jeff Layton <jlayton@redhat.com> Signed-off-by: J. Bruce Fields <bfields@redhat.com>
985 lines
22 KiB
C
985 lines
22 KiB
C
/*
|
|
* Copyright (c) 2004 The Regents of the University of Michigan.
|
|
* Copyright (c) 2012 Jeff Layton <jlayton@redhat.com>
|
|
* All rights reserved.
|
|
*
|
|
* Andy Adamson <andros@citi.umich.edu>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <linux/file.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/crypto.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/fs.h>
|
|
#include <net/net_namespace.h>
|
|
#include <linux/sunrpc/rpc_pipe_fs.h>
|
|
#include <linux/sunrpc/clnt.h>
|
|
#include <linux/nfsd/cld.h>
|
|
|
|
#include "nfsd.h"
|
|
#include "state.h"
|
|
#include "vfs.h"
|
|
#include "netns.h"
|
|
|
|
#define NFSDDBG_FACILITY NFSDDBG_PROC
|
|
|
|
/* Declarations */
|
|
struct nfsd4_client_tracking_ops {
|
|
int (*init)(struct net *);
|
|
void (*exit)(struct net *);
|
|
void (*create)(struct nfs4_client *);
|
|
void (*remove)(struct nfs4_client *);
|
|
int (*check)(struct nfs4_client *);
|
|
void (*grace_done)(struct net *, time_t);
|
|
};
|
|
|
|
/* Globals */
|
|
static struct file *rec_file;
|
|
static char user_recovery_dirname[PATH_MAX] = "/var/lib/nfs/v4recovery";
|
|
static struct nfsd4_client_tracking_ops *client_tracking_ops;
|
|
|
|
static int
|
|
nfs4_save_creds(const struct cred **original_creds)
|
|
{
|
|
struct cred *new;
|
|
|
|
new = prepare_creds();
|
|
if (!new)
|
|
return -ENOMEM;
|
|
|
|
new->fsuid = 0;
|
|
new->fsgid = 0;
|
|
*original_creds = override_creds(new);
|
|
put_cred(new);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
nfs4_reset_creds(const struct cred *original)
|
|
{
|
|
revert_creds(original);
|
|
}
|
|
|
|
static void
|
|
md5_to_hex(char *out, char *md5)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<16; i++) {
|
|
unsigned char c = md5[i];
|
|
|
|
*out++ = '0' + ((c&0xf0)>>4) + (c>=0xa0)*('a'-'9'-1);
|
|
*out++ = '0' + (c&0x0f) + ((c&0x0f)>=0x0a)*('a'-'9'-1);
|
|
}
|
|
*out = '\0';
|
|
}
|
|
|
|
__be32
|
|
nfs4_make_rec_clidname(char *dname, struct xdr_netobj *clname)
|
|
{
|
|
struct xdr_netobj cksum;
|
|
struct hash_desc desc;
|
|
struct scatterlist sg;
|
|
__be32 status = nfserr_jukebox;
|
|
|
|
dprintk("NFSD: nfs4_make_rec_clidname for %.*s\n",
|
|
clname->len, clname->data);
|
|
desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP;
|
|
desc.tfm = crypto_alloc_hash("md5", 0, CRYPTO_ALG_ASYNC);
|
|
if (IS_ERR(desc.tfm))
|
|
goto out_no_tfm;
|
|
cksum.len = crypto_hash_digestsize(desc.tfm);
|
|
cksum.data = kmalloc(cksum.len, GFP_KERNEL);
|
|
if (cksum.data == NULL)
|
|
goto out;
|
|
|
|
sg_init_one(&sg, clname->data, clname->len);
|
|
|
|
if (crypto_hash_digest(&desc, &sg, sg.length, cksum.data))
|
|
goto out;
|
|
|
|
md5_to_hex(dname, cksum.data);
|
|
|
|
status = nfs_ok;
|
|
out:
|
|
kfree(cksum.data);
|
|
crypto_free_hash(desc.tfm);
|
|
out_no_tfm:
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
nfsd4_create_clid_dir(struct nfs4_client *clp)
|
|
{
|
|
const struct cred *original_cred;
|
|
char *dname = clp->cl_recdir;
|
|
struct dentry *dir, *dentry;
|
|
int status;
|
|
|
|
dprintk("NFSD: nfsd4_create_clid_dir for \"%s\"\n", dname);
|
|
|
|
if (test_and_set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
|
|
return;
|
|
if (!rec_file)
|
|
return;
|
|
status = nfs4_save_creds(&original_cred);
|
|
if (status < 0)
|
|
return;
|
|
|
|
dir = rec_file->f_path.dentry;
|
|
/* lock the parent */
|
|
mutex_lock(&dir->d_inode->i_mutex);
|
|
|
|
dentry = lookup_one_len(dname, dir, HEXDIR_LEN-1);
|
|
if (IS_ERR(dentry)) {
|
|
status = PTR_ERR(dentry);
|
|
goto out_unlock;
|
|
}
|
|
if (dentry->d_inode)
|
|
/*
|
|
* In the 4.1 case, where we're called from
|
|
* reclaim_complete(), records from the previous reboot
|
|
* may still be left, so this is OK.
|
|
*
|
|
* In the 4.0 case, we should never get here; but we may
|
|
* as well be forgiving and just succeed silently.
|
|
*/
|
|
goto out_put;
|
|
status = mnt_want_write_file(rec_file);
|
|
if (status)
|
|
goto out_put;
|
|
status = vfs_mkdir(dir->d_inode, dentry, S_IRWXU);
|
|
mnt_drop_write_file(rec_file);
|
|
out_put:
|
|
dput(dentry);
|
|
out_unlock:
|
|
mutex_unlock(&dir->d_inode->i_mutex);
|
|
if (status == 0)
|
|
vfs_fsync(rec_file, 0);
|
|
else
|
|
printk(KERN_ERR "NFSD: failed to write recovery record"
|
|
" (err %d); please check that %s exists"
|
|
" and is writeable", status,
|
|
user_recovery_dirname);
|
|
nfs4_reset_creds(original_cred);
|
|
}
|
|
|
|
typedef int (recdir_func)(struct dentry *, struct dentry *);
|
|
|
|
struct name_list {
|
|
char name[HEXDIR_LEN];
|
|
struct list_head list;
|
|
};
|
|
|
|
static int
|
|
nfsd4_build_namelist(void *arg, const char *name, int namlen,
|
|
loff_t offset, u64 ino, unsigned int d_type)
|
|
{
|
|
struct list_head *names = arg;
|
|
struct name_list *entry;
|
|
|
|
if (namlen != HEXDIR_LEN - 1)
|
|
return 0;
|
|
entry = kmalloc(sizeof(struct name_list), GFP_KERNEL);
|
|
if (entry == NULL)
|
|
return -ENOMEM;
|
|
memcpy(entry->name, name, HEXDIR_LEN - 1);
|
|
entry->name[HEXDIR_LEN - 1] = '\0';
|
|
list_add(&entry->list, names);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
nfsd4_list_rec_dir(recdir_func *f)
|
|
{
|
|
const struct cred *original_cred;
|
|
struct dentry *dir = rec_file->f_path.dentry;
|
|
LIST_HEAD(names);
|
|
int status;
|
|
|
|
status = nfs4_save_creds(&original_cred);
|
|
if (status < 0)
|
|
return status;
|
|
|
|
status = vfs_llseek(rec_file, 0, SEEK_SET);
|
|
if (status < 0) {
|
|
nfs4_reset_creds(original_cred);
|
|
return status;
|
|
}
|
|
|
|
status = vfs_readdir(rec_file, nfsd4_build_namelist, &names);
|
|
mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
|
|
while (!list_empty(&names)) {
|
|
struct name_list *entry;
|
|
entry = list_entry(names.next, struct name_list, list);
|
|
if (!status) {
|
|
struct dentry *dentry;
|
|
dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
|
|
if (IS_ERR(dentry)) {
|
|
status = PTR_ERR(dentry);
|
|
break;
|
|
}
|
|
status = f(dir, dentry);
|
|
dput(dentry);
|
|
}
|
|
list_del(&entry->list);
|
|
kfree(entry);
|
|
}
|
|
mutex_unlock(&dir->d_inode->i_mutex);
|
|
nfs4_reset_creds(original_cred);
|
|
return status;
|
|
}
|
|
|
|
static int
|
|
nfsd4_unlink_clid_dir(char *name, int namlen)
|
|
{
|
|
struct dentry *dir, *dentry;
|
|
int status;
|
|
|
|
dprintk("NFSD: nfsd4_unlink_clid_dir. name %.*s\n", namlen, name);
|
|
|
|
dir = rec_file->f_path.dentry;
|
|
mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
|
|
dentry = lookup_one_len(name, dir, namlen);
|
|
if (IS_ERR(dentry)) {
|
|
status = PTR_ERR(dentry);
|
|
goto out_unlock;
|
|
}
|
|
status = -ENOENT;
|
|
if (!dentry->d_inode)
|
|
goto out;
|
|
status = vfs_rmdir(dir->d_inode, dentry);
|
|
out:
|
|
dput(dentry);
|
|
out_unlock:
|
|
mutex_unlock(&dir->d_inode->i_mutex);
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
nfsd4_remove_clid_dir(struct nfs4_client *clp)
|
|
{
|
|
const struct cred *original_cred;
|
|
int status;
|
|
|
|
if (!rec_file || !test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
|
|
return;
|
|
|
|
status = mnt_want_write_file(rec_file);
|
|
if (status)
|
|
goto out;
|
|
clear_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
|
|
|
|
status = nfs4_save_creds(&original_cred);
|
|
if (status < 0)
|
|
goto out;
|
|
|
|
status = nfsd4_unlink_clid_dir(clp->cl_recdir, HEXDIR_LEN-1);
|
|
nfs4_reset_creds(original_cred);
|
|
if (status == 0)
|
|
vfs_fsync(rec_file, 0);
|
|
mnt_drop_write_file(rec_file);
|
|
out:
|
|
if (status)
|
|
printk("NFSD: Failed to remove expired client state directory"
|
|
" %.*s\n", HEXDIR_LEN, clp->cl_recdir);
|
|
}
|
|
|
|
static int
|
|
purge_old(struct dentry *parent, struct dentry *child)
|
|
{
|
|
int status;
|
|
|
|
if (nfs4_has_reclaimed_state(child->d_name.name, false))
|
|
return 0;
|
|
|
|
status = vfs_rmdir(parent->d_inode, child);
|
|
if (status)
|
|
printk("failed to remove client recovery directory %s\n",
|
|
child->d_name.name);
|
|
/* Keep trying, success or failure: */
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
nfsd4_recdir_purge_old(struct net *net, time_t boot_time)
|
|
{
|
|
int status;
|
|
|
|
if (!rec_file)
|
|
return;
|
|
status = mnt_want_write_file(rec_file);
|
|
if (status)
|
|
goto out;
|
|
status = nfsd4_list_rec_dir(purge_old);
|
|
if (status == 0)
|
|
vfs_fsync(rec_file, 0);
|
|
mnt_drop_write_file(rec_file);
|
|
out:
|
|
if (status)
|
|
printk("nfsd4: failed to purge old clients from recovery"
|
|
" directory %s\n", rec_file->f_path.dentry->d_name.name);
|
|
}
|
|
|
|
static int
|
|
load_recdir(struct dentry *parent, struct dentry *child)
|
|
{
|
|
if (child->d_name.len != HEXDIR_LEN - 1) {
|
|
printk("nfsd4: illegal name %s in recovery directory\n",
|
|
child->d_name.name);
|
|
/* Keep trying; maybe the others are OK: */
|
|
return 0;
|
|
}
|
|
nfs4_client_to_reclaim(child->d_name.name);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
nfsd4_recdir_load(void) {
|
|
int status;
|
|
|
|
if (!rec_file)
|
|
return 0;
|
|
|
|
status = nfsd4_list_rec_dir(load_recdir);
|
|
if (status)
|
|
printk("nfsd4: failed loading clients from recovery"
|
|
" directory %s\n", rec_file->f_path.dentry->d_name.name);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Hold reference to the recovery directory.
|
|
*/
|
|
|
|
static int
|
|
nfsd4_init_recdir(void)
|
|
{
|
|
const struct cred *original_cred;
|
|
int status;
|
|
|
|
printk("NFSD: Using %s as the NFSv4 state recovery directory\n",
|
|
user_recovery_dirname);
|
|
|
|
BUG_ON(rec_file);
|
|
|
|
status = nfs4_save_creds(&original_cred);
|
|
if (status < 0) {
|
|
printk("NFSD: Unable to change credentials to find recovery"
|
|
" directory: error %d\n",
|
|
status);
|
|
return status;
|
|
}
|
|
|
|
rec_file = filp_open(user_recovery_dirname, O_RDONLY | O_DIRECTORY, 0);
|
|
if (IS_ERR(rec_file)) {
|
|
printk("NFSD: unable to find recovery directory %s\n",
|
|
user_recovery_dirname);
|
|
status = PTR_ERR(rec_file);
|
|
rec_file = NULL;
|
|
}
|
|
|
|
nfs4_reset_creds(original_cred);
|
|
return status;
|
|
}
|
|
|
|
static int
|
|
nfsd4_load_reboot_recovery_data(struct net *net)
|
|
{
|
|
int status;
|
|
|
|
nfs4_lock_state();
|
|
status = nfsd4_init_recdir();
|
|
if (!status)
|
|
status = nfsd4_recdir_load();
|
|
nfs4_unlock_state();
|
|
if (status)
|
|
printk(KERN_ERR "NFSD: Failure reading reboot recovery data\n");
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
nfsd4_shutdown_recdir(void)
|
|
{
|
|
if (!rec_file)
|
|
return;
|
|
fput(rec_file);
|
|
rec_file = NULL;
|
|
}
|
|
|
|
static void
|
|
nfsd4_legacy_tracking_exit(struct net *net)
|
|
{
|
|
nfs4_release_reclaim();
|
|
nfsd4_shutdown_recdir();
|
|
}
|
|
|
|
/*
|
|
* Change the NFSv4 recovery directory to recdir.
|
|
*/
|
|
int
|
|
nfs4_reset_recoverydir(char *recdir)
|
|
{
|
|
int status;
|
|
struct path path;
|
|
|
|
status = kern_path(recdir, LOOKUP_FOLLOW, &path);
|
|
if (status)
|
|
return status;
|
|
status = -ENOTDIR;
|
|
if (S_ISDIR(path.dentry->d_inode->i_mode)) {
|
|
strcpy(user_recovery_dirname, recdir);
|
|
status = 0;
|
|
}
|
|
path_put(&path);
|
|
return status;
|
|
}
|
|
|
|
char *
|
|
nfs4_recoverydir(void)
|
|
{
|
|
return user_recovery_dirname;
|
|
}
|
|
|
|
static int
|
|
nfsd4_check_legacy_client(struct nfs4_client *clp)
|
|
{
|
|
/* did we already find that this client is stable? */
|
|
if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
|
|
return 0;
|
|
|
|
/* look for it in the reclaim hashtable otherwise */
|
|
if (nfsd4_find_reclaim_client(clp)) {
|
|
set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
|
|
return 0;
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
static struct nfsd4_client_tracking_ops nfsd4_legacy_tracking_ops = {
|
|
.init = nfsd4_load_reboot_recovery_data,
|
|
.exit = nfsd4_legacy_tracking_exit,
|
|
.create = nfsd4_create_clid_dir,
|
|
.remove = nfsd4_remove_clid_dir,
|
|
.check = nfsd4_check_legacy_client,
|
|
.grace_done = nfsd4_recdir_purge_old,
|
|
};
|
|
|
|
/* Globals */
|
|
#define NFSD_PIPE_DIR "nfsd"
|
|
#define NFSD_CLD_PIPE "cld"
|
|
|
|
/* per-net-ns structure for holding cld upcall info */
|
|
struct cld_net {
|
|
struct rpc_pipe *cn_pipe;
|
|
spinlock_t cn_lock;
|
|
struct list_head cn_list;
|
|
unsigned int cn_xid;
|
|
};
|
|
|
|
struct cld_upcall {
|
|
struct list_head cu_list;
|
|
struct cld_net *cu_net;
|
|
struct task_struct *cu_task;
|
|
struct cld_msg cu_msg;
|
|
};
|
|
|
|
static int
|
|
__cld_pipe_upcall(struct rpc_pipe *pipe, struct cld_msg *cmsg)
|
|
{
|
|
int ret;
|
|
struct rpc_pipe_msg msg;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.data = cmsg;
|
|
msg.len = sizeof(*cmsg);
|
|
|
|
/*
|
|
* Set task state before we queue the upcall. That prevents
|
|
* wake_up_process in the downcall from racing with schedule.
|
|
*/
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
ret = rpc_queue_upcall(pipe, &msg);
|
|
if (ret < 0) {
|
|
set_current_state(TASK_RUNNING);
|
|
goto out;
|
|
}
|
|
|
|
schedule();
|
|
set_current_state(TASK_RUNNING);
|
|
|
|
if (msg.errno < 0)
|
|
ret = msg.errno;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
cld_pipe_upcall(struct rpc_pipe *pipe, struct cld_msg *cmsg)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* -EAGAIN occurs when pipe is closed and reopened while there are
|
|
* upcalls queued.
|
|
*/
|
|
do {
|
|
ret = __cld_pipe_upcall(pipe, cmsg);
|
|
} while (ret == -EAGAIN);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t
|
|
cld_pipe_downcall(struct file *filp, const char __user *src, size_t mlen)
|
|
{
|
|
struct cld_upcall *tmp, *cup;
|
|
struct cld_msg *cmsg = (struct cld_msg *)src;
|
|
uint32_t xid;
|
|
struct nfsd_net *nn = net_generic(filp->f_dentry->d_sb->s_fs_info,
|
|
nfsd_net_id);
|
|
struct cld_net *cn = nn->cld_net;
|
|
|
|
if (mlen != sizeof(*cmsg)) {
|
|
dprintk("%s: got %lu bytes, expected %lu\n", __func__, mlen,
|
|
sizeof(*cmsg));
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* copy just the xid so we can try to find that */
|
|
if (copy_from_user(&xid, &cmsg->cm_xid, sizeof(xid)) != 0) {
|
|
dprintk("%s: error when copying xid from userspace", __func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* walk the list and find corresponding xid */
|
|
cup = NULL;
|
|
spin_lock(&cn->cn_lock);
|
|
list_for_each_entry(tmp, &cn->cn_list, cu_list) {
|
|
if (get_unaligned(&tmp->cu_msg.cm_xid) == xid) {
|
|
cup = tmp;
|
|
list_del_init(&cup->cu_list);
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock(&cn->cn_lock);
|
|
|
|
/* couldn't find upcall? */
|
|
if (!cup) {
|
|
dprintk("%s: couldn't find upcall -- xid=%u\n", __func__,
|
|
cup->cu_msg.cm_xid);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (copy_from_user(&cup->cu_msg, src, mlen) != 0)
|
|
return -EFAULT;
|
|
|
|
wake_up_process(cup->cu_task);
|
|
return mlen;
|
|
}
|
|
|
|
static void
|
|
cld_pipe_destroy_msg(struct rpc_pipe_msg *msg)
|
|
{
|
|
struct cld_msg *cmsg = msg->data;
|
|
struct cld_upcall *cup = container_of(cmsg, struct cld_upcall,
|
|
cu_msg);
|
|
|
|
/* errno >= 0 means we got a downcall */
|
|
if (msg->errno >= 0)
|
|
return;
|
|
|
|
wake_up_process(cup->cu_task);
|
|
}
|
|
|
|
static const struct rpc_pipe_ops cld_upcall_ops = {
|
|
.upcall = rpc_pipe_generic_upcall,
|
|
.downcall = cld_pipe_downcall,
|
|
.destroy_msg = cld_pipe_destroy_msg,
|
|
};
|
|
|
|
static struct dentry *
|
|
nfsd4_cld_register_sb(struct super_block *sb, struct rpc_pipe *pipe)
|
|
{
|
|
struct dentry *dir, *dentry;
|
|
|
|
dir = rpc_d_lookup_sb(sb, NFSD_PIPE_DIR);
|
|
if (dir == NULL)
|
|
return ERR_PTR(-ENOENT);
|
|
dentry = rpc_mkpipe_dentry(dir, NFSD_CLD_PIPE, NULL, pipe);
|
|
dput(dir);
|
|
return dentry;
|
|
}
|
|
|
|
static void
|
|
nfsd4_cld_unregister_sb(struct rpc_pipe *pipe)
|
|
{
|
|
if (pipe->dentry)
|
|
rpc_unlink(pipe->dentry);
|
|
}
|
|
|
|
static struct dentry *
|
|
nfsd4_cld_register_net(struct net *net, struct rpc_pipe *pipe)
|
|
{
|
|
struct super_block *sb;
|
|
struct dentry *dentry;
|
|
|
|
sb = rpc_get_sb_net(net);
|
|
if (!sb)
|
|
return NULL;
|
|
dentry = nfsd4_cld_register_sb(sb, pipe);
|
|
rpc_put_sb_net(net);
|
|
return dentry;
|
|
}
|
|
|
|
static void
|
|
nfsd4_cld_unregister_net(struct net *net, struct rpc_pipe *pipe)
|
|
{
|
|
struct super_block *sb;
|
|
|
|
sb = rpc_get_sb_net(net);
|
|
if (sb) {
|
|
nfsd4_cld_unregister_sb(pipe);
|
|
rpc_put_sb_net(net);
|
|
}
|
|
}
|
|
|
|
/* Initialize rpc_pipefs pipe for communication with client tracking daemon */
|
|
static int
|
|
nfsd4_init_cld_pipe(struct net *net)
|
|
{
|
|
int ret;
|
|
struct dentry *dentry;
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
|
struct cld_net *cn;
|
|
|
|
if (nn->cld_net)
|
|
return 0;
|
|
|
|
cn = kzalloc(sizeof(*cn), GFP_KERNEL);
|
|
if (!cn) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
cn->cn_pipe = rpc_mkpipe_data(&cld_upcall_ops, RPC_PIPE_WAIT_FOR_OPEN);
|
|
if (IS_ERR(cn->cn_pipe)) {
|
|
ret = PTR_ERR(cn->cn_pipe);
|
|
goto err;
|
|
}
|
|
spin_lock_init(&cn->cn_lock);
|
|
INIT_LIST_HEAD(&cn->cn_list);
|
|
|
|
dentry = nfsd4_cld_register_net(net, cn->cn_pipe);
|
|
if (IS_ERR(dentry)) {
|
|
ret = PTR_ERR(dentry);
|
|
goto err_destroy_data;
|
|
}
|
|
|
|
cn->cn_pipe->dentry = dentry;
|
|
nn->cld_net = cn;
|
|
return 0;
|
|
|
|
err_destroy_data:
|
|
rpc_destroy_pipe_data(cn->cn_pipe);
|
|
err:
|
|
kfree(cn);
|
|
printk(KERN_ERR "NFSD: unable to create nfsdcld upcall pipe (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
nfsd4_remove_cld_pipe(struct net *net)
|
|
{
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
|
struct cld_net *cn = nn->cld_net;
|
|
|
|
nfsd4_cld_unregister_net(net, cn->cn_pipe);
|
|
rpc_destroy_pipe_data(cn->cn_pipe);
|
|
kfree(nn->cld_net);
|
|
nn->cld_net = NULL;
|
|
}
|
|
|
|
static struct cld_upcall *
|
|
alloc_cld_upcall(struct cld_net *cn)
|
|
{
|
|
struct cld_upcall *new, *tmp;
|
|
|
|
new = kzalloc(sizeof(*new), GFP_KERNEL);
|
|
if (!new)
|
|
return new;
|
|
|
|
/* FIXME: hard cap on number in flight? */
|
|
restart_search:
|
|
spin_lock(&cn->cn_lock);
|
|
list_for_each_entry(tmp, &cn->cn_list, cu_list) {
|
|
if (tmp->cu_msg.cm_xid == cn->cn_xid) {
|
|
cn->cn_xid++;
|
|
spin_unlock(&cn->cn_lock);
|
|
goto restart_search;
|
|
}
|
|
}
|
|
new->cu_task = current;
|
|
new->cu_msg.cm_vers = CLD_UPCALL_VERSION;
|
|
put_unaligned(cn->cn_xid++, &new->cu_msg.cm_xid);
|
|
new->cu_net = cn;
|
|
list_add(&new->cu_list, &cn->cn_list);
|
|
spin_unlock(&cn->cn_lock);
|
|
|
|
dprintk("%s: allocated xid %u\n", __func__, new->cu_msg.cm_xid);
|
|
|
|
return new;
|
|
}
|
|
|
|
static void
|
|
free_cld_upcall(struct cld_upcall *victim)
|
|
{
|
|
struct cld_net *cn = victim->cu_net;
|
|
|
|
spin_lock(&cn->cn_lock);
|
|
list_del(&victim->cu_list);
|
|
spin_unlock(&cn->cn_lock);
|
|
kfree(victim);
|
|
}
|
|
|
|
/* Ask daemon to create a new record */
|
|
static void
|
|
nfsd4_cld_create(struct nfs4_client *clp)
|
|
{
|
|
int ret;
|
|
struct cld_upcall *cup;
|
|
/* FIXME: determine net from clp */
|
|
struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
|
|
struct cld_net *cn = nn->cld_net;
|
|
|
|
/* Don't upcall if it's already stored */
|
|
if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
|
|
return;
|
|
|
|
cup = alloc_cld_upcall(cn);
|
|
if (!cup) {
|
|
ret = -ENOMEM;
|
|
goto out_err;
|
|
}
|
|
|
|
cup->cu_msg.cm_cmd = Cld_Create;
|
|
cup->cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
|
|
memcpy(cup->cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
|
|
clp->cl_name.len);
|
|
|
|
ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
|
|
if (!ret) {
|
|
ret = cup->cu_msg.cm_status;
|
|
set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
|
|
}
|
|
|
|
free_cld_upcall(cup);
|
|
out_err:
|
|
if (ret)
|
|
printk(KERN_ERR "NFSD: Unable to create client "
|
|
"record on stable storage: %d\n", ret);
|
|
}
|
|
|
|
/* Ask daemon to create a new record */
|
|
static void
|
|
nfsd4_cld_remove(struct nfs4_client *clp)
|
|
{
|
|
int ret;
|
|
struct cld_upcall *cup;
|
|
/* FIXME: determine net from clp */
|
|
struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
|
|
struct cld_net *cn = nn->cld_net;
|
|
|
|
/* Don't upcall if it's already removed */
|
|
if (!test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
|
|
return;
|
|
|
|
cup = alloc_cld_upcall(cn);
|
|
if (!cup) {
|
|
ret = -ENOMEM;
|
|
goto out_err;
|
|
}
|
|
|
|
cup->cu_msg.cm_cmd = Cld_Remove;
|
|
cup->cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
|
|
memcpy(cup->cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
|
|
clp->cl_name.len);
|
|
|
|
ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
|
|
if (!ret) {
|
|
ret = cup->cu_msg.cm_status;
|
|
clear_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
|
|
}
|
|
|
|
free_cld_upcall(cup);
|
|
out_err:
|
|
if (ret)
|
|
printk(KERN_ERR "NFSD: Unable to remove client "
|
|
"record from stable storage: %d\n", ret);
|
|
}
|
|
|
|
/* Check for presence of a record, and update its timestamp */
|
|
static int
|
|
nfsd4_cld_check(struct nfs4_client *clp)
|
|
{
|
|
int ret;
|
|
struct cld_upcall *cup;
|
|
/* FIXME: determine net from clp */
|
|
struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
|
|
struct cld_net *cn = nn->cld_net;
|
|
|
|
/* Don't upcall if one was already stored during this grace pd */
|
|
if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
|
|
return 0;
|
|
|
|
cup = alloc_cld_upcall(cn);
|
|
if (!cup) {
|
|
printk(KERN_ERR "NFSD: Unable to check client record on "
|
|
"stable storage: %d\n", -ENOMEM);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cup->cu_msg.cm_cmd = Cld_Check;
|
|
cup->cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
|
|
memcpy(cup->cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
|
|
clp->cl_name.len);
|
|
|
|
ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
|
|
if (!ret) {
|
|
ret = cup->cu_msg.cm_status;
|
|
set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
|
|
}
|
|
|
|
free_cld_upcall(cup);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
nfsd4_cld_grace_done(struct net *net, time_t boot_time)
|
|
{
|
|
int ret;
|
|
struct cld_upcall *cup;
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
|
struct cld_net *cn = nn->cld_net;
|
|
|
|
cup = alloc_cld_upcall(cn);
|
|
if (!cup) {
|
|
ret = -ENOMEM;
|
|
goto out_err;
|
|
}
|
|
|
|
cup->cu_msg.cm_cmd = Cld_GraceDone;
|
|
cup->cu_msg.cm_u.cm_gracetime = (int64_t)boot_time;
|
|
ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
|
|
if (!ret)
|
|
ret = cup->cu_msg.cm_status;
|
|
|
|
free_cld_upcall(cup);
|
|
out_err:
|
|
if (ret)
|
|
printk(KERN_ERR "NFSD: Unable to end grace period: %d\n", ret);
|
|
}
|
|
|
|
static struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops = {
|
|
.init = nfsd4_init_cld_pipe,
|
|
.exit = nfsd4_remove_cld_pipe,
|
|
.create = nfsd4_cld_create,
|
|
.remove = nfsd4_cld_remove,
|
|
.check = nfsd4_cld_check,
|
|
.grace_done = nfsd4_cld_grace_done,
|
|
};
|
|
|
|
int
|
|
nfsd4_client_tracking_init(struct net *net)
|
|
{
|
|
int status;
|
|
struct path path;
|
|
|
|
if (!client_tracking_ops) {
|
|
client_tracking_ops = &nfsd4_cld_tracking_ops;
|
|
status = kern_path(nfs4_recoverydir(), LOOKUP_FOLLOW, &path);
|
|
if (!status) {
|
|
if (S_ISDIR(path.dentry->d_inode->i_mode))
|
|
client_tracking_ops =
|
|
&nfsd4_legacy_tracking_ops;
|
|
path_put(&path);
|
|
}
|
|
}
|
|
|
|
status = client_tracking_ops->init(net);
|
|
if (status) {
|
|
printk(KERN_WARNING "NFSD: Unable to initialize client "
|
|
"recovery tracking! (%d)\n", status);
|
|
client_tracking_ops = NULL;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
void
|
|
nfsd4_client_tracking_exit(struct net *net)
|
|
{
|
|
if (client_tracking_ops) {
|
|
client_tracking_ops->exit(net);
|
|
client_tracking_ops = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
nfsd4_client_record_create(struct nfs4_client *clp)
|
|
{
|
|
if (client_tracking_ops)
|
|
client_tracking_ops->create(clp);
|
|
}
|
|
|
|
void
|
|
nfsd4_client_record_remove(struct nfs4_client *clp)
|
|
{
|
|
if (client_tracking_ops)
|
|
client_tracking_ops->remove(clp);
|
|
}
|
|
|
|
int
|
|
nfsd4_client_record_check(struct nfs4_client *clp)
|
|
{
|
|
if (client_tracking_ops)
|
|
return client_tracking_ops->check(clp);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
void
|
|
nfsd4_record_grace_done(struct net *net, time_t boot_time)
|
|
{
|
|
if (client_tracking_ops)
|
|
client_tracking_ops->grace_done(net, boot_time);
|
|
}
|