| /* 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; | |
| } |