| /* SPDX-License-Identifier: GPL-2.0 */ | |
| #include <linux/nfs4.h> | |
| #include <linux/nfs.h> | |
| #include <linux/sunrpc/sched.h> | |
| #include <linux/nfs_fs.h> | |
| #include "internal.h" | |
| #include "nfs4_fs.h" | |
| #include "nfs40.h" | |
| #include "nfs4session.h" | |
| #include "nfs4trace.h" | |
| static void nfs40_call_sync_prepare(struct rpc_task *task, void *calldata) | |
| { | |
| struct nfs4_call_sync_data *data = calldata; | |
| nfs4_setup_sequence(data->seq_server->nfs_client, | |
| data->seq_args, data->seq_res, task); | |
| } | |
| static void nfs40_call_sync_done(struct rpc_task *task, void *calldata) | |
| { | |
| struct nfs4_call_sync_data *data = calldata; | |
| nfs4_sequence_done(task, data->seq_res); | |
| } | |
| static void nfs40_sequence_free_slot(struct nfs4_sequence_res *res) | |
| { | |
| struct nfs4_slot *slot = res->sr_slot; | |
| struct nfs4_slot_table *tbl; | |
| tbl = slot->table; | |
| spin_lock(&tbl->slot_tbl_lock); | |
| if (!nfs41_wake_and_assign_slot(tbl, slot)) | |
| nfs4_free_slot(tbl, slot); | |
| spin_unlock(&tbl->slot_tbl_lock); | |
| res->sr_slot = NULL; | |
| } | |
| static int nfs40_sequence_done(struct rpc_task *task, | |
| struct nfs4_sequence_res *res) | |
| { | |
| if (res->sr_slot != NULL) | |
| nfs40_sequence_free_slot(res); | |
| return 1; | |
| } | |
| static void nfs40_clear_delegation_stateid(struct nfs4_state *state) | |
| { | |
| if (rcu_access_pointer(NFS_I(state->inode)->delegation) != NULL) | |
| nfs_finish_clear_delegation_stateid(state, NULL); | |
| } | |
| static int nfs40_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *state) | |
| { | |
| /* NFSv4.0 doesn't allow for delegation recovery on open expire */ | |
| nfs40_clear_delegation_stateid(state); | |
| nfs_state_clear_open_state_flags(state); | |
| return nfs4_open_expired(sp, state); | |
| } | |
| struct nfs4_renewdata { | |
| struct nfs_client *client; | |
| unsigned long timestamp; | |
| }; | |
| /* | |
| * nfs4_proc_async_renew(): This is not one of the nfs_rpc_ops; it is a special | |
| * standalone procedure for queueing an asynchronous RENEW. | |
| */ | |
| static void nfs4_renew_release(void *calldata) | |
| { | |
| struct nfs4_renewdata *data = calldata; | |
| struct nfs_client *clp = data->client; | |
| if (refcount_read(&clp->cl_count) > 1) | |
| nfs4_schedule_state_renewal(clp); | |
| nfs_put_client(clp); | |
| kfree(data); | |
| } | |
| static void nfs4_renew_done(struct rpc_task *task, void *calldata) | |
| { | |
| struct nfs4_renewdata *data = calldata; | |
| struct nfs_client *clp = data->client; | |
| unsigned long timestamp = data->timestamp; | |
| trace_nfs4_renew_async(clp, task->tk_status); | |
| switch (task->tk_status) { | |
| case 0: | |
| break; | |
| case -NFS4ERR_LEASE_MOVED: | |
| nfs4_schedule_lease_moved_recovery(clp); | |
| break; | |
| default: | |
| /* Unless we're shutting down, schedule state recovery! */ | |
| if (test_bit(NFS_CS_RENEWD, &clp->cl_res_state) == 0) | |
| return; | |
| if (task->tk_status != NFS4ERR_CB_PATH_DOWN) { | |
| nfs4_schedule_lease_recovery(clp); | |
| return; | |
| } | |
| nfs4_schedule_path_down_recovery(clp); | |
| } | |
| do_renew_lease(clp, timestamp); | |
| } | |
| static const struct rpc_call_ops nfs4_renew_ops = { | |
| .rpc_call_done = nfs4_renew_done, | |
| .rpc_release = nfs4_renew_release, | |
| }; | |
| static int nfs4_proc_async_renew(struct nfs_client *clp, const struct cred *cred, unsigned renew_flags) | |
| { | |
| struct rpc_message msg = { | |
| .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_RENEW], | |
| .rpc_argp = clp, | |
| .rpc_cred = cred, | |
| }; | |
| struct nfs4_renewdata *data; | |
| if (renew_flags == 0) | |
| return 0; | |
| if (!refcount_inc_not_zero(&clp->cl_count)) | |
| return -EIO; | |
| data = kmalloc_obj(*data, GFP_NOFS); | |
| if (data == NULL) { | |
| nfs_put_client(clp); | |
| return -ENOMEM; | |
| } | |
| data->client = clp; | |
| data->timestamp = jiffies; | |
| return rpc_call_async(clp->cl_rpcclient, &msg, RPC_TASK_TIMEOUT, | |
| &nfs4_renew_ops, data); | |
| } | |
| static int nfs4_proc_renew(struct nfs_client *clp, const struct cred *cred) | |
| { | |
| struct rpc_message msg = { | |
| .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_RENEW], | |
| .rpc_argp = clp, | |
| .rpc_cred = cred, | |
| }; | |
| unsigned long now = jiffies; | |
| int status; | |
| status = rpc_call_sync(clp->cl_rpcclient, &msg, RPC_TASK_TIMEOUT); | |
| if (status < 0) | |
| return status; | |
| do_renew_lease(clp, now); | |
| return 0; | |
| } | |
| static int nfs40_test_and_free_expired_stateid(struct nfs_server *server, | |
| nfs4_stateid *stateid, | |
| const struct cred *cred) | |
| { | |
| return -NFS4ERR_BAD_STATEID; | |
| } | |
| /* | |
| * This operation also signals the server that this client is | |
| * performing migration recovery. The server can stop returning | |
| * NFS4ERR_LEASE_MOVED to this client. A RENEW operation is | |
| * appended to this compound to identify the client ID which is | |
| * performing recovery. | |
| */ | |
| static int _nfs40_proc_get_locations(struct nfs_server *server, | |
| struct nfs_fh *fhandle, | |
| struct nfs4_fs_locations *locations, | |
| struct page *page, const struct cred *cred) | |
| { | |
| struct rpc_clnt *clnt = server->client; | |
| struct nfs_client *clp = server->nfs_client; | |
| u32 bitmask[2] = { | |
| [0] = FATTR4_WORD0_FSID | FATTR4_WORD0_FS_LOCATIONS, | |
| }; | |
| struct nfs4_fs_locations_arg args = { | |
| .clientid = clp->cl_clientid, | |
| .fh = fhandle, | |
| .page = page, | |
| .bitmask = bitmask, | |
| .migration = 1, /* skip LOOKUP */ | |
| .renew = 1, /* append RENEW */ | |
| }; | |
| struct nfs4_fs_locations_res res = { | |
| .fs_locations = locations, | |
| .migration = 1, | |
| .renew = 1, | |
| }; | |
| struct rpc_message msg = { | |
| .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_FS_LOCATIONS], | |
| .rpc_argp = &args, | |
| .rpc_resp = &res, | |
| .rpc_cred = cred, | |
| }; | |
| unsigned long now = jiffies; | |
| int status; | |
| nfs_fattr_init(locations->fattr); | |
| locations->server = server; | |
| locations->nlocations = 0; | |
| nfs4_init_sequence(clp, &args.seq_args, &res.seq_res, 0, 1); | |
| status = nfs4_call_sync_sequence(clnt, server, &msg, | |
| &args.seq_args, &res.seq_res); | |
| if (status) | |
| return status; | |
| renew_lease(server, now); | |
| return 0; | |
| } | |
| /* | |
| * This operation also signals the server that this client is | |
| * performing "lease moved" recovery. The server can stop | |
| * returning NFS4ERR_LEASE_MOVED to this client. A RENEW operation | |
| * is appended to this compound to identify the client ID which is | |
| * performing recovery. | |
| */ | |
| static int _nfs40_proc_fsid_present(struct inode *inode, const struct cred *cred) | |
| { | |
| struct nfs_server *server = NFS_SERVER(inode); | |
| struct nfs_client *clp = NFS_SERVER(inode)->nfs_client; | |
| struct rpc_clnt *clnt = server->client; | |
| struct nfs4_fsid_present_arg args = { | |
| .fh = NFS_FH(inode), | |
| .clientid = clp->cl_clientid, | |
| .renew = 1, /* append RENEW */ | |
| }; | |
| struct nfs4_fsid_present_res res = { | |
| .renew = 1, | |
| }; | |
| struct rpc_message msg = { | |
| .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_FSID_PRESENT], | |
| .rpc_argp = &args, | |
| .rpc_resp = &res, | |
| .rpc_cred = cred, | |
| }; | |
| unsigned long now = jiffies; | |
| int status; | |
| res.fh = nfs_alloc_fhandle(); | |
| if (res.fh == NULL) | |
| return -ENOMEM; | |
| nfs4_init_sequence(clp, &args.seq_args, &res.seq_res, 0, 1); | |
| status = nfs4_call_sync_sequence(clnt, server, &msg, | |
| &args.seq_args, &res.seq_res); | |
| nfs_free_fhandle(res.fh); | |
| if (status) | |
| return status; | |
| do_renew_lease(clp, now); | |
| return 0; | |
| } | |
| struct nfs_release_lockowner_data { | |
| struct nfs4_lock_state *lsp; | |
| struct nfs_server *server; | |
| struct nfs_release_lockowner_args args; | |
| struct nfs_release_lockowner_res res; | |
| unsigned long timestamp; | |
| }; | |
| static void nfs4_release_lockowner_prepare(struct rpc_task *task, void *calldata) | |
| { | |
| struct nfs_release_lockowner_data *data = calldata; | |
| struct nfs_server *server = data->server; | |
| nfs4_setup_sequence(server->nfs_client, &data->args.seq_args, | |
| &data->res.seq_res, task); | |
| data->args.lock_owner.clientid = server->nfs_client->cl_clientid; | |
| data->timestamp = jiffies; | |
| } | |
| static void nfs4_release_lockowner_done(struct rpc_task *task, void *calldata) | |
| { | |
| struct nfs_release_lockowner_data *data = calldata; | |
| struct nfs_server *server = data->server; | |
| nfs40_sequence_done(task, &data->res.seq_res); | |
| switch (task->tk_status) { | |
| case 0: | |
| renew_lease(server, data->timestamp); | |
| break; | |
| case -NFS4ERR_STALE_CLIENTID: | |
| case -NFS4ERR_EXPIRED: | |
| nfs4_schedule_lease_recovery(server->nfs_client); | |
| break; | |
| case -NFS4ERR_LEASE_MOVED: | |
| case -NFS4ERR_DELAY: | |
| if (nfs4_async_handle_error(task, server, | |
| NULL, NULL) == -EAGAIN) | |
| rpc_restart_call_prepare(task); | |
| } | |
| } | |
| static void nfs4_release_lockowner_release(void *calldata) | |
| { | |
| struct nfs_release_lockowner_data *data = calldata; | |
| nfs4_free_lock_state(data->server, data->lsp); | |
| kfree(calldata); | |
| } | |
| static const struct rpc_call_ops nfs4_release_lockowner_ops = { | |
| .rpc_call_prepare = nfs4_release_lockowner_prepare, | |
| .rpc_call_done = nfs4_release_lockowner_done, | |
| .rpc_release = nfs4_release_lockowner_release, | |
| }; | |
| static void | |
| nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_state *lsp) | |
| { | |
| struct nfs_release_lockowner_data *data; | |
| struct nfs_client *clp = server->nfs_client; | |
| struct rpc_message msg = { | |
| .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_RELEASE_LOCKOWNER], | |
| }; | |
| if (clp->cl_mvops->minor_version != 0) | |
| return; | |
| data = kmalloc_obj(*data); | |
| if (!data) | |
| return; | |
| data->lsp = lsp; | |
| data->server = server; | |
| data->args.lock_owner.clientid = clp->cl_clientid; | |
| data->args.lock_owner.id = lsp->ls_seqid.owner_id; | |
| data->args.lock_owner.s_dev = server->s_dev; | |
| msg.rpc_argp = &data->args; | |
| msg.rpc_resp = &data->res; | |
| nfs4_init_sequence(clp, &data->args.seq_args, &data->res.seq_res, 0, 0); | |
| rpc_call_async(server->client, &msg, 0, &nfs4_release_lockowner_ops, data); | |
| } | |
| static const struct rpc_call_ops nfs40_call_sync_ops = { | |
| .rpc_call_prepare = nfs40_call_sync_prepare, | |
| .rpc_call_done = nfs40_call_sync_done, | |
| }; | |
| static const struct nfs4_sequence_slot_ops nfs40_sequence_slot_ops = { | |
| .process = nfs40_sequence_done, | |
| .done = nfs40_sequence_done, | |
| .free_slot = nfs40_sequence_free_slot, | |
| }; | |
| static const struct nfs4_state_recovery_ops nfs40_reboot_recovery_ops = { | |
| .owner_flag_bit = NFS_OWNER_RECLAIM_REBOOT, | |
| .state_flag_bit = NFS_STATE_RECLAIM_REBOOT, | |
| .recover_open = nfs4_open_reclaim, | |
| .recover_lock = nfs4_lock_reclaim, | |
| .establish_clid = nfs4_init_clientid, | |
| .detect_trunking = nfs40_discover_server_trunking, | |
| }; | |
| static const struct nfs4_state_recovery_ops nfs40_nograce_recovery_ops = { | |
| .owner_flag_bit = NFS_OWNER_RECLAIM_NOGRACE, | |
| .state_flag_bit = NFS_STATE_RECLAIM_NOGRACE, | |
| .recover_open = nfs40_open_expired, | |
| .recover_lock = nfs4_lock_expired, | |
| .establish_clid = nfs4_init_clientid, | |
| }; | |
| static const struct nfs4_state_maintenance_ops nfs40_state_renewal_ops = { | |
| .sched_state_renewal = nfs4_proc_async_renew, | |
| .get_state_renewal_cred = nfs4_get_renew_cred, | |
| .renew_lease = nfs4_proc_renew, | |
| }; | |
| static const struct nfs4_mig_recovery_ops nfs40_mig_recovery_ops = { | |
| .get_locations = _nfs40_proc_get_locations, | |
| .fsid_present = _nfs40_proc_fsid_present, | |
| }; | |
| const struct nfs4_minor_version_ops nfs_v4_0_minor_ops = { | |
| .minor_version = 0, | |
| .init_caps = NFS_CAP_READDIRPLUS | |
| | NFS_CAP_ATOMIC_OPEN | |
| | NFS_CAP_POSIX_LOCK, | |
| .init_client = nfs40_init_client, | |
| .shutdown_client = nfs40_shutdown_client, | |
| .match_stateid = nfs4_match_stateid, | |
| .find_root_sec = nfs4_find_root_sec, | |
| .free_lock_state = nfs4_release_lockowner, | |
| .test_and_free_expired = nfs40_test_and_free_expired_stateid, | |
| .alloc_seqid = nfs_alloc_seqid, | |
| .call_sync_ops = &nfs40_call_sync_ops, | |
| .sequence_slot_ops = &nfs40_sequence_slot_ops, | |
| .reboot_recovery_ops = &nfs40_reboot_recovery_ops, | |
| .nograce_recovery_ops = &nfs40_nograce_recovery_ops, | |
| .state_renewal_ops = &nfs40_state_renewal_ops, | |
| .mig_recovery_ops = &nfs40_mig_recovery_ops, | |
| }; |