#include <linux/sunrpc/svc.h>
#include <linux/sunrpc/svcsock.h>
#include <linux/nfs_fs.h>
+#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/freezer.h>
#include <linux/kthread.h>
#include "nfs4_fs.h"
#include "callback.h"
#include "internal.h"
+#include "netns.h"
#define NFSDBG_FACILITY NFSDBG_CALLBACK
static DEFINE_MUTEX(nfs_callback_mutex);
static struct svc_program nfs4_callback_program;
-unsigned short nfs_callback_tcpport6;
+static int nfs4_callback_up_net(struct svc_serv *serv, struct net *net)
+{
+ int ret;
+ struct nfs_net *nn = net_generic(net, nfs_net_id);
+
+ ret = svc_create_xprt(serv, "tcp", net, PF_INET,
+ nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);
+ if (ret <= 0)
+ goto out_err;
+ nn->nfs_callback_tcpport = ret;
+ dprintk("NFS: Callback listener port = %u (af %u, net %p)\n",
+ nn->nfs_callback_tcpport, PF_INET, net);
+
+ ret = svc_create_xprt(serv, "tcp", net, PF_INET6,
+ nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);
+ if (ret > 0) {
+ nn->nfs_callback_tcpport6 = ret;
+ dprintk("NFS: Callback listener port = %u (af %u, net %p)\n",
+ nn->nfs_callback_tcpport6, PF_INET6, net);
+ } else if (ret != -EAFNOSUPPORT)
+ goto out_err;
+ return 0;
+
+out_err:
+ return (ret) ? ret : -ENOMEM;
+}
/*
* This is the NFSv4 callback kernel thread.
static int
nfs4_callback_svc(void *vrqstp)
{
- int err, preverr = 0;
+ int err;
struct svc_rqst *rqstp = vrqstp;
set_freezable();
* Listen for a request on the socket
*/
err = svc_recv(rqstp, MAX_SCHEDULE_TIMEOUT);
- if (err == -EAGAIN || err == -EINTR) {
- preverr = err;
+ if (err == -EAGAIN || err == -EINTR)
continue;
- }
- if (err < 0) {
- if (err != preverr) {
- printk(KERN_WARNING "NFS: %s: unexpected error "
- "from svc_recv (%d)\n", __func__, err);
- preverr = err;
- }
- schedule_timeout_uninterruptible(HZ);
- continue;
- }
- preverr = err;
svc_process(rqstp);
}
return 0;
* Prepare to bring up the NFSv4 callback service
*/
static struct svc_rqst *
-nfs4_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt)
+nfs4_callback_up(struct svc_serv *serv)
{
- int ret;
-
- ret = svc_create_xprt(serv, "tcp", &init_net, PF_INET,
- nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);
- if (ret <= 0)
- goto out_err;
- nfs_callback_tcpport = ret;
- dprintk("NFS: Callback listener port = %u (af %u)\n",
- nfs_callback_tcpport, PF_INET);
-
- ret = svc_create_xprt(serv, "tcp", &init_net, PF_INET6,
- nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);
- if (ret > 0) {
- nfs_callback_tcpport6 = ret;
- dprintk("NFS: Callback listener port = %u (af %u)\n",
- nfs_callback_tcpport6, PF_INET6);
- } else if (ret == -EAFNOSUPPORT)
- ret = 0;
- else
- goto out_err;
-
return svc_prepare_thread(serv, &serv->sv_pools[0], NUMA_NO_NODE);
-
-out_err:
- if (ret == 0)
- ret = -ENOMEM;
- return ERR_PTR(ret);
}
#if defined(CONFIG_NFS_V4_1)
+static int nfs41_callback_up_net(struct svc_serv *serv, struct net *net)
+{
+ /*
+ * Create an svc_sock for the back channel service that shares the
+ * fore channel connection.
+ * Returns the input port (0) and sets the svc_serv bc_xprt on success
+ */
+ return svc_create_xprt(serv, "tcp-bc", net, PF_INET, 0,
+ SVC_SOCK_ANONYMOUS);
+}
+
/*
* The callback service for NFSv4.1 callbacks
*/
* Bring up the NFSv4.1 callback service
*/
static struct svc_rqst *
-nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt)
+nfs41_callback_up(struct svc_serv *serv)
{
struct svc_rqst *rqstp;
- int ret;
-
- /*
- * Create an svc_sock for the back channel service that shares the
- * fore channel connection.
- * Returns the input port (0) and sets the svc_serv bc_xprt on success
- */
- ret = svc_create_xprt(serv, "tcp-bc", &init_net, PF_INET, 0,
- SVC_SOCK_ANONYMOUS);
- if (ret < 0) {
- rqstp = ERR_PTR(ret);
- goto out;
- }
-
- /*
- * Save the svc_serv in the transport so that it can
- * be referenced when the session backchannel is initialized
- */
- xprt->bc_serv = serv;
INIT_LIST_HEAD(&serv->sv_cb_list);
spin_lock_init(&serv->sv_cb_lock);
svc_xprt_put(serv->sv_bc_xprt);
serv->sv_bc_xprt = NULL;
}
-out:
dprintk("--> %s return %ld\n", __func__,
IS_ERR(rqstp) ? PTR_ERR(rqstp) : 0);
return rqstp;
}
-static inline int nfs_minorversion_callback_svc_setup(u32 minorversion,
- struct svc_serv *serv, struct rpc_xprt *xprt,
+static void nfs_minorversion_callback_svc_setup(struct svc_serv *serv,
struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp))
{
- if (minorversion) {
- *rqstpp = nfs41_callback_up(serv, xprt);
- *callback_svc = nfs41_callback_svc;
- }
- return minorversion;
+ *rqstpp = nfs41_callback_up(serv);
+ *callback_svc = nfs41_callback_svc;
}
static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt,
- struct nfs_callback_data *cb_info)
+ struct svc_serv *serv)
{
if (minorversion)
- xprt->bc_serv = cb_info->serv;
+ /*
+ * Save the svc_serv in the transport so that it can
+ * be referenced when the session backchannel is initialized
+ */
+ xprt->bc_serv = serv;
}
#else
-static inline int nfs_minorversion_callback_svc_setup(u32 minorversion,
- struct svc_serv *serv, struct rpc_xprt *xprt,
- struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp))
+static int nfs41_callback_up_net(struct svc_serv *serv, struct net *net)
{
return 0;
}
+static void nfs_minorversion_callback_svc_setup(struct svc_serv *serv,
+ struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp))
+{
+ *rqstpp = ERR_PTR(-ENOTSUPP);
+ *callback_svc = ERR_PTR(-ENOTSUPP);
+}
+
static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt,
- struct nfs_callback_data *cb_info)
+ struct svc_serv *serv)
{
}
#endif /* CONFIG_NFS_V4_1 */
-/*
- * Bring up the callback thread if it is not already up.
- */
-int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
+static int nfs_callback_start_svc(int minorversion, struct rpc_xprt *xprt,
+ struct svc_serv *serv)
{
- struct svc_serv *serv = NULL;
struct svc_rqst *rqstp;
int (*callback_svc)(void *vrqstp);
struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion];
char svc_name[12];
- int ret = 0;
- int minorversion_setup;
- struct net *net = &init_net;
+ int ret;
- mutex_lock(&nfs_callback_mutex);
- if (cb_info->users++ || cb_info->task != NULL) {
- nfs_callback_bc_serv(minorversion, xprt, cb_info);
- goto out;
- }
- serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL);
- if (!serv) {
- ret = -ENOMEM;
- goto out_err;
- }
- /* As there is only one thread we need to over-ride the
- * default maximum of 80 connections
- */
- serv->sv_maxconn = 1024;
+ nfs_callback_bc_serv(minorversion, xprt, serv);
- ret = svc_bind(serv, net);
- if (ret < 0) {
- printk(KERN_WARNING "NFS: bind callback service failed\n");
- goto out_err;
- }
+ if (cb_info->task)
+ return 0;
- minorversion_setup = nfs_minorversion_callback_svc_setup(minorversion,
- serv, xprt, &rqstp, &callback_svc);
- if (!minorversion_setup) {
+ switch (minorversion) {
+ case 0:
/* v4.0 callback setup */
- rqstp = nfs4_callback_up(serv, xprt);
+ rqstp = nfs4_callback_up(serv);
callback_svc = nfs4_callback_svc;
+ break;
+ default:
+ nfs_minorversion_callback_svc_setup(serv,
+ &rqstp, &callback_svc);
}
- if (IS_ERR(rqstp)) {
- ret = PTR_ERR(rqstp);
- goto out_err;
- }
+ if (IS_ERR(rqstp))
+ return PTR_ERR(rqstp);
svc_sock_update_bufs(serv);
svc_exit_thread(cb_info->rqst);
cb_info->rqst = NULL;
cb_info->task = NULL;
- goto out_err;
+ return PTR_ERR(cb_info->task);
+ }
+ dprintk("nfs_callback_up: service started\n");
+ return 0;
+}
+
+static void nfs_callback_down_net(u32 minorversion, struct svc_serv *serv, struct net *net)
+{
+ struct nfs_net *nn = net_generic(net, nfs_net_id);
+
+ if (--nn->cb_users[minorversion])
+ return;
+
+ dprintk("NFS: destroy per-net callback data; net=%p\n", net);
+ svc_shutdown_net(serv, net);
+}
+
+static int nfs_callback_up_net(int minorversion, struct svc_serv *serv, struct net *net)
+{
+ struct nfs_net *nn = net_generic(net, nfs_net_id);
+ int ret;
+
+ if (nn->cb_users[minorversion]++)
+ return 0;
+
+ dprintk("NFS: create per-net callback data; net=%p\n", net);
+
+ ret = svc_bind(serv, net);
+ if (ret < 0) {
+ printk(KERN_WARNING "NFS: bind callback service failed\n");
+ goto err_bind;
+ }
+
+ switch (minorversion) {
+ case 0:
+ ret = nfs4_callback_up_net(serv, net);
+ break;
+ case 1:
+ ret = nfs41_callback_up_net(serv, net);
+ break;
+ default:
+ printk(KERN_ERR "NFS: unknown callback version: %d\n",
+ minorversion);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret < 0) {
+ printk(KERN_ERR "NFS: callback service start failed\n");
+ goto err_socks;
+ }
+ return 0;
+
+err_socks:
+ svc_rpcb_cleanup(serv, net);
+err_bind:
+ dprintk("NFS: Couldn't create callback socket: err = %d; "
+ "net = %p\n", ret, net);
+ return ret;
+}
+
+static struct svc_serv *nfs_callback_create_svc(int minorversion)
+{
+ struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion];
+ struct svc_serv *serv;
+
+ /*
+ * Check whether we're already up and running.
+ */
+ if (cb_info->task) {
+ /*
+ * Note: increase service usage, because later in case of error
+ * svc_destroy() will be called.
+ */
+ svc_get(cb_info->serv);
+ return cb_info->serv;
}
-out:
+
+ /*
+ * Sanity check: if there's no task,
+ * we should be the first user ...
+ */
+ if (cb_info->users)
+ printk(KERN_WARNING "nfs_callback_create_svc: no kthread, %d users??\n",
+ cb_info->users);
+
+ serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL);
+ if (!serv) {
+ printk(KERN_ERR "nfs_callback_create_svc: create service failed\n");
+ return ERR_PTR(-ENOMEM);
+ }
+ /* As there is only one thread we need to over-ride the
+ * default maximum of 80 connections
+ */
+ serv->sv_maxconn = 1024;
+ dprintk("nfs_callback_create_svc: service created\n");
+ return serv;
+}
+
+/*
+ * Bring up the callback thread if it is not already up.
+ */
+int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
+{
+ struct svc_serv *serv;
+ struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion];
+ int ret;
+ struct net *net = xprt->xprt_net;
+
+ mutex_lock(&nfs_callback_mutex);
+
+ serv = nfs_callback_create_svc(minorversion);
+ if (IS_ERR(serv)) {
+ ret = PTR_ERR(serv);
+ goto err_create;
+ }
+
+ ret = nfs_callback_up_net(minorversion, serv, net);
+ if (ret < 0)
+ goto err_net;
+
+ ret = nfs_callback_start_svc(minorversion, xprt, serv);
+ if (ret < 0)
+ goto err_start;
+
+ cb_info->users++;
/*
* svc_create creates the svc_serv with sv_nrthreads == 1, and then
* svc_prepare_thread increments that. So we need to call svc_destroy
* on both success and failure so that the refcount is 1 when the
* thread exits.
*/
- if (serv)
- svc_destroy(serv);
+err_net:
+ svc_destroy(serv);
+err_create:
mutex_unlock(&nfs_callback_mutex);
return ret;
-out_err:
- dprintk("NFS: Couldn't create callback socket or server thread; "
- "err = %d\n", ret);
- cb_info->users--;
- if (serv)
- svc_shutdown_net(serv, net);
- goto out;
+
+err_start:
+ nfs_callback_down_net(minorversion, serv, net);
+ dprintk("NFS: Couldn't create server thread; err = %d\n", ret);
+ goto err_net;
}
/*
* Kill the callback thread if it's no longer being used.
*/
-void nfs_callback_down(int minorversion)
+void nfs_callback_down(int minorversion, struct net *net)
{
struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion];
mutex_lock(&nfs_callback_mutex);
+ nfs_callback_down_net(minorversion, cb_info->serv, net);
cb_info->users--;
if (cb_info->users == 0 && cb_info->task != NULL) {
kthread_stop(cb_info->task);
- svc_shutdown_net(cb_info->serv, &init_net);
+ dprintk("nfs_callback_down: service stopped\n");
svc_exit_thread(cb_info->rqst);
+ dprintk("nfs_callback_down: service destroyed\n");
cb_info->serv = NULL;
cb_info->rqst = NULL;
cb_info->task = NULL;
spin_unlock(&nfsd_drc_lock);
}
- static struct nfsd4_session *alloc_session(int slotsize, int numslots)
+ static struct nfsd4_session *__alloc_session(int slotsize, int numslots)
{
struct nfsd4_session *new;
int mem, i;
return register_xpt_user(conn->cn_xprt, &conn->cn_xpt_user);
}
- static __be32 nfsd4_new_conn(struct svc_rqst *rqstp, struct nfsd4_session *ses, u32 dir)
+ static void nfsd4_init_conn(struct svc_rqst *rqstp, struct nfsd4_conn *conn, struct nfsd4_session *ses)
{
- struct nfsd4_conn *conn;
int ret;
- conn = alloc_conn(rqstp, dir);
- if (!conn)
- return nfserr_jukebox;
nfsd4_hash_conn(conn, ses);
ret = nfsd4_register_conn(conn);
if (ret)
/* oops; xprt is already down: */
nfsd4_conn_lost(&conn->cn_xpt_user);
- if (ses->se_client->cl_cb_state == NFSD4_CB_DOWN &&
- dir & NFS4_CDFC4_BACK) {
+ if (conn->cn_flags & NFS4_CDFC4_BACK) {
/* callback channel may be back up */
nfsd4_probe_callback(ses->se_client);
}
- return nfs_ok;
}
- static __be32 nfsd4_new_conn_from_crses(struct svc_rqst *rqstp, struct nfsd4_session *ses)
+ static struct nfsd4_conn *alloc_conn_from_crses(struct svc_rqst *rqstp, struct nfsd4_create_session *cses)
{
u32 dir = NFS4_CDFC4_FORE;
- if (ses->se_flags & SESSION4_BACK_CHAN)
+ if (cses->flags & SESSION4_BACK_CHAN)
dir |= NFS4_CDFC4_BACK;
-
- return nfsd4_new_conn(rqstp, ses, dir);
+ return alloc_conn(rqstp, dir);
}
/* must be called under client_lock */
spin_unlock(&clp->cl_lock);
}
+ static void __free_session(struct nfsd4_session *ses)
+ {
+ nfsd4_put_drc_mem(slot_bytes(&ses->se_fchannel), ses->se_fchannel.maxreqs);
+ free_session_slots(ses);
+ kfree(ses);
+ }
+
static void free_session(struct kref *kref)
{
struct nfsd4_session *ses;
- int mem;
lockdep_assert_held(&client_lock);
ses = container_of(kref, struct nfsd4_session, se_ref);
nfsd4_del_conns(ses);
- spin_lock(&nfsd_drc_lock);
- mem = ses->se_fchannel.maxreqs * slot_bytes(&ses->se_fchannel);
- nfsd_drc_mem_used -= mem;
- spin_unlock(&nfsd_drc_lock);
- free_session_slots(ses);
- kfree(ses);
+ __free_session(ses);
}
void nfsd4_put_session(struct nfsd4_session *ses)
spin_unlock(&client_lock);
}
- static struct nfsd4_session *alloc_init_session(struct svc_rqst *rqstp, struct nfs4_client *clp, struct nfsd4_create_session *cses)
+ static struct nfsd4_session *alloc_session(struct nfsd4_channel_attrs *fchan)
{
struct nfsd4_session *new;
- struct nfsd4_channel_attrs *fchan = &cses->fore_channel;
int numslots, slotsize;
- __be32 status;
- int idx;
-
/*
* Note decreasing slot size below client's request may
* make it difficult for client to function correctly, whereas
if (numslots < 1)
return NULL;
- new = alloc_session(slotsize, numslots);
+ new = __alloc_session(slotsize, numslots);
if (!new) {
nfsd4_put_drc_mem(slotsize, fchan->maxreqs);
return NULL;
}
init_forechannel_attrs(&new->se_fchannel, fchan, numslots, slotsize);
+ return new;
+ }
+
+ static struct nfsd4_session *init_session(struct svc_rqst *rqstp, struct nfsd4_session *new, struct nfs4_client *clp, struct nfsd4_create_session *cses)
+ {
+ int idx;
new->se_client = clp;
gen_sessionid(new);
spin_unlock(&clp->cl_lock);
spin_unlock(&client_lock);
- status = nfsd4_new_conn_from_crses(rqstp, new);
- /* whoops: benny points out, status is ignored! (err, or bogus) */
- if (status) {
- spin_lock(&client_lock);
- free_session(&new->se_ref);
- spin_unlock(&client_lock);
- return NULL;
- }
if (cses->flags & SESSION4_BACK_CHAN) {
struct sockaddr *sa = svc_addr(rqstp);
/*
rpc_copy_addr((struct sockaddr *)&clp->cl_cb_conn.cb_addr, sa);
clp->cl_cb_conn.cb_addrlen = svc_addr_len(sa);
}
- nfsd4_probe_callback(clp);
return new;
}
}
static void
- expire_client(struct nfs4_client *clp)
+ destroy_client(struct nfs4_client *clp)
{
struct nfs4_openowner *oo;
struct nfs4_delegation *dp;
spin_unlock(&client_lock);
}
+ static void expire_client(struct nfs4_client *clp)
+ {
+ nfsd4_client_record_remove(clp);
+ destroy_client(clp);
+ }
+
static void copy_verf(struct nfs4_client *target, nfs4_verifier *source)
{
memcpy(target->cl_verifier.data, source->data,
return true;
}
+ /*
+ * RFC 3530 language requires clid_inuse be returned when the
+ * "principal" associated with a requests differs from that previously
+ * used. We use uid, gid's, and gss principal string as our best
+ * approximation. We also don't want to allow non-gss use of a client
+ * established using gss: in theory cr_principal should catch that
+ * change, but in practice cr_principal can be null even in the gss case
+ * since gssd doesn't always pass down a principal string.
+ */
+ static bool is_gss_cred(struct svc_cred *cr)
+ {
+ /* Is cr_flavor one of the gss "pseudoflavors"?: */
+ return (cr->cr_flavor > RPC_AUTH_MAXFLAVOR);
+ }
+
+
static bool
same_creds(struct svc_cred *cr1, struct svc_cred *cr2)
{
- if ((cr1->cr_flavor != cr2->cr_flavor)
+ if ((is_gss_cred(cr1) != is_gss_cred(cr2))
|| (cr1->cr_uid != cr2->cr_uid)
|| (cr1->cr_gid != cr2->cr_gid)
|| !groups_equal(cr1->cr_group_info, cr2->cr_group_info))
}
static struct nfs4_client *
- find_confirmed_client(clientid_t *clid)
+ find_confirmed_client(clientid_t *clid, bool sessions)
{
struct nfs4_client *clp;
unsigned int idhashval = clientid_hashval(clid->cl_id);
list_for_each_entry(clp, &conf_id_hashtbl[idhashval], cl_idhash) {
if (same_clid(&clp->cl_clientid, clid)) {
+ if ((bool)clp->cl_minorversion != sessions)
+ return NULL;
renew_client(clp);
return clp;
}
}
static struct nfs4_client *
- find_unconfirmed_client(clientid_t *clid)
+ find_unconfirmed_client(clientid_t *clid, bool sessions)
{
struct nfs4_client *clp;
unsigned int idhashval = clientid_hashval(clid->cl_id);
list_for_each_entry(clp, &unconf_id_hashtbl[idhashval], cl_idhash) {
- if (same_clid(&clp->cl_clientid, clid))
+ if (same_clid(&clp->cl_clientid, clid)) {
+ if ((bool)clp->cl_minorversion != sessions)
+ return NULL;
return clp;
+ }
}
return NULL;
}
status = nfserr_jukebox;
goto out;
}
+ new->cl_minorversion = 1;
gen_clid(new);
add_to_unconfirmed(new, strhashval);
struct sockaddr *sa = svc_addr(rqstp);
struct nfs4_client *conf, *unconf;
struct nfsd4_session *new;
+ struct nfsd4_conn *conn;
struct nfsd4_clid_slot *cs_slot = NULL;
- bool confirm_me = false;
__be32 status = 0;
if (cr_ses->flags & ~SESSION4_FLAG_MASK_A)
return nfserr_inval;
+ if (check_forechannel_attrs(cr_ses->fore_channel))
+ return nfserr_toosmall;
+ new = alloc_session(&cr_ses->fore_channel);
+ if (!new)
+ return nfserr_jukebox;
+ status = nfserr_jukebox;
+ conn = alloc_conn_from_crses(rqstp, cr_ses);
+ if (!conn)
+ goto out_free_session;
nfs4_lock_state();
- unconf = find_unconfirmed_client(&cr_ses->clientid);
- conf = find_confirmed_client(&cr_ses->clientid);
+ unconf = find_unconfirmed_client(&cr_ses->clientid, true);
+ conf = find_confirmed_client(&cr_ses->clientid, true);
if (conf) {
cs_slot = &conf->cl_cs_slot;
status = check_slot_seqid(cr_ses->seqid, cs_slot->sl_seqid, 0);
if (status == nfserr_replay_cache) {
status = nfsd4_replay_create_session(cr_ses, cs_slot);
- goto out;
+ goto out_free_conn;
} else if (cr_ses->seqid != cs_slot->sl_seqid + 1) {
status = nfserr_seq_misordered;
- goto out;
+ goto out_free_conn;
}
} else if (unconf) {
+ unsigned int hash;
+ struct nfs4_client *old;
if (!same_creds(&unconf->cl_cred, &rqstp->rq_cred) ||
!rpc_cmp_addr(sa, (struct sockaddr *) &unconf->cl_addr)) {
status = nfserr_clid_inuse;
- goto out;
+ goto out_free_conn;
}
cs_slot = &unconf->cl_cs_slot;
status = check_slot_seqid(cr_ses->seqid, cs_slot->sl_seqid, 0);
if (status) {
/* an unconfirmed replay returns misordered */
status = nfserr_seq_misordered;
- goto out;
+ goto out_free_conn;
}
- confirm_me = true;
+ hash = clientstr_hashval(unconf->cl_recdir);
+ old = find_confirmed_client_by_str(unconf->cl_recdir, hash);
+ if (old)
+ expire_client(old);
+ move_to_confirmed(unconf);
conf = unconf;
} else {
status = nfserr_stale_clientid;
- goto out;
+ goto out_free_conn;
}
-
- /*
- * XXX: we should probably set this at creation time, and check
- * for consistent minorversion use throughout:
- */
- conf->cl_minorversion = 1;
+ status = nfs_ok;
/*
* We do not support RDMA or persistent sessions
*/
cr_ses->flags &= ~SESSION4_PERSIST;
cr_ses->flags &= ~SESSION4_RDMA;
- status = nfserr_toosmall;
- if (check_forechannel_attrs(cr_ses->fore_channel))
- goto out;
+ init_session(rqstp, new, conf, cr_ses);
+ nfsd4_init_conn(rqstp, conn, new);
- status = nfserr_jukebox;
- new = alloc_init_session(rqstp, conf, cr_ses);
- if (!new)
- goto out;
- status = nfs_ok;
memcpy(cr_ses->sessionid.data, new->se_sessionid.data,
NFS4_MAX_SESSIONID_LEN);
memcpy(&cr_ses->fore_channel, &new->se_fchannel,
/* cache solo and embedded create sessions under the state lock */
nfsd4_cache_create_session(cr_ses, cs_slot, status);
- if (confirm_me) {
- unsigned int hash = clientstr_hashval(unconf->cl_recdir);
- struct nfs4_client *old =
- find_confirmed_client_by_str(conf->cl_recdir, hash);
- if (old)
- expire_client(old);
- move_to_confirmed(conf);
- }
out:
nfs4_unlock_state();
dprintk("%s returns %d\n", __func__, ntohl(status));
return status;
+ out_free_conn:
+ free_conn(conn);
+ out_free_session:
+ __free_session(new);
+ goto out;
}
static bool nfsd4_last_compound_op(struct svc_rqst *rqstp)
struct nfsd4_bind_conn_to_session *bcts)
{
__be32 status;
+ struct nfsd4_conn *conn;
if (!nfsd4_last_compound_op(rqstp))
return nfserr_not_only_op;
return nfserr_badsession;
status = nfsd4_map_bcts_dir(&bcts->dir);
- if (!status)
- nfsd4_new_conn(rqstp, cstate->session, bcts->dir);
- return status;
+ if (status)
+ return status;
+ conn = alloc_conn(rqstp, bcts->dir);
+ if (!conn)
+ return nfserr_jukebox;
+ nfsd4_init_conn(rqstp, conn, cstate->session);
+ return nfs_ok;
}
static bool nfsd4_compound_in_session(struct nfsd4_session *session, struct nfs4_sessionid *sid)
__be32 status = 0;
nfs4_lock_state();
- unconf = find_unconfirmed_client(&dc->clientid);
- conf = find_confirmed_client(&dc->clientid);
+ unconf = find_unconfirmed_client(&dc->clientid, true);
+ conf = find_confirmed_client(&dc->clientid, true);
if (conf) {
clp = conf;
copy_clid(new, conf);
else /* case 4 (new client) or cases 2, 3 (client reboot): */
gen_clid(new);
- /*
- * XXX: we should probably set this at creation time, and check
- * for consistent minorversion use throughout:
- */
new->cl_minorversion = 0;
gen_callback(new, setclid, rqstp);
add_to_unconfirmed(new, strhashval);
return nfserr_stale_clientid;
nfs4_lock_state();
- conf = find_confirmed_client(clid);
- unconf = find_unconfirmed_client(clid);
+ conf = find_confirmed_client(clid, false);
+ unconf = find_unconfirmed_client(clid, false);
/*
* We try hard to give out unique clientid's, so if we get an
* attempt to confirm the same clientid with a different cred,
unsigned int hash = clientstr_hashval(unconf->cl_recdir);
conf = find_confirmed_client_by_str(unconf->cl_recdir, hash);
- if (conf) {
- nfsd4_client_record_remove(conf);
+ if (conf)
expire_client(conf);
- }
move_to_confirmed(unconf);
nfsd4_probe_callback(unconf);
}
}
static struct nfs4_openowner *
- find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open)
+ find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open, bool sessions)
{
struct nfs4_stateowner *so;
struct nfs4_openowner *oo;
+ struct nfs4_client *clp;
list_for_each_entry(so, &ownerstr_hashtbl[hashval], so_strhash) {
if (!so->so_is_open_owner)
continue;
if (same_owner_str(so, &open->op_owner, &open->op_clientid)) {
oo = openowner(so);
+ clp = oo->oo_owner.so_client;
+ if ((bool)clp->cl_minorversion != sessions)
+ return NULL;
renew_client(oo->oo_owner.so_client);
return oo;
}
return nfserr_jukebox;
strhashval = ownerstr_hashval(clientid->cl_id, &open->op_owner);
- oo = find_openstateowner_str(strhashval, open);
+ oo = find_openstateowner_str(strhashval, open, cstate->minorversion);
open->op_openowner = oo;
if (!oo) {
- clp = find_confirmed_client(clientid);
+ clp = find_confirmed_client(clientid, cstate->minorversion);
if (clp == NULL)
return nfserr_expired;
goto new_owner;
return nfs_ok;
}
- static void nfs4_free_stateid(struct nfs4_ol_stateid *s)
- {
- kmem_cache_free(stateid_slab, s);
- }
-
static inline int nfs4_access_to_access(u32 nfs4_access)
{
int flags = 0;
return -ENOMEM;
}
fp->fi_lease = fl;
- fp->fi_deleg_file = fl->fl_file;
- get_file(fp->fi_deleg_file);
+ fp->fi_deleg_file = get_file(fl->fl_file);
atomic_set(&fp->fi_delegees, 1);
list_add(&dp->dl_perfile, &fp->fi_delegations);
return 0;
if (open->op_file)
nfsd4_free_file(open->op_file);
if (open->op_stp)
- nfs4_free_stateid(open->op_stp);
+ free_generic_stateid(open->op_stp);
}
__be32
status = nfserr_stale_clientid;
if (STALE_CLIENTID(clid, nn))
goto out;
- clp = find_confirmed_client(clid);
+ clp = find_confirmed_client(clid, cstate->minorversion);
status = nfserr_expired;
if (clp == NULL) {
/* We assume the client took too long to RENEW. */
clp = list_entry(pos, struct nfs4_client, cl_lru);
dprintk("NFSD: purging unused client (clientid %08x)\n",
clp->cl_clientid.cl_id);
- nfsd4_client_record_remove(clp);
expire_client(clp);
}
spin_lock(&recall_lock);
return nfs_ok;
}
- static __be32 nfsd4_lookup_stateid(stateid_t *stateid, unsigned char typemask, struct nfs4_stid **s)
+ static __be32 nfsd4_lookup_stateid(stateid_t *stateid, unsigned char typemask, struct nfs4_stid **s, bool sessions)
{
struct nfs4_client *cl;
struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
return nfserr_bad_stateid;
if (STALE_STATEID(stateid, nn))
return nfserr_stale_stateid;
- cl = find_confirmed_client(&stateid->si_opaque.so_clid);
+ cl = find_confirmed_client(&stateid->si_opaque.so_clid, sessions);
if (!cl)
return nfserr_expired;
*s = find_stateid_by_type(cl, stateid, typemask);
if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
return check_special_stateids(net, current_fh, stateid, flags);
- status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID, &s);
+ status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID, &s, cstate->minorversion);
if (status)
return status;
status = check_stateid_generation(stateid, &s->sc_stateid, nfsd4_has_session(cstate));
seqid, STATEID_VAL(stateid));
*stpp = NULL;
- status = nfsd4_lookup_stateid(stateid, typemask, &s);
+ status = nfsd4_lookup_stateid(stateid, typemask, &s, cstate->minorversion);
if (status)
return status;
*stpp = openlockstateid(s);
memcpy(&close->cl_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
nfsd4_close_open_stateid(stp);
+ release_last_closed_stateid(oo);
oo->oo_last_closed_stid = stp;
if (list_empty(&oo->oo_owner.so_stateids)) {
inode = cstate->current_fh.fh_dentry->d_inode;
nfs4_lock_state();
- status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID, &s);
+ status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID, &s, cstate->minorversion);
if (status)
goto out;
dp = delegstateid(s);
struct nfs4_lockowner *lock_sop = NULL;
struct nfs4_ol_stateid *lock_stp;
struct file *filp = NULL;
- struct file_lock file_lock;
- struct file_lock conflock;
+ struct file_lock *file_lock = NULL;
+ struct file_lock *conflock = NULL;
__be32 status = 0;
bool new_state = false;
int lkflg;
if (!locks_in_grace(SVC_NET(rqstp)) && lock->lk_reclaim)
goto out;
- locks_init_lock(&file_lock);
+ file_lock = locks_alloc_lock();
+ if (!file_lock) {
+ dprintk("NFSD: %s: unable to allocate lock!\n", __func__);
+ status = nfserr_jukebox;
+ goto out;
+ }
+
+ locks_init_lock(file_lock);
switch (lock->lk_type) {
case NFS4_READ_LT:
case NFS4_READW_LT:
filp = find_readable_file(lock_stp->st_file);
if (filp)
get_lock_access(lock_stp, NFS4_SHARE_ACCESS_READ);
- file_lock.fl_type = F_RDLCK;
+ file_lock->fl_type = F_RDLCK;
break;
case NFS4_WRITE_LT:
case NFS4_WRITEW_LT:
filp = find_writeable_file(lock_stp->st_file);
if (filp)
get_lock_access(lock_stp, NFS4_SHARE_ACCESS_WRITE);
- file_lock.fl_type = F_WRLCK;
+ file_lock->fl_type = F_WRLCK;
break;
default:
status = nfserr_inval;
status = nfserr_openmode;
goto out;
}
- file_lock.fl_owner = (fl_owner_t)lock_sop;
- file_lock.fl_pid = current->tgid;
- file_lock.fl_file = filp;
- file_lock.fl_flags = FL_POSIX;
- file_lock.fl_lmops = &nfsd_posix_mng_ops;
-
- file_lock.fl_start = lock->lk_offset;
- file_lock.fl_end = last_byte_offset(lock->lk_offset, lock->lk_length);
- nfs4_transform_lock_offset(&file_lock);
-
- /*
- * Try to lock the file in the VFS.
- * Note: locks.c uses the BKL to protect the inode's lock list.
- */
+ file_lock->fl_owner = (fl_owner_t)lock_sop;
+ file_lock->fl_pid = current->tgid;
+ file_lock->fl_file = filp;
+ file_lock->fl_flags = FL_POSIX;
+ file_lock->fl_lmops = &nfsd_posix_mng_ops;
+ file_lock->fl_start = lock->lk_offset;
+ file_lock->fl_end = last_byte_offset(lock->lk_offset, lock->lk_length);
+ nfs4_transform_lock_offset(file_lock);
+
+ conflock = locks_alloc_lock();
+ if (!conflock) {
+ dprintk("NFSD: %s: unable to allocate lock!\n", __func__);
+ status = nfserr_jukebox;
+ goto out;
+ }
- err = vfs_lock_file(filp, F_SETLK, &file_lock, &conflock);
+ err = vfs_lock_file(filp, F_SETLK, file_lock, conflock);
switch (-err) {
case 0: /* success! */
update_stateid(&lock_stp->st_stid.sc_stateid);
case (EAGAIN): /* conflock holds conflicting lock */
status = nfserr_denied;
dprintk("NFSD: nfsd4_lock: conflicting lock found!\n");
- nfs4_set_lock_denied(&conflock, &lock->lk_denied);
+ nfs4_set_lock_denied(conflock, &lock->lk_denied);
break;
case (EDEADLK):
status = nfserr_deadlock;
release_lockowner(lock_sop);
if (!cstate->replay_owner)
nfs4_unlock_state();
+ if (file_lock)
+ locks_free_lock(file_lock);
+ if (conflock)
+ locks_free_lock(conflock);
return status;
}
struct nfsd4_lockt *lockt)
{
struct inode *inode;
- struct file_lock file_lock;
+ struct file_lock *file_lock = NULL;
struct nfs4_lockowner *lo;
__be32 status;
struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
goto out;
inode = cstate->current_fh.fh_dentry->d_inode;
- locks_init_lock(&file_lock);
+ file_lock = locks_alloc_lock();
+ if (!file_lock) {
+ dprintk("NFSD: %s: unable to allocate lock!\n", __func__);
+ status = nfserr_jukebox;
+ goto out;
+ }
+ locks_init_lock(file_lock);
switch (lockt->lt_type) {
case NFS4_READ_LT:
case NFS4_READW_LT:
- file_lock.fl_type = F_RDLCK;
+ file_lock->fl_type = F_RDLCK;
break;
case NFS4_WRITE_LT:
case NFS4_WRITEW_LT:
- file_lock.fl_type = F_WRLCK;
+ file_lock->fl_type = F_WRLCK;
break;
default:
dprintk("NFSD: nfs4_lockt: bad lock type!\n");
lo = find_lockowner_str(inode, &lockt->lt_clientid, &lockt->lt_owner);
if (lo)
- file_lock.fl_owner = (fl_owner_t)lo;
- file_lock.fl_pid = current->tgid;
- file_lock.fl_flags = FL_POSIX;
+ file_lock->fl_owner = (fl_owner_t)lo;
+ file_lock->fl_pid = current->tgid;
+ file_lock->fl_flags = FL_POSIX;
- file_lock.fl_start = lockt->lt_offset;
- file_lock.fl_end = last_byte_offset(lockt->lt_offset, lockt->lt_length);
+ file_lock->fl_start = lockt->lt_offset;
+ file_lock->fl_end = last_byte_offset(lockt->lt_offset, lockt->lt_length);
- nfs4_transform_lock_offset(&file_lock);
+ nfs4_transform_lock_offset(file_lock);
- status = nfsd_test_lock(rqstp, &cstate->current_fh, &file_lock);
+ status = nfsd_test_lock(rqstp, &cstate->current_fh, file_lock);
if (status)
goto out;
- if (file_lock.fl_type != F_UNLCK) {
+ if (file_lock->fl_type != F_UNLCK) {
status = nfserr_denied;
- nfs4_set_lock_denied(&file_lock, &lockt->lt_denied);
+ nfs4_set_lock_denied(file_lock, &lockt->lt_denied);
}
out:
nfs4_unlock_state();
+ if (file_lock)
+ locks_free_lock(file_lock);
return status;
}
{
struct nfs4_ol_stateid *stp;
struct file *filp = NULL;
- struct file_lock file_lock;
+ struct file_lock *file_lock = NULL;
__be32 status;
int err;
status = nfserr_lock_range;
goto out;
}
- BUG_ON(!filp);
- locks_init_lock(&file_lock);
- file_lock.fl_type = F_UNLCK;
- file_lock.fl_owner = (fl_owner_t)lockowner(stp->st_stateowner);
- file_lock.fl_pid = current->tgid;
- file_lock.fl_file = filp;
- file_lock.fl_flags = FL_POSIX;
- file_lock.fl_lmops = &nfsd_posix_mng_ops;
- file_lock.fl_start = locku->lu_offset;
-
- file_lock.fl_end = last_byte_offset(locku->lu_offset, locku->lu_length);
- nfs4_transform_lock_offset(&file_lock);
+ file_lock = locks_alloc_lock();
+ if (!file_lock) {
+ dprintk("NFSD: %s: unable to allocate lock!\n", __func__);
+ status = nfserr_jukebox;
+ goto out;
+ }
+ locks_init_lock(file_lock);
+ file_lock->fl_type = F_UNLCK;
+ file_lock->fl_owner = (fl_owner_t)lockowner(stp->st_stateowner);
+ file_lock->fl_pid = current->tgid;
+ file_lock->fl_file = filp;
+ file_lock->fl_flags = FL_POSIX;
+ file_lock->fl_lmops = &nfsd_posix_mng_ops;
+ file_lock->fl_start = locku->lu_offset;
+
+ file_lock->fl_end = last_byte_offset(locku->lu_offset,
+ locku->lu_length);
+ nfs4_transform_lock_offset(file_lock);
/*
* Try to unlock the file in the VFS.
*/
- err = vfs_lock_file(filp, F_SETLK, &file_lock, NULL);
+ err = vfs_lock_file(filp, F_SETLK, file_lock, NULL);
if (err) {
dprintk("NFSD: nfs4_locku: vfs_lock_file failed!\n");
goto out_nfserr;
out:
if (!cstate->replay_owner)
nfs4_unlock_state();
+ if (file_lock)
+ locks_free_lock(file_lock);
return status;
out_nfserr:
* Called from OPEN. Look for clientid in reclaim list.
*/
__be32
- nfs4_check_open_reclaim(clientid_t *clid)
+ nfs4_check_open_reclaim(clientid_t *clid, bool sessions)
{
struct nfs4_client *clp;
/* find clientid in conf_id_hashtbl */
- clp = find_confirmed_client(clid);
+ clp = find_confirmed_client(clid, sessions);
if (clp == NULL)
return nfserr_reclaim_bad;
nfs4_lock_state();
list_for_each_entry_safe(clp, next, &client_lru, cl_lru) {
- nfsd4_client_record_remove(clp);
expire_client(clp);
if (++count == num)
break;
printk(KERN_INFO "NFSD: Forgot %d open owners", count);
}
- int nfsd_process_n_delegations(u64 num, struct list_head *list)
+ static int nfsd_process_n_delegations(u64 num, struct list_head *list)
{
int i, count = 0;
struct nfs4_file *fp, *fnext;
for (i = 0; i < CLIENT_HASH_SIZE; i++) {
while (!list_empty(&conf_id_hashtbl[i])) {
clp = list_entry(conf_id_hashtbl[i].next, struct nfs4_client, cl_idhash);
- expire_client(clp);
+ destroy_client(clp);
}
while (!list_empty(&unconf_str_hashtbl[i])) {
clp = list_entry(unconf_str_hashtbl[i].next, struct nfs4_client, cl_strhash);
- expire_client(clp);
+ destroy_client(clp);
}
}
INIT_LIST_HEAD(&reaplist);