/* * fs/nfs/nfs4proc.c * * Client-side procedure declarations for NFSv4. * * Copyright (c) 2002 The Regents of the University of Michigan. * All rights reserved. * * Kendrick Smith <kmsmith@umich.edu> * Andy Adamson <andros@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.
*/
/* Prevent leaks of NFSv4 errors into userland */ staticint nfs4_map_errors(int err)
{ if (err >= -1000) return err; switch (err) { case -NFS4ERR_RESOURCE: case -NFS4ERR_LAYOUTTRYLATER: case -NFS4ERR_RECALLCONFLICT: case -NFS4ERR_RETURNCONFLICT: return -EREMOTEIO; case -NFS4ERR_WRONGSEC: case -NFS4ERR_WRONG_CRED: return -EPERM; case -NFS4ERR_BADOWNER: case -NFS4ERR_BADNAME: return -EINVAL; case -NFS4ERR_SHARE_DENIED: return -EACCES; case -NFS4ERR_MINOR_VERS_MISMATCH: return -EPROTONOSUPPORT; case -NFS4ERR_FILE_OPEN: return -EBUSY; case -NFS4ERR_NOT_SAME: return -ENOTSYNC; case -ENETDOWN: case -ENETUNREACH: break; default:
dprintk("%s could not handle NFSv4 error %d\n",
__func__, -err); break;
} return -EIO;
}
/* Remove the attributes over which we have full control */
dst[1] &= ~FATTR4_WORD1_RAWDEV; if (!(cache_validity & NFS_INO_INVALID_SIZE))
dst[0] &= ~FATTR4_WORD0_SIZE;
if (!(cache_validity & NFS_INO_INVALID_CHANGE))
dst[0] &= ~FATTR4_WORD0_CHANGE;
if (!(cache_validity & NFS_INO_INVALID_MODE))
dst[1] &= ~FATTR4_WORD1_MODE; if (!(cache_validity & NFS_INO_INVALID_OTHER))
dst[1] &= ~(FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP);
if (!(cache_validity & NFS_INO_INVALID_BTIME))
dst[1] &= ~FATTR4_WORD1_TIME_CREATE;
if (nfs_have_delegated_mtime(inode)) { if (!(cache_validity & NFS_INO_INVALID_ATIME))
dst[1] &= ~(FATTR4_WORD1_TIME_ACCESS|FATTR4_WORD1_TIME_ACCESS_SET); if (!(cache_validity & NFS_INO_INVALID_MTIME))
dst[1] &= ~(FATTR4_WORD1_TIME_MODIFY|FATTR4_WORD1_TIME_MODIFY_SET); if (!(cache_validity & NFS_INO_INVALID_CTIME))
dst[1] &= ~(FATTR4_WORD1_TIME_METADATA|FATTR4_WORD1_TIME_MODIFY_SET);
} elseif (nfs_have_delegated_atime(inode)) { if (!(cache_validity & NFS_INO_INVALID_ATIME))
dst[1] &= ~(FATTR4_WORD1_TIME_ACCESS|FATTR4_WORD1_TIME_ACCESS_SET);
}
}
/* * NFSv4 servers do not return entries for '.' and '..' * Therefore, we fake these entries here. We let '.' * have cookie 0 and '..' have cookie 1. Note that * when talking to the server, we always send cookie 0 * instead of 1 or 2.
*/
start = p = kmap_atomic(*readdir->pages);
if (cookie == 0) {
*p++ = xdr_one; /* next */
*p++ = xdr_zero; /* cookie, first word */
*p++ = xdr_one; /* cookie, second word */
*p++ = xdr_one; /* entry len */
memcpy(p, ".\0\0\0", 4); /* entry */
p++;
*p++ = xdr_one; /* bitmap length */
*p++ = htonl(attrs); /* bitmap */
*p++ = htonl(12); /* attribute buffer length */
*p++ = htonl(NF4DIR);
p = xdr_encode_hyper(p, NFS_FILEID(d_inode(dentry)));
}
*p++ = xdr_one; /* next */
*p++ = xdr_zero; /* cookie, first word */
*p++ = xdr_two; /* cookie, second word */
*p++ = xdr_two; /* entry len */
memcpy(p, "..\0\0", 4); /* entry */
p++;
*p++ = xdr_one; /* bitmap length */
*p++ = htonl(attrs); /* bitmap */
*p++ = htonl(12); /* attribute buffer length */
*p++ = htonl(NF4DIR);
spin_lock(&dentry->d_lock);
p = xdr_encode_hyper(p, NFS_FILEID(d_inode(dentry->d_parent)));
spin_unlock(&dentry->d_lock);
staticconst nfs4_stateid *
nfs4_recoverable_stateid(const nfs4_stateid *stateid)
{ if (!stateid) return NULL; switch (stateid->type) { case NFS4_OPEN_STATEID_TYPE: case NFS4_LOCK_STATEID_TYPE: case NFS4_DELEGATION_STATEID_TYPE: return stateid; default: break;
} return NULL;
}
/* This is the error handling routine for processes that are allowed * to sleep.
*/ staticint nfs4_do_handle_exception(struct nfs_server *server, int errorcode, struct nfs4_exception *exception)
{ struct nfs_client *clp = server->nfs_client; struct nfs4_state *state = exception->state; const nfs4_stateid *stateid; struct inode *inode = exception->inode; int ret = errorcode;
stateid = nfs4_recoverable_stateid(exception->stateid); if (stateid == NULL && state != NULL)
stateid = nfs4_recoverable_stateid(&state->stateid);
switch(errorcode) { case 0: return 0; case -NFS4ERR_BADHANDLE: case -ESTALE: if (inode != NULL && S_ISREG(inode->i_mode))
pnfs_destroy_layout(NFS_I(inode)); break; case -NFS4ERR_DELEG_REVOKED: case -NFS4ERR_ADMIN_REVOKED: case -NFS4ERR_EXPIRED: case -NFS4ERR_BAD_STATEID: case -NFS4ERR_PARTNER_NO_AUTH: if (inode != NULL && stateid != NULL) {
nfs_inode_find_state_and_recover(inode,
stateid); goto wait_on_recovery;
}
fallthrough; case -NFS4ERR_OPENMODE: if (inode) { int err;
err = nfs_async_inode_return_delegation(inode,
stateid); if (err == 0) goto wait_on_recovery; if (stateid != NULL && stateid->type == NFS4_DELEGATION_STATEID_TYPE) {
exception->retry = 1; break;
}
} if (state == NULL) break;
ret = nfs4_schedule_stateid_recovery(server, state); if (ret < 0) break; goto wait_on_recovery; case -NFS4ERR_STALE_STATEID: case -NFS4ERR_STALE_CLIENTID:
nfs4_schedule_lease_recovery(clp); goto wait_on_recovery; case -NFS4ERR_MOVED:
ret = nfs4_schedule_migration_recovery(server); if (ret < 0) break; goto wait_on_recovery; case -NFS4ERR_LEASE_MOVED:
nfs4_schedule_lease_moved_recovery(clp); goto wait_on_recovery; #ifdefined(CONFIG_NFS_V4_1) case -NFS4ERR_BADSESSION: case -NFS4ERR_BADSLOT: case -NFS4ERR_BAD_HIGH_SLOT: case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION: case -NFS4ERR_DEADSESSION: case -NFS4ERR_SEQ_FALSE_RETRY: case -NFS4ERR_SEQ_MISORDERED: /* Handled in nfs41_sequence_process() */ goto wait_on_recovery; #endif/* defined(CONFIG_NFS_V4_1) */ case -NFS4ERR_FILE_OPEN: if (exception->timeout > HZ) { /* We have retried a decent amount, time to * fail
*/
ret = -EBUSY; break;
}
fallthrough; case -NFS4ERR_DELAY:
nfs_inc_server_stats(server, NFSIOS_DELAY);
fallthrough; case -NFS4ERR_GRACE: case -NFS4ERR_LAYOUTTRYLATER: case -NFS4ERR_RECALLCONFLICT: case -NFS4ERR_RETURNCONFLICT:
exception->delay = 1; return 0;
case -NFS4ERR_RETRY_UNCACHED_REP: case -NFS4ERR_OLD_STATEID:
exception->retry = 1; break; case -NFS4ERR_BADOWNER: /* The following works around a Linux server bug! */ case -NFS4ERR_BADNAME: if (server->caps & NFS_CAP_UIDGID_NOMAP) {
server->caps &= ~NFS_CAP_UIDGID_NOMAP;
exception->retry = 1;
printk(KERN_WARNING "NFS: v4 server %s " "does not accept raw " "uid/gids. " "Reenabling the idmapper.\n",
server->nfs_client->cl_hostname);
}
} /* We failed to handle the error */ return nfs4_map_errors(ret);
wait_on_recovery:
exception->recovering = 1; return 0;
}
/* * Track the number of NFS4ERR_DELAY related retransmissions and return * EAGAIN if the 'softerr' mount option is set, and we've exceeded the limit * set by 'nfs_delay_retrans'.
*/ staticint nfs4_exception_should_retrans(conststruct nfs_server *server, struct nfs4_exception *exception)
{ if (server->flags & NFS_MOUNT_SOFTERR && nfs_delay_retrans >= 0) { if (exception->retrans++ >= (unsignedshort)nfs_delay_retrans) return -EAGAIN;
} return 0;
}
/* This is the error handling routine for processes that are allowed * to sleep.
*/ int nfs4_handle_exception(struct nfs_server *server, int errorcode, struct nfs4_exception *exception)
{ struct nfs_client *clp = server->nfs_client; int ret;
ret = nfs4_do_handle_exception(server, errorcode, exception); if (exception->delay) { int ret2 = nfs4_exception_should_retrans(server, exception); if (ret2 < 0) {
exception->retry = 0; return ret2;
}
ret = nfs4_delay(&exception->timeout,
exception->interruptible); goto out_retry;
} if (exception->recovering) { if (exception->task_is_privileged) return -EDEADLOCK;
ret = nfs4_wait_clnt_recover(clp); if (test_bit(NFS_MIG_FAILED, &server->mig_status)) return -EIO; goto out_retry;
} return ret;
out_retry: if (ret == 0)
exception->retry = 1; return ret;
}
staticint
nfs4_async_handle_exception(struct rpc_task *task, struct nfs_server *server, int errorcode, struct nfs4_exception *exception)
{ struct nfs_client *clp = server->nfs_client; int ret;
ret = nfs4_do_handle_exception(server, errorcode, exception); if (exception->delay) { int ret2 = nfs4_exception_should_retrans(server, exception); if (ret2 < 0) {
exception->retry = 0; return ret2;
}
rpc_delay(task, nfs4_update_delay(&exception->timeout)); goto out_retry;
} if (exception->recovering) { if (exception->task_is_privileged) return -EDEADLOCK;
rpc_sleep_on(&clp->cl_rpcwaitq, task, NULL); if (test_bit(NFS4CLNT_MANAGER_RUNNING, &clp->cl_state) == 0)
rpc_wake_up_queued_task(&clp->cl_rpcwaitq, task); goto out_retry;
} if (test_bit(NFS_MIG_FAILED, &server->mig_status))
ret = -EIO; return ret;
out_retry: if (ret == 0) {
exception->retry = 1; /* * For NFS4ERR_MOVED, the client transport will need to * be recomputed after migration recovery has completed.
*/ if (errorcode == -NFS4ERR_MOVED)
rpc_task_release_transport(task);
} return ret;
}
if (!slot) return;
tbl = slot->table;
session = tbl->session;
/* Bump the slot sequence number */ if (slot->seq_done)
slot->seq_nr++;
slot->seq_done = 0;
spin_lock(&tbl->slot_tbl_lock); /* Be nice to the server: try to ensure that the last transmitted * value for highest_user_slotid <= target_highest_slotid
*/ if (tbl->highest_used_slotid > tbl->target_highest_slotid)
send_new_highest_used_slotid = true;
staticint nfs41_sequence_process(struct rpc_task *task, struct nfs4_sequence_res *res)
{ struct nfs4_session *session; struct nfs4_slot *slot = res->sr_slot; struct nfs_client *clp; int status; int ret = 1;
if (slot == NULL) goto out_noaction; /* don't increment the sequence number if the task wasn't sent */ if (!RPC_WAS_SENT(task) || slot->seq_done) goto out;
status = res->sr_status; if (task->tk_status == -NFS4ERR_DEADSESSION)
status = -NFS4ERR_DEADSESSION;
/* Check the SEQUENCE operation status */ switch (status) { case 0: /* Mark this sequence number as having been acked */
nfs4_slot_sequence_acked(slot, slot->seq_nr); /* Update the slot's sequence and clientid lease timer */
slot->seq_done = 1;
do_renew_lease(clp, res->sr_timestamp); /* Check sequence flags */
nfs41_handle_sequence_flag_errors(clp, res->sr_status_flags,
!!slot->privileged);
nfs41_update_target_slotid(slot->table, slot, res); break; case 1: /* * sr_status remains 1 if an RPC level error occurred. * The server may or may not have processed the sequence * operation..
*/
nfs4_slot_sequence_record_sent(slot, slot->seq_nr);
slot->seq_done = 1; goto out; case -NFS4ERR_DELAY: /* The server detected a resend of the RPC call and * returned NFS4ERR_DELAY as per Section 2.10.6.2 * of RFC5661.
*/
dprintk("%s: slot=%u seq=%u: Operation in progress\n",
__func__,
slot->slot_nr,
slot->seq_nr); goto out_retry; case -NFS4ERR_RETRY_UNCACHED_REP: case -NFS4ERR_SEQ_FALSE_RETRY: /* * The server thinks we tried to replay a request. * Retry the call after bumping the sequence ID.
*/
nfs4_slot_sequence_acked(slot, slot->seq_nr); goto retry_new_seq; case -NFS4ERR_BADSLOT: /* * The slot id we used was probably retired. Try again * using a different slot id.
*/ if (slot->slot_nr < slot->table->target_highest_slotid) goto session_recover; goto retry_nowait; case -NFS4ERR_SEQ_MISORDERED:
nfs4_slot_sequence_record_sent(slot, slot->seq_nr); /* * Were one or more calls using this slot interrupted? * If the server never received the request, then our * transmitted slot sequence number may be too high. However, * if the server did receive the request then it might * accidentally give us a reply with a mismatched operation. * We can sort this out by sending a lone sequence operation * to the server on the same slot.
*/ if ((s32)(slot->seq_nr - slot->seq_nr_last_acked) > 1) {
slot->seq_nr--; if (task->tk_msg.rpc_proc != &nfs4_procedures[NFSPROC4_CLNT_SEQUENCE]) {
nfs4_probe_sequence(clp, task->tk_msg.rpc_cred, slot);
res->sr_slot = NULL;
} goto retry_nowait;
} /* * RFC5661: * A retry might be sent while the original request is * still in progress on the replier. The replier SHOULD * deal with the issue by returning NFS4ERR_DELAY as the * reply to SEQUENCE or CB_SEQUENCE operation, but * implementations MAY return NFS4ERR_SEQ_MISORDERED. * * Restart the search after a delay.
*/
slot->seq_nr = slot->seq_nr_highest_sent; goto out_retry; case -NFS4ERR_BADSESSION: case -NFS4ERR_DEADSESSION: case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION: goto session_recover; default: /* Just update the slot sequence no. */
slot->seq_done = 1;
}
out: /* The session may be reset by one of the error handlers. */
dprintk("%s: Error %d free the slot \n", __func__, res->sr_status);
out_noaction: return ret;
session_recover:
set_bit(NFS4_SLOT_TBL_DRAINING, &session->fc_slot_table.slot_tbl_state);
nfs4_schedule_session_recovery(session, status);
dprintk("%s ERROR: %d Reset session\n", __func__, status);
nfs41_sequence_free_slot(res); goto out;
retry_new_seq:
++slot->seq_nr;
retry_nowait: if (rpc_restart_call_prepare(task)) {
nfs41_sequence_free_slot(res);
task->tk_status = 0;
ret = 0;
} goto out;
out_retry: if (!rpc_restart_call(task)) goto out;
rpc_delay(task, NFS4_POLL_RETRY_MAX); return 0;
}
int nfs41_sequence_done(struct rpc_task *task, struct nfs4_sequence_res *res)
{ if (!nfs41_sequence_process(task, res)) return 0; if (res->sr_slot != NULL)
nfs41_sequence_free_slot(res); return 1;
spin_lock(&tbl->slot_tbl_lock); /* The state manager will wait until the slot table is empty */ if (nfs4_slot_tbl_draining(tbl) && !args->sa_privileged) goto out_sleep;
slot = nfs4_alloc_slot(tbl); if (IS_ERR(slot)) { if (slot == ERR_PTR(-ENOMEM)) goto out_sleep_timeout; goto out_sleep;
}
spin_unlock(&tbl->slot_tbl_lock);
static u32
nfs4_fmode_to_share_access(fmode_t fmode)
{
u32 res = 0;
switch (fmode & (FMODE_READ | FMODE_WRITE)) { case FMODE_READ:
res = NFS4_SHARE_ACCESS_READ; break; case FMODE_WRITE:
res = NFS4_SHARE_ACCESS_WRITE; break; case FMODE_READ|FMODE_WRITE:
res = NFS4_SHARE_ACCESS_BOTH;
} return res;
}
static u32
nfs4_map_atomic_open_share(struct nfs_server *server,
fmode_t fmode, int openflags)
{
u32 res = nfs4_fmode_to_share_access(fmode);
if (!(server->caps & NFS_CAP_ATOMIC_OPEN_V1)) goto out; /* Want no delegation if we're using O_DIRECT */ if (openflags & O_DIRECT) {
res |= NFS4_SHARE_WANT_NO_DELEG; goto out;
} /* res |= NFS4_SHARE_WANT_NO_PREFERENCE; */ if (server->caps & NFS_CAP_DELEGTIME)
res |= NFS4_SHARE_WANT_DELEG_TIMESTAMPS; if (server->caps & NFS_CAP_OPEN_XOR)
res |= NFS4_SHARE_WANT_OPEN_XOR_DELEGATION;
out: return res;
}
staticenum open_claim_type4
nfs4_map_atomic_open_claim(struct nfs_server *server, enum open_claim_type4 claim)
{ if (server->caps & NFS_CAP_ATOMIC_OPEN_V1) return claim; switch (claim) { default: return claim; case NFS4_OPEN_CLAIM_FH: return NFS4_OPEN_CLAIM_NULL; case NFS4_OPEN_CLAIM_DELEG_CUR_FH: return NFS4_OPEN_CLAIM_DELEGATE_CUR; case NFS4_OPEN_CLAIM_DELEG_PREV_FH: return NFS4_OPEN_CLAIM_DELEGATE_PREV;
}
}
if (test_and_clear_bit(NFS_O_RDONLY_STATE, &state->flags) && state->n_rdonly)
need_recover = true; if (test_and_clear_bit(NFS_O_WRONLY_STATE, &state->flags) && state->n_wronly)
need_recover = true; if (test_and_clear_bit(NFS_O_RDWR_STATE, &state->flags) && state->n_rdwr)
need_recover = true; if (need_recover)
nfs4_state_mark_reclaim_nograce(clp, state);
}
/* * Check for whether or not the caller may update the open stateid * to the value passed in by stateid. * * Note: This function relies heavily on the server implementing * RFC7530 Section 9.1.4.2, and RFC5661 Section 8.2.2 * correctly. * i.e. The stateid seqids have to be initialised to 1, and * are then incremented on every state transition.
*/ staticbool nfs_stateid_is_sequential(struct nfs4_state *state, const nfs4_stateid *stateid)
{ if (test_bit(NFS_OPEN_STATE, &state->flags)) { /* The common case - we're updating to a new sequence number */ if (nfs4_stateid_match_other(stateid, &state->open_stateid)) { if (nfs4_stateid_is_next(&state->open_stateid, stateid)) returntrue; returnfalse;
} /* The server returned a new stateid */
} /* This is the first OPEN in this generation */ if (stateid->seqid == cpu_to_be32(1)) returntrue; returnfalse;
}
staticvoid nfs_resync_open_stateid_locked(struct nfs4_state *state)
{ if (!(state->n_wronly || state->n_rdonly || state->n_rdwr)) return; if (state->n_wronly)
set_bit(NFS_O_WRONLY_STATE, &state->flags); if (state->n_rdonly)
set_bit(NFS_O_RDONLY_STATE, &state->flags); if (state->n_rdwr)
set_bit(NFS_O_RDWR_STATE, &state->flags);
set_bit(NFS_OPEN_STATE, &state->flags);
}
staticvoid nfs_clear_open_stateid(struct nfs4_state *state,
nfs4_stateid *arg_stateid,
nfs4_stateid *stateid, fmode_t fmode)
{
write_seqlock(&state->seqlock); /* Ignore, if the CLOSE argment doesn't match the current stateid */ if (nfs4_state_match_open_stateid_other(state, arg_stateid))
nfs_clear_open_stateid_locked(state, stateid, fmode);
write_sequnlock(&state->seqlock); if (test_bit(NFS_STATE_RECLAIM_NOGRACE, &state->flags))
nfs4_schedule_state_manager(state->owner->so_server->nfs_client);
}
if (nfs_stateid_is_sequential(state, stateid)) break;
if (status) break; /* Rely on seqids for serialisation with NFSv4.0 */ if (!nfs4_has_session(NFS_SERVER(state->inode)->nfs_client)) break;
set_bit(NFS_STATE_CHANGE_WAIT, &state->flags);
prepare_to_wait(&state->waitq, &wait, TASK_KILLABLE); /* * Ensure we process the state changes in the same order * in which the server processed them by delaying the * update of the stateid until we are in sequence.
*/
write_sequnlock(&state->seqlock);
spin_unlock(&state->owner->so_lock);
rcu_read_unlock();
trace_nfs4_open_stateid_update_wait(state->inode, stateid, 0);
if (!fatal_signal_pending(current) &&
!nfs_current_task_exiting()) { if (schedule_timeout(5*HZ) == 0)
status = -EAGAIN; else
status = 0;
} else
status = -EINTR;
finish_wait(&state->waitq, &wait);
rcu_read_lock();
spin_lock(&state->owner->so_lock);
write_seqlock(&state->seqlock);
}
if (data->o_arg.claim == NFS4_OPEN_CLAIM_PREVIOUS)
ret =_nfs4_opendata_reclaim_to_nfs4_state(data); else
ret = _nfs4_opendata_to_nfs4_state(data);
nfs4_sequence_free_slot(&data->o_res.seq_res); return ret;
}
if (!nfs4_mode_match_open_stateid(opendata->state, fmode)) return 0;
opendata->o_arg.fmode = fmode;
opendata->o_arg.share_access =
nfs4_map_atomic_open_share(server, fmode, openflags);
memset(&opendata->o_res, 0, sizeof(opendata->o_res));
memset(&opendata->c_res, 0, sizeof(opendata->c_res));
nfs4_init_opendata_res(opendata);
ret = _nfs4_recover_proc_open(opendata); if (ret != 0) return ret;
newstate = nfs4_opendata_to_nfs4_state(opendata); if (IS_ERR(newstate)) return PTR_ERR(newstate); if (newstate != opendata->state)
ret = -ESTALE;
nfs4_close_state(newstate, fmode); return ret;
}
staticint nfs4_open_recover(struct nfs4_opendata *opendata, struct nfs4_state *state)
{ int ret;
/* memory barrier prior to reading state->n_* */
smp_rmb();
ret = nfs4_open_recover_helper(opendata, FMODE_READ|FMODE_WRITE); if (ret != 0) return ret;
ret = nfs4_open_recover_helper(opendata, FMODE_WRITE); if (ret != 0) return ret;
ret = nfs4_open_recover_helper(opendata, FMODE_READ); if (ret != 0) return ret; /* * We may have performed cached opens for all three recoveries. * Check if we need to update the current stateid.
*/ if (test_bit(NFS_DELEGATED_STATE, &state->flags) == 0 &&
!nfs4_stateid_match(&state->stateid, &state->open_stateid)) {
write_seqlock(&state->seqlock); if (test_bit(NFS_DELEGATED_STATE, &state->flags) == 0)
nfs4_stateid_copy(&state->stateid, &state->open_stateid);
write_sequnlock(&state->seqlock);
} return 0;
}
/* * OPEN_RECLAIM: * reclaim state on the server after a reboot.
*/ staticint _nfs4_do_open_reclaim(struct nfs_open_context *ctx, struct nfs4_state *state)
{ struct nfs_delegation *delegation; struct nfs4_opendata *opendata;
u32 delegation_type = NFS4_OPEN_DELEGATE_NONE; int status;
opendata = nfs4_open_recoverdata_alloc(ctx, state,
NFS4_OPEN_CLAIM_PREVIOUS); if (IS_ERR(opendata)) return PTR_ERR(opendata);
rcu_read_lock();
delegation = rcu_dereference(NFS_I(state->inode)->delegation); if (delegation != NULL && test_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags) != 0) { switch(delegation->type) { case FMODE_READ:
delegation_type = NFS4_OPEN_DELEGATE_READ; if (test_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags))
delegation_type = NFS4_OPEN_DELEGATE_READ_ATTRS_DELEG; break; case FMODE_WRITE: case FMODE_READ|FMODE_WRITE:
delegation_type = NFS4_OPEN_DELEGATE_WRITE; if (test_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags))
delegation_type = NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG;
}
}
rcu_read_unlock();
opendata->o_arg.u.delegation_type = delegation_type;
status = nfs4_open_recover(opendata, state);
nfs4_opendata_put(opendata); return status;
}
staticint nfs4_do_open_reclaim(struct nfs_open_context *ctx, struct nfs4_state *state)
{ struct nfs_server *server = NFS_SERVER(state->inode); struct nfs4_exception exception = { }; int err; do {
err = _nfs4_do_open_reclaim(ctx, state);
trace_nfs4_open_reclaim(ctx, 0, err); if (nfs4_clear_cap_atomic_open_v1(server, err, &exception)) continue; if (err != -NFS4ERR_DELAY) break;
nfs4_handle_exception(server, err, &exception);
} while (exception.retry); return err;
}
ctx = nfs4_state_find_open_context(state); if (IS_ERR(ctx)) return -EAGAIN;
clear_bit(NFS_DELEGATED_STATE, &state->flags);
nfs_state_clear_open_state_flags(state);
ret = nfs4_do_open_reclaim(ctx, state);
put_nfs_open_context(ctx); return ret;
}
staticint nfs4_handle_delegation_recall_error(struct nfs_server *server, struct nfs4_state *state, const nfs4_stateid *stateid, struct file_lock *fl, int err)
{ switch (err) { default:
printk(KERN_ERR "NFS: %s: unhandled error " "%d.\n", __func__, err);
fallthrough; case 0: case -ENOENT: case -EAGAIN: case -ESTALE: case -ETIMEDOUT: break; case -NFS4ERR_BADSESSION: case -NFS4ERR_BADSLOT: case -NFS4ERR_BAD_HIGH_SLOT: case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION: case -NFS4ERR_DEADSESSION: return -EAGAIN; case -NFS4ERR_STALE_CLIENTID: case -NFS4ERR_STALE_STATEID: /* Don't recall a delegation if it was lost */
nfs4_schedule_lease_recovery(server->nfs_client); return -EAGAIN; case -NFS4ERR_MOVED:
nfs4_schedule_migration_recovery(server); return -EAGAIN; case -NFS4ERR_LEASE_MOVED:
nfs4_schedule_lease_moved_recovery(server->nfs_client); return -EAGAIN; case -NFS4ERR_DELEG_REVOKED: case -NFS4ERR_ADMIN_REVOKED: case -NFS4ERR_EXPIRED: case -NFS4ERR_BAD_STATEID: case -NFS4ERR_OPENMODE:
nfs_inode_find_state_and_recover(state->inode,
stateid);
nfs4_schedule_stateid_recovery(server, state); return -EAGAIN; case -NFS4ERR_DELAY: case -NFS4ERR_GRACE:
ssleep(1); return -EAGAIN; case -ENOMEM: case -NFS4ERR_DENIED: if (fl) { struct nfs4_lock_state *lsp = fl->fl_u.nfs4_fl.owner; if (lsp)
set_bit(NFS_LOCK_LOST, &lsp->ls_flags);
} return 0;
} return err;
}
/* If this request hasn't been cancelled, do nothing */ if (!data->cancelled) goto out_free; /* In case of error, no cleanup! */ if (!data->rpc_done) goto out_free;
state = nfs4_opendata_to_nfs4_state(data); if (!IS_ERR(state))
nfs4_close_state(state, data->o_arg.fmode);
out_free:
nfs4_opendata_put(data);
}
if (nfs_wait_on_sequence(data->o_arg.seqid, task) != 0) goto out_wait; /* * Check if we still need to send an OPEN call, or if we can use * a delegation instead.
*/ if (data->state != NULL) { struct nfs_delegation *delegation;
if (can_open_cached(data->state, data->o_arg.fmode,
data->o_arg.open_flags, claim)) goto out_no_action;
rcu_read_lock();
delegation = nfs4_get_valid_delegation(data->state->inode); if (can_open_delegated(delegation, data->o_arg.fmode, claim)) goto unlock_no_action;
rcu_read_unlock();
} /* Update client id. */
data->o_arg.clientid = clp->cl_clientid; switch (claim) { default: break; case NFS4_OPEN_CLAIM_PREVIOUS: case NFS4_OPEN_CLAIM_DELEG_CUR_FH: case NFS4_OPEN_CLAIM_DELEG_PREV_FH:
data->o_arg.open_bitmap = &nfs4_open_noattr_bitmap[0];
fallthrough; case NFS4_OPEN_CLAIM_FH:
task->tk_msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_NOATTR];
}
data->timestamp = jiffies; if (nfs4_setup_sequence(data->o_arg.server->nfs_client,
&data->o_arg.seq_args,
&data->o_res.seq_res,
task) != 0)
nfs_release_seqid(data->o_arg.seqid);
/* Set the create mode (note dependency on the session type) */
data->o_arg.createmode = NFS4_CREATE_UNCHECKED; if (data->o_arg.open_flags & O_EXCL) {
data->o_arg.createmode = NFS4_CREATE_EXCLUSIVE4_1; if (clp->cl_mvops->minor_version == 0) {
data->o_arg.createmode = NFS4_CREATE_EXCLUSIVE; /* don't put an ACCESS op in OPEN compound if O_EXCL, * because ACCESS will return permission denied for
* all bits until close */
data->o_res.access_request = data->o_arg.access = 0;
} elseif (nfs4_has_persistent_session(clp))
data->o_arg.createmode = NFS4_CREATE_GUARDED;
} return;
unlock_no_action:
trace_nfs4_cached_open(data->state);
rcu_read_unlock();
out_no_action:
task->tk_action = NULL;
out_wait:
nfs4_sequence_done(task, &data->o_res.seq_res);
}
/* In case of error, no cleanup! */ if (data->rpc_status != 0 || !data->rpc_done) {
nfs_release_seqid(data->o_arg.seqid); goto out_free;
} /* If this request hasn't been cancelled, do nothing */ if (!data->cancelled) goto out_free; /* In case we need an open_confirm, no cleanup! */ if (data->o_res.rflags & NFS4_OPEN_RESULT_CONFIRM) goto out_free;
state = nfs4_opendata_to_nfs4_state(data); if (!IS_ERR(state))
nfs4_close_state(state, data->o_arg.fmode);
out_free:
nfs4_opendata_put(data);
}
if (o_res->rflags & NFS4_OPEN_RESULT_CONFIRM)
status = _nfs4_proc_open_confirm(data);
return status;
}
/* * Additional permission checks in order to distinguish between an * open for read, and an open for execute. This works around the * fact that NFSv4 OPEN treats read and execute permissions as being * the same. * Note that in the non-execute case, we want to turn off permission * checking if we just created a new file (POSIX open() semantics).
*/ staticint nfs4_opendata_access(conststruct cred *cred, struct nfs4_opendata *opendata, struct nfs4_state *state, fmode_t fmode)
{ struct nfs_access_entry cache;
u32 mask, flags;
/* access call failed or for some reason the server doesn't
* support any access modes -- defer access call until later */ if (opendata->o_res.access_supported == 0) return 0;
mask = 0; if (fmode & FMODE_EXEC) { /* ONLY check for exec rights */ if (S_ISDIR(state->inode->i_mode))
mask = NFS4_ACCESS_LOOKUP; else
mask = NFS4_ACCESS_EXECUTE;
} elseif ((fmode & FMODE_READ) && !opendata->file_created)
mask = NFS4_ACCESS_READ;
/* * OPEN_EXPIRED: * reclaim state on the server after a network partition. * Assumes caller holds the appropriate lock
*/ staticint _nfs4_open_expired(struct nfs_open_context *ctx, struct nfs4_state *state)
{ struct nfs4_opendata *opendata; int ret;
opendata = nfs4_open_recoverdata_alloc(ctx, state, NFS4_OPEN_CLAIM_FH); if (IS_ERR(opendata)) return PTR_ERR(opendata); /* * We're not recovering a delegation, so ask for no delegation. * Otherwise the recovery thread could deadlock with an outstanding * delegation return.
*/
opendata->o_arg.open_flags = O_DIRECT;
ret = nfs4_open_recover(opendata, state); if (ret == -ESTALE)
d_drop(ctx->dentry);
nfs4_opendata_put(opendata); return ret;
}
switch (stateid->type) { default: break; case NFS4_INVALID_STATEID_TYPE: case NFS4_SPECIAL_STATEID_TYPE: case NFS4_FREED_STATEID_TYPE: return -NFS4ERR_BAD_STATEID; case NFS4_REVOKED_STATEID_TYPE: goto out_free;
}
status = nfs41_test_stateid(server, stateid, cred); switch (status) { case -NFS4ERR_EXPIRED: case -NFS4ERR_ADMIN_REVOKED: case -NFS4ERR_DELEG_REVOKED: break; default: return status;
}
out_free: /* Ack the revoked state to the server */
nfs41_free_stateid(server, stateid, cred, true); return -NFS4ERR_EXPIRED;
}
/* Get the delegation credential for use by test/free_stateid */
rcu_read_lock();
delegation = rcu_dereference(NFS_I(state->inode)->delegation); if (delegation == NULL) {
rcu_read_unlock();
nfs_state_clear_delegation(state); return NFS_OK;
}
/** * nfs41_check_expired_locks - possibly free a lock stateid * * @state: NFSv4 state for an inode * * Returns NFS_OK if recovery for this stateid is now finished. * Otherwise a negative NFS4ERR value is returned.
*/ staticint nfs41_check_expired_locks(struct nfs4_state *state)
{ int status, ret = NFS_OK; struct nfs4_lock_state *lsp, *prev = NULL; struct nfs_server *server = NFS_SERVER(state->inode);
if (!test_bit(LK_STATE_IN_USE, &state->flags)) goto out;
status = nfs41_test_and_free_expired_stateid(server,
&lsp->ls_stateid,
cred);
trace_nfs4_test_lock_stateid(state, lsp, status); if (status == -NFS4ERR_EXPIRED ||
status == -NFS4ERR_BAD_STATEID) {
clear_bit(NFS_LOCK_INITIALIZED, &lsp->ls_flags);
lsp->ls_stateid.type = NFS4_INVALID_STATEID_TYPE; if (!recover_lost_locks)
set_bit(NFS_LOCK_LOST, &lsp->ls_flags);
} elseif (status != NFS_OK) {
ret = status;
nfs4_put_lock_state(prev); goto out;
}
spin_lock(&state->state_lock);
}
}
spin_unlock(&state->state_lock);
nfs4_put_lock_state(prev);
out: return ret;
}
/** * nfs41_check_open_stateid - possibly free an open stateid * * @state: NFSv4 state for an inode * * Returns NFS_OK if recovery for this stateid is now finished. * Otherwise a negative NFS4ERR value is returned.
*/ staticint nfs41_check_open_stateid(struct nfs4_state *state)
{ struct nfs_server *server = NFS_SERVER(state->inode);
nfs4_stateid *stateid = &state->open_stateid; conststruct cred *cred = state->owner->so_cred; int status;
if (test_bit(NFS_OPEN_STATE, &state->flags) == 0) return -NFS4ERR_BAD_STATEID;
status = nfs41_test_and_free_expired_stateid(server, stateid, cred);
trace_nfs4_test_open_stateid(state, NULL, status); if (status == -NFS4ERR_EXPIRED || status == -NFS4ERR_BAD_STATEID) {
nfs_state_clear_open_state_flags(state);
stateid->type = NFS4_INVALID_STATEID_TYPE; return status;
} if (nfs_open_stateid_recover_openmode(state)) return -NFS4ERR_OPENMODE; return NFS_OK;
}
staticint nfs41_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *state)
{ int status;
status = nfs41_check_delegation_stateid(state); if (status != NFS_OK) return status;
nfs41_delegation_recover_stateid(state);
status = nfs41_check_expired_locks(state); if (status != NFS_OK) return status;
status = nfs41_check_open_stateid(state); if (status != NFS_OK)
status = nfs4_open_expired(sp, state); return status;
} #endif
/* * on an EXCLUSIVE create, the server should send back a bitmask with FATTR4-* * fields corresponding to attributes that were used to store the verifier. * Make sure we clobber those fields in the later setattr call
*/ staticunsigned nfs4_exclusive_attrset(struct nfs4_opendata *opendata, struct iattr *sattr, struct nfs4_label **label)
{ const __u32 *bitmask = opendata->o_arg.server->exclcreat_bitmask;
__u32 attrset[3]; unsigned ret; unsigned i;
for (i = 0; i < ARRAY_SIZE(attrset); i++) {
attrset[i] = opendata->o_res.attrset[i]; if (opendata->o_arg.createmode == NFS4_CREATE_EXCLUSIVE4_1)
attrset[i] &= ~bitmask[i];
}
ret = (opendata->o_arg.createmode == NFS4_CREATE_EXCLUSIVE) ?
sattr->ia_valid : 0;
if ((attrset[1] & (FATTR4_WORD1_TIME_ACCESS|FATTR4_WORD1_TIME_ACCESS_SET))) { if (sattr->ia_valid & ATTR_ATIME_SET)
ret |= ATTR_ATIME_SET; else
ret |= ATTR_ATIME;
}
if ((attrset[1] & (FATTR4_WORD1_TIME_MODIFY|FATTR4_WORD1_TIME_MODIFY_SET))) { if (sattr->ia_valid & ATTR_MTIME_SET)
ret |= ATTR_MTIME_SET; else
ret |= ATTR_MTIME;
}
if (!(attrset[2] & FATTR4_WORD2_SECURITY_LABEL))
*label = NULL; return ret;
}
ret = _nfs4_proc_open(opendata, ctx); if (ret != 0) goto out;
state = _nfs4_opendata_to_nfs4_state(opendata);
ret = PTR_ERR(state); if (IS_ERR(state)) goto out;
ctx->state = state; if (server->caps & NFS_CAP_POSIX_LOCK)
set_bit(NFS_STATE_POSIX_LOCKS, &state->flags); if (opendata->o_res.rflags & NFS4_OPEN_RESULT_MAY_NOTIFY_LOCK)
set_bit(NFS_STATE_MAY_NOTIFY_LOCK, &state->flags); if (opendata->o_res.rflags & NFS4_OPEN_RESULT_PRESERVE_UNLINKED)
set_bit(NFS_INO_PRESERVE_UNLINKED, &NFS_I(state->inode)->flags);
dentry = opendata->dentry; if (d_really_is_negative(dentry)) { struct dentry *alias;
d_drop(dentry);
alias = d_splice_alias(igrab(state->inode), dentry); /* d_splice_alias() can't fail here - it's a non-directory */ if (alias) {
dput(ctx->dentry);
ctx->dentry = dentry = alias;
}
}
switch(opendata->o_arg.claim) { default: break; case NFS4_OPEN_CLAIM_NULL: case NFS4_OPEN_CLAIM_DELEGATE_CUR: case NFS4_OPEN_CLAIM_DELEGATE_PREV: if (!opendata->rpc_done) break; if (opendata->o_res.delegation.type != 0)
dir_verifier = nfs_save_change_attribute(dir);
nfs_set_verifier(dentry, dir_verifier);
}
/* Parse layoutget results before we check for access */
pnfs_parse_lgopen(state->inode, opendata->lgp, ctx);
ret = nfs4_opendata_access(sp->so_cred, opendata, state, acc_mode); if (ret != 0) goto out;
if (d_inode(dentry) == state->inode)
nfs_inode_attach_open_context(ctx);
out: if (!opendata->cancelled) { if (opendata->lgp) {
nfs4_lgopen_release(opendata->lgp);
opendata->lgp = NULL;
}
nfs4_sequence_free_slot(&opendata->o_res.seq_res);
} return ret;
}
/* Protect against reboot recovery conflicts */
status = -ENOMEM;
sp = nfs4_get_state_owner(server, cred, GFP_KERNEL); if (sp == NULL) {
dprintk("nfs4_do_open: nfs4_get_state_owner failed!\n"); goto out_err;
}
status = nfs4_client_recover_expired_lease(server->nfs_client); if (status != 0) goto err_put_state_owner; if (d_really_is_positive(dentry))
nfs4_return_incompatible_delegation(d_inode(dentry), fmode);
status = -ENOMEM; if (d_really_is_positive(dentry))
claim = NFS4_OPEN_CLAIM_FH;
opendata = nfs4_opendata_alloc(dentry, sp, fmode, flags,
c, claim, GFP_KERNEL); if (opendata == NULL) goto err_put_state_owner;
if (server->attr_bitmask[2] & FATTR4_WORD2_MDSTHRESHOLD) { if (!opendata->f_attr.mdsthreshold) {
opendata->f_attr.mdsthreshold = pnfs_mdsthreshold_alloc(); if (!opendata->f_attr.mdsthreshold) goto err_opendata_put;
}
opendata->o_arg.open_bitmap = &nfs4_pnfs_open_bitmap[0];
} if (d_really_is_positive(dentry))
opendata->state = nfs4_get_open_state(d_inode(dentry), sp);
status = _nfs4_open_and_get_state(opendata, ctx); if (status != 0) goto err_opendata_put;
state = ctx->state;
if ((opendata->o_arg.open_flags & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL) &&
(opendata->o_arg.createmode != NFS4_CREATE_GUARDED)) { unsigned attrs = nfs4_exclusive_attrset(opendata, sattr, &label); /* * send create attributes which was not set by open * with an extra setattr.
*/ if (attrs || label) { unsigned ia_old = sattr->ia_valid;
do {
status = _nfs4_do_open(dir, ctx, flags, &c, opened);
res = ctx->state;
trace_nfs4_open_file(ctx, flags, status); if (status == 0) break; /* NOTE: BAD_SEQID means the server and client disagree about the * book-keeping w.r.t. state-changing operations * (OPEN/CLOSE/LOCK/LOCKU...) * It is actually a sign of a bug on the client or on the server. * * If we receive a BAD_SEQID error in the particular case of * doing an OPEN, we assume that nfs_increment_open_seqid() will * have unhashed the old state_owner for us, and that we can * therefore safely retry using a new one. We should still warn * the user though...
*/ if (status == -NFS4ERR_BAD_SEQID) {
pr_warn_ratelimited("NFS: v4 server %s " " returned a bad sequence-id error!\n",
NFS_SERVER(dir)->nfs_client->cl_hostname);
exception.retry = 1; continue;
} /* * BAD_STATEID on OPEN means that the server cancelled our * state before it received the OPEN_CONFIRM. * Recover by retrying the request as per the discussion * on Page 181 of RFC3530.
*/ if (status == -NFS4ERR_BAD_STATEID) {
exception.retry = 1; continue;
} if (status == -NFS4ERR_EXPIRED) {
nfs4_schedule_lease_recovery(server->nfs_client);
exception.retry = 1; continue;
} if (status == -EAGAIN) { /* We must have found a delegation */
exception.retry = 1; continue;
} if (nfs4_clear_cap_atomic_open_v1(server, status, &exception)) continue;
res = ERR_PTR(nfs4_handle_exception(server,
status, &exception));
} while (exception.retry); return res;
}
/* * Update the seqid of an open stateid
*/ staticvoid nfs4_sync_open_stateid(nfs4_stateid *dst, struct nfs4_state *state)
{
__be32 seqid_open;
u32 dst_seqid; int seq;
for (;;) { if (!nfs4_valid_open_stateid(state)) break;
seq = read_seqbegin(&state->seqlock); if (!nfs4_state_match_open_stateid_other(state, dst)) {
nfs4_stateid_copy(dst, &state->open_stateid); if (read_seqretry(&state->seqlock, seq)) continue; break;
}
seqid_open = state->open_stateid.seqid; if (read_seqretry(&state->seqlock, seq)) continue;
/* * Update the seqid of an open stateid after receiving * NFS4ERR_OLD_STATEID
*/ staticbool nfs4_refresh_open_old_stateid(nfs4_stateid *dst, struct nfs4_state *state)
{
__be32 seqid_open;
u32 dst_seqid; bool ret; int seq, status = -EAGAIN;
DEFINE_WAIT(wait);
for (;;) {
ret = false; if (!nfs4_valid_open_stateid(state)) break;
seq = read_seqbegin(&state->seqlock); if (!nfs4_state_match_open_stateid_other(state, dst)) { if (read_seqretry(&state->seqlock, seq)) continue; break;
}
/* Did another OPEN bump the state's seqid? try again: */ if ((s32)(be32_to_cpu(seqid_open) - dst_seqid) > 0) {
dst->seqid = seqid_open;
write_sequnlock(&state->seqlock);
ret = true; break;
}
/* server says we're behind but we haven't seen the update yet */
set_bit(NFS_STATE_CHANGE_WAIT, &state->flags);
prepare_to_wait(&state->waitq, &wait, TASK_KILLABLE);
write_sequnlock(&state->seqlock);
trace_nfs4_close_stateid_update_wait(state->inode, dst, 0);
if (fatal_signal_pending(current) || nfs_current_task_exiting())
status = -EINTR; else if (schedule_timeout(5*HZ) != 0)
status = 0;
finish_wait(&state->waitq, &wait);
if (!status) continue; if (status == -EINTR) break;
/* we slept the whole 5 seconds, we must have lost a seqid */
dst->seqid = cpu_to_be32(dst_seqid + 1);
ret = true; break;
}
/* hmm. we are done with the inode, and in the process of freeing * the state_owner. we keep this around to process errors
*/ switch (task->tk_status) { case 0:
res_stateid = &calldata->res.stateid;
renew_lease(server, calldata->timestamp); break; case -NFS4ERR_ACCESS: if (calldata->arg.bitmask != NULL) {
calldata->arg.bitmask = NULL;
calldata->res.fattr = NULL; goto out_restart;
} break; case -NFS4ERR_OLD_STATEID: /* Did we race with OPEN? */ if (nfs4_refresh_open_old_stateid(&calldata->arg.stateid,
state)) goto out_restart; goto out_release; case -NFS4ERR_ADMIN_REVOKED: case -NFS4ERR_STALE_STATEID: case -NFS4ERR_EXPIRED:
nfs4_free_revoked_stateid(server,
&calldata->arg.stateid,
task->tk_msg.rpc_cred);
fallthrough; case -NFS4ERR_BAD_STATEID: if (calldata->arg.fmode == 0) break;
fallthrough; default:
task->tk_status = nfs4_async_handle_exception(task,
server, task->tk_status, &exception);
calldata->retrans = exception.retrans; if (exception.retry) goto out_restart;
}
nfs_clear_open_stateid(state, &calldata->arg.stateid,
res_stateid, calldata->arg.fmode);
out_release:
task->tk_status = 0;
nfs_release_seqid(calldata->arg.seqid);
nfs_refresh_inode(calldata->inode, &calldata->fattr);
dprintk("%s: ret = %d\n", __func__, task->tk_status); return;
out_restart:
task->tk_status = 0;
rpc_restart_call_prepare(task); goto out_release;
}
/* * It is possible for data to be read/written from a mem-mapped file * after the sys_close call (which hits the vfs layer as a flush). * This means that we can't safely call nfsv4 close on a file until * the inode is cleared. This in turn means that we are not good * NFSv4 citizens - we do not indicate to the server to update the file's * share state even when we are done with one of the three share * stateid's in the inode. * * NOTE: Caller must be holding the sp->so_owner semaphore!
*/ int nfs4_do_close(struct nfs4_state *state, gfp_t gfp_mask, int wait)
{ struct nfs_server *server = NFS_SERVER(state->inode); struct nfs_seqid *(*alloc_seqid)(struct nfs_seqid_counter *, gfp_t); struct nfs4_closedata *calldata; struct nfs4_state_owner *sp = state->owner; struct rpc_task *task; struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CLOSE],
.rpc_cred = state->owner->so_cred,
}; struct rpc_task_setup task_setup_data = {
.rpc_client = server->client,
.rpc_message = &msg,
.callback_ops = &nfs4_close_ops,
.workqueue = nfsiod_workqueue,
.flags = RPC_TASK_ASYNC | RPC_TASK_CRED_NOREF,
}; int status = -ENOMEM;
if (nfs_server_capable(state->inode, NFS_CAP_MOVEABLE))
task_setup_data.flags |= RPC_TASK_MOVEABLE;
/* Avoid a regression due to buggy server */ for (i = 0; i < ARRAY_SIZE(res.exclcreat_bitmask); i++)
res.exclcreat_bitmask[i] &= res.attr_bitmask[i];
memcpy(server->exclcreat_bitmask, res.exclcreat_bitmask, sizeof(server->exclcreat_bitmask));
staticbool _is_same_nfs4_pathname(struct nfs4_pathname *path1, struct nfs4_pathname *path2)
{ int i;
if (path1->ncomponents != path2->ncomponents) returnfalse; for (i = 0; i < path1->ncomponents; i++) { if (path1->components[i].len != path2->components[i].len) returnfalse; if (memcmp(path1->components[i].data, path2->components[i].data,
path1->components[i].len)) returnfalse;
} returntrue;
}
/* * Retry pseudoroot lookup with various security flavors. We do this when: * * NFSv4.0: the PUTROOTFH operation returns NFS4ERR_WRONGSEC * NFSv4.1: the server does not support the SECINFO_NO_NAME operation * * Returns zero on success, or a negative NFS4ERR value, or a * negative errno value.
*/ staticint nfs4_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fattr *fattr)
{ /* Per 3530bis 15.33.5 */ staticconst rpc_authflavor_t flav_array[] = {
RPC_AUTH_GSS_KRB5P,
RPC_AUTH_GSS_KRB5I,
RPC_AUTH_GSS_KRB5,
RPC_AUTH_UNIX, /* courtesy */
RPC_AUTH_NULL,
}; int status = -EPERM;
size_t i;
if (server->auth_info.flavor_len > 0) { /* try each flavor specified by user */ for (i = 0; i < server->auth_info.flavor_len; i++) {
status = nfs4_lookup_root_sec(
server, fhandle, fattr,
server->auth_info.flavors[i]); if (status == -NFS4ERR_WRONGSEC || status == -EACCES) continue; break;
}
} else { /* no flavors specified by user, try default list */ for (i = 0; i < ARRAY_SIZE(flav_array); i++) {
status = nfs4_lookup_root_sec(server, fhandle, fattr,
flav_array[i]); if (status == -NFS4ERR_WRONGSEC || status == -EACCES) continue; break;
}
}
/* * -EACCES could mean that the user doesn't have correct permissions * to access the mount. It could also mean that we tried to mount * with a gss auth flavor, but rpc.gssd isn't running. Either way, * existing mount programs don't handle -EACCES very well so it should * be mapped to -EPERM instead.
*/ if (status == -EACCES)
status = -EPERM; return status;
}
/** * nfs4_proc_get_rootfh - get file handle for server's pseudoroot * @server: initialized nfs_server handle * @fhandle: we fill in the pseudo-fs root file handle * @fattr: we fill in a bare bones struct fattr * @auth_probe: probe the auth flavours * * Returns zero on success, or a negative errno.
*/ int nfs4_proc_get_rootfh(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fattr *fattr, bool auth_probe)
{ int status = 0;
if (!auth_probe)
status = nfs4_lookup_root(server, fhandle, fattr);
if (auth_probe || status == NFS4ERR_WRONGSEC)
status = server->nfs_client->cl_mvops->find_root_sec(
server, fhandle, fattr);
if (fattr->valid & NFS_ATTR_FATTR_FSID &&
!nfs_fsid_equal(&server->fsid, &fattr->fsid))
memcpy(&server->fsid, &fattr->fsid, sizeof(server->fsid));
out: return error;
}
/* * Get locations and (maybe) other attributes of a referral. * Note that we'll actually follow the referral later when * we detect fsid mismatch in inode revalidation
*/ staticint nfs4_get_referral(struct rpc_clnt *client, struct inode *dir, conststruct qstr *name, struct nfs_fattr *fattr, struct nfs_fh *fhandle)
{ int status = -ENOMEM; struct page *page = NULL; struct nfs4_fs_locations *locations = NULL;
page = alloc_page(GFP_KERNEL); if (page == NULL) goto out;
locations = kmalloc(sizeof(struct nfs4_fs_locations), GFP_KERNEL); if (locations == NULL) goto out;
locations->fattr = fattr;
status = nfs4_proc_fs_locations(client, dir, name, locations, page); if (status != 0) goto out;
/* * If the fsid didn't change, this is a migration event, not a * referral. Cause us to drop into the exception handler, which * will kick off migration recovery.
*/ if (nfs_fsid_equal(&NFS_SERVER(dir)->fsid, &fattr->fsid)) {
dprintk("%s: server did not return a different fsid for" " a referral at %s\n", __func__, name->name);
status = -NFS4ERR_MOVED; goto out;
} /* Fixup attributes for the nfs_lookup() call to nfs_fhget() */
nfs_fixup_referral_attributes(fattr);
memset(fhandle, 0, sizeof(struct nfs_fh));
out: if (page)
__free_page(page);
kfree(locations); return status;
}
/* * The file is not closed if it is opened due to the a request to change * the size of the file. The open call will not be needed once the * VFS layer lookup-intents are implemented. * * Close is called when the inode is destroyed. * If we haven't opened the file for O_WRONLY, we * need to in the size_change case to obtain a stateid. * * Got race? * Because OPEN is always done by name in nfsv4, it is * possible that we opened a different file by the same * name. We can recognize this race condition, but we * can't do anything about it besides returning an error. * * This will be fixed with VFS changes (lookup-intent).
*/ staticint
nfs4_proc_setattr(struct dentry *dentry, struct nfs_fattr *fattr, struct iattr *sattr)
{ struct inode *inode = d_inode(dentry); conststruct cred *cred = NULL; struct nfs_open_context *ctx = NULL; int status;
/* Deal with open(O_TRUNC) */ if (sattr->ia_valid & ATTR_OPEN)
sattr->ia_valid &= ~(ATTR_MTIME|ATTR_CTIME);
/* Optimization: if the end result is no change, don't RPC */ if ((sattr->ia_valid & ~(ATTR_FILE|ATTR_OPEN)) == 0) return 0;
/* Search for an existing open(O_WRITE) file */ if (sattr->ia_valid & ATTR_FILE) {
ctx = nfs_file_open_context(sattr->ia_file); if (ctx)
cred = ctx->cred;
}
/* Return any delegations if we're going to change ACLs */ if ((sattr->ia_valid & (ATTR_MODE|ATTR_UID|ATTR_GID)) != 0)
nfs4_inode_make_writeable(inode);
/* * TODO: For the time being, we don't try to get any attributes * along with any of the zero-copy operations READ, READDIR, * READLINK, WRITE. * * In the case of the first three, we want to put the GETATTR * after the read-type operation -- this is because it is hard * to predict the length of a GETATTR response in v4, and thus * align the READ data correctly. This means that the GETATTR * may end up partially falling into the page cache, and we should * shift it into the 'tail' of the xdr_buf before processing. * To do this efficiently, we need to know the total length * of data received, which doesn't seem to be available outside * of the RPC layer. * * In the case of WRITE, we also want to put the GETATTR after * the operation -- in this case because we want to make sure * we get the post-operation mtime and size. * * Both of these changes to the XDR layer would in fact be quite * minor, but I decided to leave them for a subsequent patch.
*/ staticint _nfs4_proc_readlink(struct inode *inode, struct page *page, unsignedint pgbase, unsignedint pglen)
{ struct nfs4_readlink args = {
.fh = NFS_FH(inode),
.pgbase = pgbase,
.pglen = pglen,
.pages = &page,
}; struct nfs4_readlink_res res; struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_READLINK],
.rpc_argp = &args,
.rpc_resp = &res,
};
/* * This is just for mknod. open(O_CREAT) will always do ->open_context().
*/ staticint
nfs4_proc_create(struct inode *dir, struct dentry *dentry, struct iattr *sattr, int flags)
{ struct nfs_server *server = NFS_SERVER(dir); struct nfs4_label l, *ilabel; struct nfs_open_context *ctx; struct nfs4_state *state; int status = 0;
ctx = alloc_nfs_open_context(dentry, FMODE_READ, NULL); if (IS_ERR(ctx)) return PTR_ERR(ctx);
if (!(server->attr_bitmask[2] & FATTR4_WORD2_MODE_UMASK))
sattr->ia_mode &= ~current_umask(); do {
alias = _nfs4_proc_mkdir(dir, dentry, sattr, label, &err);
trace_nfs4_mkdir(dir, &dentry->d_name, err); if (err)
alias = ERR_PTR(nfs4_handle_exception(NFS_SERVER(dir),
err,
&exception));
} while (exception.retry);
nfs4_label_release_security(label);
/* None of the pathconf attributes are mandatory to implement */ if ((args.bitmask[0] & nfs4_pathconf_bitmap[0]) == 0) {
memset(pathconf, 0, sizeof(*pathconf)); return 0;
}
/* If the current stateid represents a lost lock, then exit */ if (nfs4_set_rw_stateid(&_current_stateid, ctx, l_ctx, fmode) == -EIO) returntrue; return nfs4_stateid_match(stateid, &_current_stateid);
}
staticbool nfs4_error_stateid_expired(int err)
{ switch (err) { case -NFS4ERR_DELEG_REVOKED: case -NFS4ERR_ADMIN_REVOKED: case -NFS4ERR_BAD_STATEID: case -NFS4ERR_STALE_STATEID: case -NFS4ERR_OLD_STATEID: case -NFS4ERR_OPENMODE: case -NFS4ERR_EXPIRED: returntrue;
} returnfalse;
}
static bool nfs4_write_need_cache_consistency_data(struct nfs_pgio_header *hdr)
{ /* Don't request attributes for pNFS or O_DIRECT writes */ if (hdr->ds_clp != NULL || hdr->dreq != NULL) returnfalse; /* Otherwise, request attributes if and only if we don't hold * a delegation
*/ return nfs4_have_delegation(hdr->inode, FMODE_READ, 0) == 0;
}
/* * nfs4_proc_async_renew(): This is not one of the nfs_rpc_ops; it is a special * standalone procedure for queueing an asynchronous RENEW.
*/ staticvoid 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);
}
/* Assuming that XATTR_SIZE_MAX is a multiple of PAGE_SIZE, and that * it's OK to put sizeof(void) * (XATTR_SIZE_MAX/PAGE_SIZE) bytes on * the stack.
*/ #define NFS4ACL_MAXPAGES DIV_ROUND_UP(XATTR_SIZE_MAX, PAGE_SIZE)
spin_lock(&inode->i_lock);
acl = nfsi->nfs4_acl; if (acl == NULL) goto out; if (acl->type != type) goto out; if (buf == NULL) /* user is just asking for length */ goto out_len; if (acl->cached == 0) goto out;
ret = -ERANGE; /* see getxattr(2) man page */ if (acl->len > buflen) goto out;
memcpy(buf, acl->data, acl->len);
out_len:
ret = acl->len;
out:
spin_unlock(&inode->i_lock); return ret;
}
/* * The getxattr API returns the required buffer length when called with a * NULL buf. The NFSv4 acl tool then calls getxattr again after allocating * the required buf. On a NULL buf, we send a page of data to the server * guessing that the ACL request can be serviced by a page. If so, we cache * up to the page of ACL data, and the 2nd call to getxattr is serviced by * the cache. If not so, we throw away the page, and cache the required * length. The next getxattr call will then produce another round trip to * the server, this time with the input buf of the required size.
*/ static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf,
size_t buflen, enum nfs4_acl_type type)
{ struct page **pages; struct nfs_getaclargs args = {
.fh = NFS_FH(inode),
.acl_type = type,
.acl_len = buflen,
}; struct nfs_getaclres res = {
.acl_type = type,
.acl_len = buflen,
}; struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETACL],
.rpc_argp = &args,
.rpc_resp = &res,
}; unsignedint npages; int ret = -ENOMEM, i; struct nfs_server *server = NFS_SERVER(inode);
/* Handle the case where the passed-in buffer is too short */ if (res.acl_flags & NFS4_ACL_TRUNC) { /* Did the user only issue a request for the acl length? */ if (buf == NULL) goto out_ok;
ret = -ERANGE; goto out_free;
}
nfs4_write_cached_acl(inode, pages, res.acl_data_offset, res.acl_len,
type); if (buf) { if (res.acl_len > buflen) {
ret = -ERANGE; goto out_free;
}
_copy_from_pages(buf, pages, res.acl_data_offset, res.acl_len);
}
out_ok:
ret = res.acl_len;
out_free: while (--i >= 0)
__free_page(pages[i]); if (res.acl_scratch)
__free_page(res.acl_scratch);
kfree(pages); return ret;
}
static ssize_t nfs4_get_acl_uncached(struct inode *inode, void *buf,
size_t buflen, enum nfs4_acl_type type)
{ struct nfs4_exception exception = {
.interruptible = true,
};
ssize_t ret; do {
ret = __nfs4_get_acl_uncached(inode, buf, buflen, type);
trace_nfs4_get_acl(inode, ret); if (ret >= 0) break;
ret = nfs4_handle_exception(NFS_SERVER(inode), ret, &exception);
} while (exception.retry); return ret;
}
if (unlikely(NFS_FH(inode)->size == 0)) return -ENODATA; if (!nfs4_server_supports_acls(server, type)) return -EOPNOTSUPP;
ret = nfs_revalidate_inode(inode, NFS_INO_INVALID_CHANGE); if (ret < 0) return ret; if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_ACL)
nfs_zap_acl_cache(inode);
ret = nfs4_read_cached_acl(inode, buf, buflen, type); if (ret != -ENOENT) /* -ENOENT is returned if there is no ACL or if there is an ACL
* but no cached acl data, just the acl length */ return ret; return nfs4_get_acl_uncached(inode, buf, buflen, type);
}
if (test_bit(NFS4CLNT_PURGE_STATE, &clp->cl_state)) { /* An impossible timestamp guarantees this value
* will never match a generated boot time. */
verf[0] = cpu_to_be32(U32_MAX);
verf[1] = cpu_to_be32(U32_MAX);
} else { struct nfs_net *nn = net_generic(clp->cl_net, nfs_net_id);
u64 ns = ktime_to_ns(nn->boot_time);
buflen = nfs4_get_uniquifier(clp, buf, sizeof(buf)); if (buflen)
len += buflen + 1;
if (len > NFS4_OPAQUE_LIMIT + 1) return -EINVAL;
/* * Since this string is allocated at mount time, and held until the * nfs_client is destroyed, we can use GFP_KERNEL here w/o worrying * about a memory-reclaim deadlock.
*/
str = kmalloc(len, GFP_KERNEL); if (!str) return -ENOMEM;
buflen = nfs4_get_uniquifier(clp, buf, sizeof(buf)); if (buflen)
len += buflen + 1;
if (len > NFS4_OPAQUE_LIMIT + 1) return -EINVAL;
/* * Since this string is allocated at mount time, and held until the * nfs_client is destroyed, we can use GFP_KERNEL here w/o worrying * about a memory-reclaim deadlock.
*/
str = kmalloc(len, GFP_KERNEL); if (!str) return -ENOMEM;
/* * nfs4_callback_up_net() starts only "tcp" and "tcp6" callback * services. Advertise one based on the address family of the * clientaddr.
*/ staticunsignedint
nfs4_init_callback_netid(conststruct nfs_client *clp, char *buf, size_t len)
{ if (strchr(clp->cl_ipaddr, ':') != NULL) return scnprintf(buf, len, "tcp6"); else return scnprintf(buf, len, "tcp");
}
/** * nfs4_proc_setclientid_confirm - Confirm client ID * @clp: state data structure * @arg: result of a previous SETCLIENTID * @cred: credential to use for this call * * Returns zero, a negative errno, or a negative NFS4ERR status code.
*/ int nfs4_proc_setclientid_confirm(struct nfs_client *clp, struct nfs4_setclientid_res *arg, conststruct cred *cred)
{ struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SETCLIENTID_CONFIRM],
.rpc_argp = arg,
.rpc_cred = cred,
}; int status;
if (data->args.sattr_args && task->tk_status != 0) { switch(data->res.sattr_ret) { case 0:
data->args.sattr_args = NULL;
data->res.sattr_res = false; break; case -NFS4ERR_ADMIN_REVOKED: case -NFS4ERR_DELEG_REVOKED: case -NFS4ERR_EXPIRED: case -NFS4ERR_BAD_STATEID: /* Let the main handler below do stateid recovery */ break; case -NFS4ERR_OLD_STATEID: if (nfs4_refresh_delegation_stateid(&data->stateid,
data->inode)) goto out_restart;
fallthrough; default:
data->args.sattr_args = NULL;
data->res.sattr_res = false; goto out_restart;
}
}
switch (task->tk_status) { case 0:
renew_lease(data->res.server, data->timestamp); break; case -NFS4ERR_ADMIN_REVOKED: case -NFS4ERR_DELEG_REVOKED: case -NFS4ERR_EXPIRED:
nfs4_free_revoked_stateid(data->res.server,
data->args.stateid,
task->tk_msg.rpc_cred);
fallthrough; case -NFS4ERR_BAD_STATEID: case -NFS4ERR_STALE_STATEID: case -ETIMEDOUT:
task->tk_status = 0; break; case -NFS4ERR_OLD_STATEID: if (!nfs4_refresh_delegation_stateid(&data->stateid, data->inode))
nfs4_stateid_seqid_inc(&data->stateid); if (data->args.bitmask) {
data->args.bitmask = NULL;
data->res.fattr = NULL;
} goto out_restart; case -NFS4ERR_ACCESS: if (data->args.bitmask) {
data->args.bitmask = NULL;
data->res.fattr = NULL; goto out_restart;
}
fallthrough; default:
task->tk_status = nfs4_async_handle_exception(task,
data->res.server, task->tk_status,
&exception);
data->retrans = exception.retrans; if (exception.retry) goto out_restart;
}
nfs_delegation_mark_returned(data->inode, data->args.stateid);
data->rpc_status = task->tk_status; return;
out_restart:
task->tk_status = 0;
rpc_restart_call_prepare(task);
}
/* Ensure this is an unlock - when canceling a lock, the * canceled lock is passed in, and it won't be an unlock.
*/
fl->c.flc_type = F_UNLCK; if (fl->c.flc_flags & FL_CLOSE)
set_bit(NFS_CONTEXT_UNLOCK, &ctx->flags);
data = nfs4_alloc_unlockdata(fl, ctx, lsp, seqid); if (data == NULL) {
nfs_free_seqid(seqid); return ERR_PTR(-ENOMEM);
}
/* NULL key means to wake up everyone */ if (key) { struct cb_notify_lock_args *cbnl = key; struct nfs_lowner *lowner = &cbnl->cbnl_owner,
*wowner = &waiter->owner;
/* Only wake if the callback was for the same owner. */ if (lowner->id != wowner->id || lowner->s_dev != wowner->s_dev) return 0;
/* Make sure it's for the right inode */ if (nfs_compare_fh(NFS_FH(waiter->inode), &cbnl->cbnl_fh)) return 0;
}
/* Don't bother with waitqueue if we don't expect a callback */ if (!test_bit(NFS_STATE_MAY_NOTIFY_LOCK, &state->flags)) return nfs4_retry_setlk_simple(state, cmd, request);
staticint
nfs4_proc_lock(struct file *filp, int cmd, struct file_lock *request)
{ struct nfs_open_context *ctx; struct nfs4_state *state; int status;
/* verify open state */
ctx = nfs_file_open_context(filp);
state = ctx->state;
if (IS_GETLK(cmd)) { if (state != NULL) return nfs4_proc_getlk(state, F_GETLK, request); return 0;
}
if (!(IS_SETLK(cmd) || IS_SETLKW(cmd))) return -EINVAL;
if (lock_is_unlock(request)) { if (state != NULL) return nfs4_proc_unlck(state, cmd, request); return 0;
}
if (state == NULL) return -ENOLCK;
if ((request->c.flc_flags & FL_POSIX) &&
!test_bit(NFS_STATE_POSIX_LOCKS, &state->flags)) return -ENOLCK;
/* * Don't rely on the VFS having checked the file open mode, * since it won't do this for flock() locks.
*/ switch (request->c.flc_type) { case F_RDLCK: if (!(filp->f_mode & FMODE_READ)) return -EBADF; break; case F_WRLCK: if (!(filp->f_mode & FMODE_WRITE)) return -EBADF;
}
status = nfs4_set_lock_state(state, request); if (status != 0) return status;
staticint nfs4_add_lease(struct file *file, int arg, struct file_lease **lease, void **priv)
{ struct inode *inode = file_inode(file);
fmode_t type = arg == F_RDLCK ? FMODE_READ : FMODE_WRITE; int ret;
/* No delegation, no lease */ if (!nfs4_have_delegation(inode, type, 0)) return -EAGAIN;
ret = generic_setlease(file, arg, lease, priv); if (ret || nfs4_have_delegation(inode, type, 0)) return ret; /* We raced with a delegation return */
nfs4_delete_lease(file, priv); return -EAGAIN;
}
int nfs4_proc_setlease(struct file *file, int arg, struct file_lease **lease, void **priv)
{ switch (arg) { case F_RDLCK: case F_WRLCK: return nfs4_add_lease(file, arg, lease, priv); case F_UNLCK: return nfs4_delete_lease(file, priv); default: return -EINVAL;
}
}
int nfs4_lock_delegation_recall(struct file_lock *fl, struct nfs4_state *state, const nfs4_stateid *stateid)
{ struct nfs_server *server = NFS_SERVER(state->inode); int err;
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);
}
}
if (!nfs_server_capable(inode, NFS_CAP_XATTR)) return -EOPNOTSUPP;
/* * There is no mapping from the MAY_* flags to the NFS_ACCESS_XA* * flags right now. Handling of xattr operations use the normal * file read/write permissions. * * Just in case the server has other ideas (which RFC 8276 allows), * do a cached access check for the XA* flags to possibly avoid * doing an RPC and getting EACCES back.
*/ if (!nfs_access_get_cached(inode, current_cred(), &mask, true)) { if (!(mask & NFS_ACCESS_XAWRITE)) return -EACCES;
}
if (buf == NULL) {
ret = nfs42_proc_removexattr(inode, key); if (!ret)
nfs4_xattr_cache_remove(inode, key);
} else {
ret = nfs42_proc_setxattr(inode, key, buf, buflen, flags); if (!ret)
nfs4_xattr_cache_add(inode, key, buf, NULL, buflen);
}
/* * nfs_fhget will use either the mounted_on_fileid or the fileid
*/ staticvoid nfs_fixup_referral_attributes(struct nfs_fattr *fattr)
{ if (!(((fattr->valid & NFS_ATTR_FATTR_MOUNTED_ON_FILEID) ||
(fattr->valid & NFS_ATTR_FATTR_FILEID)) &&
(fattr->valid & NFS_ATTR_FATTR_FSID) &&
(fattr->valid & NFS_ATTR_FATTR_V4_LOCATIONS))) return;
/* Ask for the fileid of the absent filesystem if mounted_on_fileid
* is not supported */ if (NFS_SERVER(dir)->attr_bitmask[1] & FATTR4_WORD1_MOUNTED_ON_FILEID)
bitmask[0] &= ~FATTR4_WORD0_FILEID; else
bitmask[1] &= ~FATTR4_WORD1_MOUNTED_ON_FILEID;
nfs_fattr_init(fs_locations->fattr);
fs_locations->server = server;
fs_locations->nlocations = 0;
status = nfs4_call_sync(client, server, &msg, &args.seq_args, &res.seq_res, 0);
dprintk("%s: returned status = %d\n", __func__, status); return status;
}
nfs4_init_sequence(&args.seq_args, &res.seq_res, 0, 1);
status = nfs4_call_sync_custom(&task_setup_data); if (status == NFS4_OK &&
res.seq_res.sr_status_flags & SEQ4_STATUS_LEASE_MOVED)
status = -NFS4ERR_LEASE_MOVED; return status;
}
#endif/* CONFIG_NFS_V4_1 */
/** * nfs4_proc_get_locations - discover locations for a migrated FSID * @server: pointer to nfs_server to process * @fhandle: pointer to the kernel NFS client file handle * @locations: result of query * @page: buffer * @cred: credential to use for this operation * * Returns NFS4_OK on success, a negative NFS4ERR status code if the * operation failed, or a negative errno if a local error occurred. * * On success, "locations" is filled in, but if the server has * no locations information, NFS_ATTR_FATTR_V4_LOCATIONS is not * asserted. * * -NFS4ERR_LEASE_MOVED is returned if the server still has leases * from this client that require migration recovery.
*/ int nfs4_proc_get_locations(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs4_fs_locations *locations, struct page *page, conststruct cred *cred)
{ struct nfs_client *clp = server->nfs_client; conststruct nfs4_mig_recovery_ops *ops =
clp->cl_mvops->mig_recovery_ops; struct nfs4_exception exception = {
.interruptible = true,
}; int status;
do {
status = ops->get_locations(server, fhandle, locations, page,
cred); if (status != -NFS4ERR_DELAY) break;
nfs4_handle_exception(server, status, &exception);
} while (exception.retry); return status;
}
/* * 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.
*/ staticint _nfs40_proc_fsid_present(struct inode *inode, conststruct 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,
}; unsignedlong now = jiffies; int status;
res.fh = nfs_alloc_fhandle(); if (res.fh == NULL) return -ENOMEM;
nfs4_init_sequence(&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;
}
#ifdef CONFIG_NFS_V4_1
/* * This operation also signals the server that this client is * performing "lease moved" recovery. The server can stop asserting * SEQ4_STATUS_LEASE_MOVED for this client. The client ID performing * this operation is identified in the SEQUENCE operation in this * compound.
*/ staticint _nfs41_proc_fsid_present(struct inode *inode, conststruct cred *cred)
{ struct nfs_server *server = NFS_SERVER(inode); struct rpc_clnt *clnt = server->client; struct nfs4_fsid_present_arg args = {
.fh = NFS_FH(inode),
}; struct nfs4_fsid_present_res res = {
}; struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_FSID_PRESENT],
.rpc_argp = &args,
.rpc_resp = &res,
.rpc_cred = cred,
}; int status;
res.fh = nfs_alloc_fhandle(); if (res.fh == NULL) return -ENOMEM;
nfs4_init_sequence(&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 == NFS4_OK &&
res.seq_res.sr_status_flags & SEQ4_STATUS_LEASE_MOVED)
status = -NFS4ERR_LEASE_MOVED; return status;
}
#endif/* CONFIG_NFS_V4_1 */
/** * nfs4_proc_fsid_present - Is this FSID present or absent on server? * @inode: inode on FSID to check * @cred: credential to use for this operation * * Server indicates whether the FSID is present, moved, or not * recognized. This operation is necessary to clear a LEASE_MOVED * condition for this client ID. * * Returns NFS4_OK if the FSID is present on this server, * -NFS4ERR_MOVED if the FSID is no longer present, a negative * NFS4ERR code if some error occurred on the server, or a * negative errno if a local failure occurred.
*/ int nfs4_proc_fsid_present(struct inode *inode, conststruct cred *cred)
{ struct nfs_server *server = NFS_SERVER(inode); struct nfs_client *clp = server->nfs_client; conststruct nfs4_mig_recovery_ops *ops =
clp->cl_mvops->mig_recovery_ops; struct nfs4_exception exception = {
.interruptible = true,
}; int status;
do {
status = ops->fsid_present(inode, cred); if (status != -NFS4ERR_DELAY) break;
nfs4_handle_exception(server, status, &exception);
} while (exception.retry); return status;
}
/* * If 'use_integrity' is true and the state managment nfs_client * cl_rpcclient is using krb5i/p, use the integrity protected cl_rpcclient * and the machine credential as per RFC3530bis and RFC5661 Security * Considerations sections. Otherwise, just use the user cred with the * filesystem's rpc_client.
*/ staticint _nfs4_proc_secinfo(struct inode *dir, conststruct qstr *name, struct nfs4_secinfo_flavors *flavors, bool use_integrity)
{ int status; struct rpc_clnt *clnt = NFS_SERVER(dir)->client; struct nfs_client *clp = NFS_SERVER(dir)->nfs_client; struct nfs4_secinfo_arg args = {
.dir_fh = NFS_FH(dir),
.name = name,
}; struct nfs4_secinfo_res res = {
.flavors = flavors,
}; struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SECINFO],
.rpc_argp = &args,
.rpc_resp = &res,
}; struct nfs4_call_sync_data data = {
.seq_server = NFS_SERVER(dir),
.seq_args = &args.seq_args,
.seq_res = &res.seq_res,
}; struct rpc_task_setup task_setup = {
.rpc_client = clnt,
.rpc_message = &msg,
.callback_ops = clp->cl_mvops->call_sync_ops,
.callback_data = &data,
.flags = RPC_TASK_NO_ROUND_ROBIN,
}; conststruct cred *cred = NULL;
if (use_integrity) {
clnt = clp->cl_rpcclient;
task_setup.rpc_client = clnt;
int nfs4_proc_secinfo(struct inode *dir, conststruct qstr *name, struct nfs4_secinfo_flavors *flavors)
{ struct nfs4_exception exception = {
.interruptible = true,
}; int err; do {
err = -NFS4ERR_WRONGSEC;
/* try to use integrity protection with machine cred */ if (_nfs4_is_integrity_protected(NFS_SERVER(dir)->nfs_client))
err = _nfs4_proc_secinfo(dir, name, flavors, true);
/* * if unable to use integrity protection, or SECINFO with * integrity protection returns NFS4ERR_WRONGSEC (which is * disallowed by spec, but exists in deployed servers) use * the current filesystem's rpc_client and the user cred.
*/ if (err == -NFS4ERR_WRONGSEC)
err = _nfs4_proc_secinfo(dir, name, flavors, false);
#ifdef CONFIG_NFS_V4_1 /* * Check the exchange flags returned by the server for invalid flags, having * both PNFS and NON_PNFS flags set, and not having one of NON_PNFS, PNFS, or * DS flags set.
*/ staticint nfs4_check_cl_exchange_flags(u32 flags, u32 version)
{ if (version >= 2 && (flags & ~EXCHGID4_2_FLAG_MASK_R)) goto out_inval; elseif (version < 2 && (flags & ~EXCHGID4_FLAG_MASK_R)) goto out_inval; if ((flags & EXCHGID4_FLAG_USE_PNFS_MDS) &&
(flags & EXCHGID4_FLAG_USE_NON_PNFS)) goto out_inval; if (!(flags & (EXCHGID4_FLAG_MASK_PNFS))) goto out_inval; return NFS_OK;
out_inval: return -NFS4ERR_INVAL;
}
/* * Minimum set of SP4_MACH_CRED operations from RFC 5661 in the enforce map * and operations we'd like to see to enable certain features in the allow map
*/ staticconststruct nfs41_state_protection nfs4_sp4_mach_cred_request = {
.how = SP4_MACH_CRED,
.enforce.u.words = {
[1] = 1 << (OP_BIND_CONN_TO_SESSION - 32) |
1 << (OP_EXCHANGE_ID - 32) |
1 << (OP_CREATE_SESSION - 32) |
1 << (OP_DESTROY_SESSION - 32) |
1 << (OP_DESTROY_CLIENTID - 32)
},
.allow.u.words = {
[0] = 1 << (OP_CLOSE) |
1 << (OP_OPEN_DOWNGRADE) |
1 << (OP_LOCKU) |
1 << (OP_DELEGRETURN) |
1 << (OP_COMMIT),
[1] = 1 << (OP_SECINFO - 32) |
1 << (OP_SECINFO_NO_NAME - 32) |
1 << (OP_LAYOUTRETURN - 32) |
1 << (OP_TEST_STATEID - 32) |
1 << (OP_FREE_STATEID - 32) |
1 << (OP_WRITE - 32)
}
};
/* * Select the state protection mode for client `clp' given the server results * from exchange_id in `sp'. * * Returns 0 on success, negative errno otherwise.
*/ staticint nfs4_sp4_select_mode(struct nfs_client *clp, struct nfs41_state_protection *sp)
{ staticconst u32 supported_enforce[NFS4_OP_MAP_NUM_WORDS] = {
[1] = 1 << (OP_BIND_CONN_TO_SESSION - 32) |
1 << (OP_EXCHANGE_ID - 32) |
1 << (OP_CREATE_SESSION - 32) |
1 << (OP_DESTROY_SESSION - 32) |
1 << (OP_DESTROY_CLIENTID - 32)
}; unsignedlong flags = 0; unsignedint i; int ret = 0;
if (sp->how == SP4_MACH_CRED) { /* Print state protect result */
dfprintk(MOUNT, "Server SP4_MACH_CRED support:\n"); for (i = 0; i <= LAST_NFS4_OP; i++) { if (test_bit(i, sp->enforce.u.longs))
dfprintk(MOUNT, " enforce op %d\n", i); if (test_bit(i, sp->allow.u.longs))
dfprintk(MOUNT, " allow op %d\n", i);
}
/* make sure nothing is on enforce list that isn't supported */ for (i = 0; i < NFS4_OP_MAP_NUM_WORDS; i++) { if (sp->enforce.u.words[i] & ~supported_enforce[i]) {
dfprintk(MOUNT, "sp4_mach_cred: disabled\n");
ret = -EINVAL; goto out;
}
}
/* * Minimal mode - state operations are allowed to use machine * credential. Note this already happens by default, so the * client doesn't have to do anything more than the negotiation. * * NOTE: we don't care if EXCHANGE_ID is in the list - * we're already using the machine cred for exchange_id * and will never use a different cred.
*/ if (test_bit(OP_BIND_CONN_TO_SESSION, sp->enforce.u.longs) &&
test_bit(OP_CREATE_SESSION, sp->enforce.u.longs) &&
test_bit(OP_DESTROY_SESSION, sp->enforce.u.longs) &&
test_bit(OP_DESTROY_CLIENTID, sp->enforce.u.longs)) {
dfprintk(MOUNT, "sp4_mach_cred:\n");
dfprintk(MOUNT, " minimal mode enabled\n");
__set_bit(NFS_SP4_MACH_CRED_MINIMAL, &flags);
} else {
dfprintk(MOUNT, "sp4_mach_cred: disabled\n");
ret = -EINVAL; goto out;
}
/* Save the EXCHANGE_ID verifier session trunk tests */
memcpy(clp->cl_confirm.data, argp->verifier.data, sizeof(clp->cl_confirm.data));
out:
trace_nfs4_exchange_id(clp, status);
rpc_put_task(task); return status;
}
/* * nfs4_proc_exchange_id() * * Returns zero, a negative errno, or a negative NFS4ERR status code. * * Since the clientid has expired, all compounds using sessions * associated with the stale clientid will be returning * NFS4ERR_BADSESSION in the sequence operation, and will therefore * be in some phase of session reset. * * Will attempt to negotiate SP4_MACH_CRED if krb5i / krb5p auth is used.
*/ int nfs4_proc_exchange_id(struct nfs_client *clp, conststruct cred *cred)
{
rpc_authflavor_t authflavor = clp->cl_rpcclient->cl_auth->au_flavor; int status;
/* try SP4_MACH_CRED if krb5i/p */ if (authflavor == RPC_AUTH_GSS_KRB5I ||
authflavor == RPC_AUTH_GSS_KRB5P) {
status = _nfs4_proc_exchange_id(clp, cred, SP4_MACH_CRED); if (!status) return 0;
}
/** * nfs4_test_session_trunk * * This is an add_xprt_test() test function called from * rpc_clnt_setup_test_and_add_xprt. * * The rpc_xprt_switch is referrenced by rpc_clnt_setup_test_and_add_xprt * and is dereferrenced in nfs4_exchange_id_release * * Upon success, add the new transport to the rpc_clnt * * @clnt: struct rpc_clnt to get new transport * @xprt: the rpc_xprt to test * @data: call data for _nfs4_proc_exchange_id.
*/ void nfs4_test_session_trunk(struct rpc_clnt *clnt, struct rpc_xprt *xprt, void *data)
{ struct nfs4_add_xprt_data *adata = data; struct rpc_task *task; int status;
status = rpc_call_sync(clp->cl_rpcclient, &msg,
RPC_TASK_TIMEOUT | RPC_TASK_NO_ROUND_ROBIN);
trace_nfs4_destroy_clientid(clp, status); if (status)
dprintk("NFS: Got error %d from the server %s on " "DESTROY_CLIENTID.", status, clp->cl_hostname); return status;
}
/* just setup sequence, do not trigger session recovery
since we're invoked within one */
nfs4_setup_sequence(data->clp,
&data->args->la_seq_args,
&data->res->lr_seq_res,
task);
}
/* * Called from nfs4_state_manager thread for session setup, so don't recover * from sequence operation or clientid errors.
*/ staticvoid nfs4_get_lease_time_done(struct rpc_task *task, void *calldata)
{ struct nfs4_get_lease_time_data *data =
(struct nfs4_get_lease_time_data *)calldata;
if (!nfs4_sequence_done(task, &data->res->lr_seq_res)) return; switch (task->tk_status) { case -NFS4ERR_DELAY: case -NFS4ERR_GRACE:
rpc_delay(task, NFS4_POLL_RETRY_MIN);
task->tk_status = 0;
fallthrough; case -NFS4ERR_RETRY_UNCACHED_REP:
rpc_restart_call_prepare(task); return;
}
}
/* * Initialize the values to be used by the client in CREATE_SESSION * If nfs4_init_session set the fore channel request and response sizes, * use them. * * Set the back channel max_resp_sz_cached to zero to force the client to * always set csa_cachethis to FALSE because the current implementation * of the back channel DRC only supports caching the CB_SEQUENCE operation.
*/ staticvoid nfs4_init_channel_attrs(struct nfs41_create_session_args *args, struct rpc_clnt *clnt)
{ unsignedint max_rqst_sz, max_resp_sz; unsignedint max_bc_payload = rpc_max_bc_payload(clnt); unsignedint max_bc_slots = rpc_num_bc_slots(clnt);
if (rcvd->max_resp_sz > sent->max_resp_sz) return -EINVAL; /* * Our requested max_ops is the minimum we need; we're not * prepared to break up compounds into smaller pieces than that. * So, no point even trying to continue if the server won't * cooperate:
*/ if (rcvd->max_ops < sent->max_ops) return -EINVAL; if (rcvd->max_reqs == 0) return -EINVAL; if (rcvd->max_reqs > NFS4_MAX_SLOT_TABLE)
rcvd->max_reqs = NFS4_MAX_SLOT_TABLE; return 0;
}
status = rpc_call_sync(session->clp->cl_rpcclient, &msg,
RPC_TASK_TIMEOUT | RPC_TASK_NO_ROUND_ROBIN);
trace_nfs4_create_session(clp, status);
switch (status) { case -NFS4ERR_STALE_CLIENTID: case -NFS4ERR_DELAY: case -ETIMEDOUT: case -EACCES: case -EAGAIN: goto out;
}
clp->cl_seqid++; if (!status) { /* Verify the session's negotiated channel_attrs values */
status = nfs4_verify_channel_attrs(&args, &res); /* Increment the clientid slot sequence id */ if (status) goto out;
nfs4_update_session(session, &res);
}
out: return status;
}
/* * Issues a CREATE_SESSION operation to the server. * It is the responsibility of the caller to verify the session is * expired before calling this routine.
*/ int nfs4_proc_create_session(struct nfs_client *clp, conststruct cred *cred)
{ int status; unsigned *ptr; struct nfs4_session *session = clp->cl_session; struct nfs4_add_xprt_data xprtdata = {
.clp = clp,
}; struct rpc_add_xprt_test rpcdata = {
.add_xprt_test = clp->cl_mvops->session_trunk,
.data = &xprtdata,
};
status = _nfs4_proc_create_session(clp, cred); if (status) goto out;
/* Init or reset the session slot tables */
status = nfs4_setup_session_slot_tables(session);
dprintk("slot table setup returned %d\n", status); if (status) goto out;
/* * Issue the over-the-wire RPC DESTROY_SESSION. * The caller must serialize access to this routine.
*/ int nfs4_proc_destroy_session(struct nfs4_session *session, conststruct cred *cred)
{ struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_DESTROY_SESSION],
.rpc_argp = session,
.rpc_cred = cred,
}; int status = 0;
/* session is still being setup */ if (!test_and_clear_bit(NFS4_SESSION_ESTABLISHED, &session->session_state)) return 0;
status = rpc_call_sync(session->clp->cl_rpcclient, &msg,
RPC_TASK_TIMEOUT | RPC_TASK_NO_ROUND_ROBIN);
trace_nfs4_destroy_session(session->clp, status);
if (status)
dprintk("NFS: Got error %d from the server on DESTROY_SESSION. " "Session has been destroyed regardless...\n", status);
rpc_clnt_manage_trunked_xprts(session->clp->cl_rpcclient); return status;
}
staticint nfs41_reclaim_complete_handle_errors(struct rpc_task *task, struct nfs_client *clp)
{ switch(task->tk_status) { case 0:
wake_up_all(&clp->cl_lock_waitq);
fallthrough; case -NFS4ERR_COMPLETE_ALREADY: case -NFS4ERR_WRONG_CRED: /* What to do here? */ break; case -NFS4ERR_DELAY:
rpc_delay(task, NFS4_POLL_RETRY_MAX);
fallthrough; case -NFS4ERR_RETRY_UNCACHED_REP: case -EACCES:
dprintk("%s: failed to reclaim complete error %d for server %s, retrying\n",
__func__, task->tk_status, clp->cl_hostname); return -EAGAIN; case -NFS4ERR_BADSESSION: case -NFS4ERR_DEADSESSION: case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION: break; default:
nfs4_schedule_lease_recovery(clp);
} return 0;
}
/* * NFS4ERR_LAYOUTUNAVAILABLE means we are not supposed to use pnfs * on the file. set tk_status to -ENODATA to tell upper layer to * retry go inband.
*/ case -NFS4ERR_LAYOUTUNAVAILABLE:
status = -ENODATA; goto out; /* * NFS4ERR_BADLAYOUT means the MDS cannot return a layout of * length lgp->args.minlength != 0 (see RFC5661 section 18.43.3).
*/ case -NFS4ERR_BADLAYOUT:
status = -EOVERFLOW; goto out; /* * NFS4ERR_LAYOUTTRYLATER is a conflict with another client * (or clients) writing to the same RAID stripe except when * the minlength argument is 0 (see RFC5661 section 18.43.3). * * Treat it like we would RECALLCONFLICT -- we retry for a little * while, and then eventually give up.
*/ case -NFS4ERR_LAYOUTTRYLATER: if (lgp->args.minlength == 0) {
status = -EOVERFLOW; goto out;
}
status = -EBUSY; break; case -NFS4ERR_RECALLCONFLICT: case -NFS4ERR_RETURNCONFLICT:
status = -ERECALLCONFLICT; break; case -NFS4ERR_DELEG_REVOKED: case -NFS4ERR_ADMIN_REVOKED: case -NFS4ERR_EXPIRED: case -NFS4ERR_BAD_STATEID:
exception->timeout = 0;
spin_lock(&inode->i_lock); /* If the open stateid was bad, then recover it. */ if (!lo || test_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags) ||
!nfs4_stateid_match_other(&lgp->args.stateid, &lo->plh_stateid)) {
spin_unlock(&inode->i_lock);
exception->state = lgp->args.ctx->state;
exception->stateid = &lgp->args.stateid; break;
}
/* * Mark the bad layout state as invalid, then retry
*/
pnfs_mark_layout_stateid_invalid(lo, &head);
spin_unlock(&inode->i_lock);
nfs_commit_inode(inode, 0);
pnfs_free_lseg_list(&head);
status = -EAGAIN; goto out;
}
err = nfs4_handle_exception(server, nfs4err, exception); if (!status) { if (exception->retry)
status = -EAGAIN; else
status = err;
}
out: return status;
}
if (!nfs41_sequence_process(task, &lrp->res.seq_res)) return;
if (task->tk_rpc_status == -ETIMEDOUT) {
lrp->rpc_status = -EAGAIN;
lrp->res.lrs_present = 0; return;
} /* * Was there an RPC level error? Assume the call succeeded, * and that we need to release the layout
*/ if (task->tk_rpc_status != 0 && RPC_WAS_SENT(task)) {
lrp->res.lrs_present = 0; return;
}
server = NFS_SERVER(lrp->args.inode); switch (task->tk_status) { case -NFS4ERR_OLD_STATEID: if (nfs4_layout_refresh_old_stateid(&lrp->args.stateid,
&lrp->args.range,
lrp->args.inode)) goto out_restart;
fallthrough; default:
task->tk_status = 0;
lrp->res.lrs_present = 0;
fallthrough; case 0: break; case -NFS4ERR_BADSESSION: case -NFS4ERR_DEADSESSION: case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION:
nfs4_schedule_session_recovery(server->nfs_client->cl_session,
task->tk_status);
lrp->res.lrs_present = 0;
lrp->rpc_status = -EAGAIN;
task->tk_status = 0; break; case -NFS4ERR_DELAY: if (nfs4_async_handle_error(task, server, NULL, NULL) ==
-EAGAIN) goto out_restart;
lrp->res.lrs_present = 0; break;
} return;
out_restart:
task->tk_status = 0;
nfs4_sequence_free_slot(&lrp->res.seq_res);
rpc_restart_call_prepare(task);
}
if (!nfs41_sequence_done(task, &data->res.seq_res)) return;
switch (task->tk_status) { /* Just ignore these failures */ case -NFS4ERR_DELEG_REVOKED: /* layout was recalled */ case -NFS4ERR_BADIOMODE: /* no IOMODE_RW layout for range */ case -NFS4ERR_BADLAYOUT: /* no layout */ case -NFS4ERR_GRACE: /* loca_recalim always false */
task->tk_status = 0; break; case 0: break; default: if (nfs4_async_handle_error(task, server, NULL, NULL) == -EAGAIN) {
rpc_restart_call_prepare(task); return;
}
}
}
staticint nfs41_proc_secinfo_no_name(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs4_secinfo_flavors *flavors)
{ struct nfs4_exception exception = {
.interruptible = true,
}; int err; do { /* first try using integrity protection */
err = -NFS4ERR_WRONGSEC;
/* try to use integrity protection with machine cred */ if (_nfs4_is_integrity_protected(server->nfs_client))
err = _nfs41_proc_secinfo_no_name(server, fhandle,
flavors, true);
/* * if unable to use integrity protection, or SECINFO with * integrity protection returns NFS4ERR_WRONGSEC (which is * disallowed by spec, but exists in deployed servers) use * the current filesystem's rpc_client and the user cred.
*/ if (err == -NFS4ERR_WRONGSEC)
err = _nfs41_proc_secinfo_no_name(server, fhandle,
flavors, false);
switch (err) { case 0: case -NFS4ERR_WRONGSEC: case -ENOTSUPP: goto out; default:
err = nfs4_handle_exception(server, err, &exception);
}
} while (exception.retry);
out: return err;
}
/* * Fall back on "guess and check" method if * the server doesn't support SECINFO_NO_NAME
*/ if (err == -NFS4ERR_WRONGSEC || err == -ENOTSUPP) {
err = nfs4_find_root_sec(server, fhandle, fattr); goto out_freepage;
} if (err) goto out_freepage;
for (i = 0; i < flavors->num_flavors; i++) {
secinfo = &flavors->flavors[i];
switch (secinfo->flavor) { case RPC_AUTH_NULL: case RPC_AUTH_UNIX: case RPC_AUTH_GSS:
flavor = rpcauth_get_pseudoflavor(secinfo->flavor,
&secinfo->flavor_info); break; default:
flavor = RPC_AUTH_MAXFLAVOR; break;
}
if (!nfs_auth_info_match(&server->auth_info, flavor))
flavor = RPC_AUTH_MAXFLAVOR;
if (flavor != RPC_AUTH_MAXFLAVOR) {
err = nfs4_lookup_root_sec(server, fhandle, fattr,
flavor); if (!err) break;
}
}
staticvoid nfs4_handle_delay_or_session_error(struct nfs_server *server, int err, struct nfs4_exception *exception)
{
exception->retry = 0; switch(err) { case -NFS4ERR_DELAY: case -NFS4ERR_RETRY_UNCACHED_REP:
nfs4_handle_exception(server, err, exception); break; case -NFS4ERR_BADSESSION: case -NFS4ERR_BADSLOT: case -NFS4ERR_BAD_HIGH_SLOT: case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION: case -NFS4ERR_DEADSESSION:
nfs4_do_handle_exception(server, err, exception);
}
}
/** * nfs41_test_stateid - perform a TEST_STATEID operation * * @server: server / transport on which to perform the operation * @stateid: state ID to test * @cred: credential * * Returns NFS_OK if the server recognizes that "stateid" is valid. * Otherwise a negative NFS4ERR value is returned if the operation * failed or the state ID is not currently valid.
*/ staticint nfs41_test_stateid(struct nfs_server *server, const nfs4_stateid *stateid, conststruct cred *cred)
{ struct nfs4_exception exception = {
.interruptible = true,
}; int err; do {
err = _nfs41_test_stateid(server, stateid, cred);
nfs4_handle_delay_or_session_error(server, err, &exception);
} while (exception.retry); return err;
}
staticvoid nfs4_enable_swap(struct inode *inode)
{ /* The state manager thread must always be running. * It will notice the client is a swapper, and stay put.
*/ struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
nfs4_schedule_state_manager(clp);
}
staticvoid nfs4_disable_swap(struct inode *inode)
{ /* The state manager thread will now exit once it is * woken.
*/ struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.328Bemerkung:
(vorverarbeitet am 2026-04-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.