| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- /* SPDX-License-Identifier: GPL-2.0 */
- #include <linux/nfs_fs.h>
- #include "nfs4_fs.h"
- #include "nfs4session.h"
- #include "callback.h"
- #include "delegation.h"
- #include "internal.h"
- #include "netns.h"
- #include "nfs40.h"
- #define NFSDBG_FACILITY NFSDBG_CLIENT
- /*
- * SETCLIENTID just did a callback update with the callback ident in
- * "drop," but server trunking discovery claims "drop" and "keep" are
- * actually the same server. Swap the callback IDs so that "keep"
- * will continue to use the callback ident the server now knows about,
- * and so that "keep"'s original callback ident is destroyed when
- * "drop" is freed.
- */
- static void nfs4_swap_callback_idents(struct nfs_client *keep,
- struct nfs_client *drop)
- {
- struct nfs_net *nn = net_generic(keep->cl_net, nfs_net_id);
- unsigned int save = keep->cl_cb_ident;
- if (keep->cl_cb_ident == drop->cl_cb_ident)
- return;
- dprintk("%s: keeping callback ident %u and dropping ident %u\n",
- __func__, keep->cl_cb_ident, drop->cl_cb_ident);
- spin_lock(&nn->nfs_client_lock);
- idr_replace(&nn->cb_ident_idr, keep, drop->cl_cb_ident);
- keep->cl_cb_ident = drop->cl_cb_ident;
- idr_replace(&nn->cb_ident_idr, drop, save);
- drop->cl_cb_ident = save;
- spin_unlock(&nn->nfs_client_lock);
- }
- static bool nfs4_same_verifier(nfs4_verifier *v1, nfs4_verifier *v2)
- {
- return memcmp(v1->data, v2->data, sizeof(v1->data)) == 0;
- }
- void nfs40_shutdown_client(struct nfs_client *clp)
- {
- if (clp->cl_slot_tbl) {
- nfs4_shutdown_slot_table(clp->cl_slot_tbl);
- kfree(clp->cl_slot_tbl);
- }
- }
- /**
- * nfs40_init_client - nfs_client initialization tasks for NFSv4.0
- * @clp: nfs_client to initialize
- *
- * Returns zero on success, or a negative errno if some error occurred.
- */
- int nfs40_init_client(struct nfs_client *clp)
- {
- struct nfs4_slot_table *tbl;
- int ret;
- tbl = kzalloc_obj(*tbl, GFP_NOFS);
- if (tbl == NULL)
- return -ENOMEM;
- ret = nfs4_setup_slot_table(tbl, NFS4_MAX_SLOT_TABLE,
- "NFSv4.0 transport Slot table");
- if (ret) {
- nfs4_shutdown_slot_table(tbl);
- kfree(tbl);
- return ret;
- }
- clp->cl_slot_tbl = tbl;
- return 0;
- }
- /*
- * nfs40_handle_cb_pathdown - return all delegations after NFS4ERR_CB_PATH_DOWN
- * @clp: client to process
- *
- * Set the NFS4CLNT_LEASE_EXPIRED state in order to force a
- * resend of the SETCLIENTID and hence re-establish the
- * callback channel. Then return all existing delegations.
- */
- void nfs40_handle_cb_pathdown(struct nfs_client *clp)
- {
- set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
- nfs_expire_all_delegations(clp);
- dprintk("%s: handling CB_PATHDOWN recovery for server %s\n", __func__,
- clp->cl_hostname);
- }
- void nfs4_schedule_path_down_recovery(struct nfs_client *clp)
- {
- nfs40_handle_cb_pathdown(clp);
- nfs4_schedule_state_manager(clp);
- }
- /**
- * nfs40_walk_client_list - Find server that recognizes a client ID
- *
- * @new: nfs_client with client ID to test
- * @result: OUT: found nfs_client, or new
- * @cred: credential to use for trunking test
- *
- * Returns zero, a negative errno, or a negative NFS4ERR status.
- * If zero is returned, an nfs_client pointer is planted in "result."
- *
- * NB: nfs40_walk_client_list() relies on the new nfs_client being
- * the last nfs_client on the list.
- */
- static int nfs40_walk_client_list(struct nfs_client *new,
- struct nfs_client **result,
- const struct cred *cred)
- {
- struct nfs_net *nn = net_generic(new->cl_net, nfs_net_id);
- struct nfs_client *pos, *prev = NULL;
- struct nfs4_setclientid_res clid = {
- .clientid = new->cl_clientid,
- .confirm = new->cl_confirm,
- };
- int status = -NFS4ERR_STALE_CLIENTID;
- spin_lock(&nn->nfs_client_lock);
- list_for_each_entry(pos, &nn->nfs_client_list, cl_share_link) {
- if (pos == new)
- goto found;
- status = nfs4_match_client(pos, new, &prev, nn);
- if (status < 0)
- goto out_unlock;
- if (status != 0)
- continue;
- /*
- * We just sent a new SETCLIENTID, which should have
- * caused the server to return a new cl_confirm. So if
- * cl_confirm is the same, then this is a different
- * server that just returned the same cl_confirm by
- * coincidence:
- */
- if ((new != pos) && nfs4_same_verifier(&pos->cl_confirm,
- &new->cl_confirm))
- continue;
- /*
- * But if the cl_confirm's are different, then the only
- * way that a SETCLIENTID_CONFIRM to pos can succeed is
- * if new and pos point to the same server:
- */
- found:
- refcount_inc(&pos->cl_count);
- spin_unlock(&nn->nfs_client_lock);
- nfs_put_client(prev);
- prev = pos;
- status = nfs4_proc_setclientid_confirm(pos, &clid, cred);
- switch (status) {
- case -NFS4ERR_STALE_CLIENTID:
- break;
- case 0:
- nfs4_swap_callback_idents(pos, new);
- pos->cl_confirm = new->cl_confirm;
- nfs_mark_client_ready(pos, NFS_CS_READY);
- prev = NULL;
- *result = pos;
- goto out;
- case -ERESTARTSYS:
- case -ETIMEDOUT:
- /* The callback path may have been inadvertently
- * changed. Schedule recovery!
- */
- nfs4_schedule_path_down_recovery(pos);
- goto out;
- default:
- goto out;
- }
- spin_lock(&nn->nfs_client_lock);
- }
- out_unlock:
- spin_unlock(&nn->nfs_client_lock);
- /* No match found. The server lost our clientid */
- out:
- nfs_put_client(prev);
- return status;
- }
- /**
- * nfs40_discover_server_trunking - Detect server IP address trunking (mv0)
- *
- * @clp: nfs_client under test
- * @result: OUT: found nfs_client, or clp
- * @cred: credential to use for trunking test
- *
- * Returns zero, a negative errno, or a negative NFS4ERR status.
- * If zero is returned, an nfs_client pointer is planted in
- * "result".
- *
- * Note: The returned client may not yet be marked ready.
- */
- int nfs40_discover_server_trunking(struct nfs_client *clp,
- struct nfs_client **result,
- const struct cred *cred)
- {
- struct nfs4_setclientid_res clid = {
- .clientid = clp->cl_clientid,
- .confirm = clp->cl_confirm,
- };
- struct nfs_net *nn = net_generic(clp->cl_net, nfs_net_id);
- unsigned short port;
- int status;
- port = nn->nfs_callback_tcpport;
- if (clp->cl_addr.ss_family == AF_INET6)
- port = nn->nfs_callback_tcpport6;
- status = nfs4_proc_setclientid(clp, NFS4_CALLBACK, port, cred, &clid);
- if (status != 0)
- goto out;
- clp->cl_clientid = clid.clientid;
- clp->cl_confirm = clid.confirm;
- status = nfs40_walk_client_list(clp, result, cred);
- if (status == 0) {
- /* Sustain the lease, even if it's empty. If the clientid4
- * goes stale it's of no use for trunking discovery. */
- nfs4_schedule_state_renewal(*result);
- /* If the client state need to recover, do it. */
- if (clp->cl_state)
- nfs4_schedule_state_manager(clp);
- }
- out:
- return status;
- }
|