713f46ac47
get a reply of EEXIST from an NFS server when a Mkdir RPC was retried, for an NFS over UDP mount. Upon investigation, it was found that the client was retransmitting the Mkdir RPC request over UDP, but with a different xid. As such, the retransmitted message would miss the Duplicate Request Cache in the server, causing it to reply EEXIST. The kernel client side UDP rpc code has two timers. The first one causes a retransmit using the same xid and socket and was set to a fixed value of 3seconds. (The default can be overridden via CLSET_RETRY_TIMEOUT.) The second one creates a new socket and xid and should be larger than the first. However, both NFS clients were setting the second timer to nm_timeo ("timeout=<value>" mount argument), which defaulted to 1second, so the first timer would never time out. This patch fixes both NFS clients so that they set the first timer using nm_timeo and makes the second timer larger than the first one. Reported by: jwd Tested by: jwd Reviewed by: jhb MFC after: 2 weeks
882 lines
23 KiB
C
882 lines
23 KiB
C
/*-
|
|
* Copyright (c) 1989, 1991, 1993, 1995
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Rick Macklem at The University of Guelph.
|
|
*
|
|
* 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.
|
|
* 4. 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 BY THE REGENTS AND CONTRIBUTORS ``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.
|
|
*
|
|
* @(#)nfs_socket.c 8.5 (Berkeley) 3/30/95
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* Socket operations for use by nfs
|
|
*/
|
|
|
|
#include "opt_inet6.h"
|
|
#include "opt_kdtrace.h"
|
|
#include "opt_kgssapi.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/signalvar.h>
|
|
#include <sys/syscallsubr.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/vnode.h>
|
|
|
|
#include <rpc/rpc.h>
|
|
|
|
#include <nfs/nfsproto.h>
|
|
#include <nfsclient/nfs.h>
|
|
#include <nfs/xdr_subs.h>
|
|
#include <nfsclient/nfsm_subs.h>
|
|
#include <nfsclient/nfsmount.h>
|
|
#include <nfsclient/nfsnode.h>
|
|
|
|
#ifdef KDTRACE_HOOKS
|
|
#include <sys/dtrace_bsd.h>
|
|
|
|
dtrace_nfsclient_nfs23_start_probe_func_t
|
|
dtrace_nfsclient_nfs23_start_probe;
|
|
|
|
dtrace_nfsclient_nfs23_done_probe_func_t
|
|
dtrace_nfsclient_nfs23_done_probe;
|
|
|
|
/*
|
|
* Registered probes by RPC type.
|
|
*/
|
|
uint32_t nfsclient_nfs2_start_probes[NFS_NPROCS];
|
|
uint32_t nfsclient_nfs2_done_probes[NFS_NPROCS];
|
|
|
|
uint32_t nfsclient_nfs3_start_probes[NFS_NPROCS];
|
|
uint32_t nfsclient_nfs3_done_probes[NFS_NPROCS];
|
|
#endif
|
|
|
|
static int nfs_bufpackets = 4;
|
|
static int nfs_reconnects;
|
|
static int nfs3_jukebox_delay = 10;
|
|
static int nfs_skip_wcc_data_onerr = 1;
|
|
static int fake_wchan;
|
|
|
|
SYSCTL_DECL(_vfs_oldnfs);
|
|
|
|
SYSCTL_INT(_vfs_oldnfs, OID_AUTO, bufpackets, CTLFLAG_RW, &nfs_bufpackets, 0,
|
|
"Buffer reservation size 2 < x < 64");
|
|
SYSCTL_INT(_vfs_oldnfs, OID_AUTO, reconnects, CTLFLAG_RD, &nfs_reconnects, 0,
|
|
"Number of times the nfs client has had to reconnect");
|
|
SYSCTL_INT(_vfs_oldnfs, OID_AUTO, nfs3_jukebox_delay, CTLFLAG_RW,
|
|
&nfs3_jukebox_delay, 0,
|
|
"Number of seconds to delay a retry after receiving EJUKEBOX");
|
|
SYSCTL_INT(_vfs_oldnfs, OID_AUTO, skip_wcc_data_onerr, CTLFLAG_RW,
|
|
&nfs_skip_wcc_data_onerr, 0,
|
|
"Disable weak cache consistency checking when server returns an error");
|
|
|
|
static void nfs_down(struct nfsmount *, struct thread *, const char *,
|
|
int, int);
|
|
static void nfs_up(struct nfsmount *, struct thread *, const char *,
|
|
int, int);
|
|
static int nfs_msg(struct thread *, const char *, const char *, int);
|
|
|
|
extern int nfsv2_procid[];
|
|
|
|
struct nfs_cached_auth {
|
|
int ca_refs; /* refcount, including 1 from the cache */
|
|
uid_t ca_uid; /* uid that corresponds to this auth */
|
|
AUTH *ca_auth; /* RPC auth handle */
|
|
};
|
|
|
|
/*
|
|
* RTT estimator
|
|
*/
|
|
|
|
static enum nfs_rto_timer_t nfs_proct[NFS_NPROCS] = {
|
|
NFS_DEFAULT_TIMER, /* NULL */
|
|
NFS_GETATTR_TIMER, /* GETATTR */
|
|
NFS_DEFAULT_TIMER, /* SETATTR */
|
|
NFS_LOOKUP_TIMER, /* LOOKUP */
|
|
NFS_GETATTR_TIMER, /* ACCESS */
|
|
NFS_READ_TIMER, /* READLINK */
|
|
NFS_READ_TIMER, /* READ */
|
|
NFS_WRITE_TIMER, /* WRITE */
|
|
NFS_DEFAULT_TIMER, /* CREATE */
|
|
NFS_DEFAULT_TIMER, /* MKDIR */
|
|
NFS_DEFAULT_TIMER, /* SYMLINK */
|
|
NFS_DEFAULT_TIMER, /* MKNOD */
|
|
NFS_DEFAULT_TIMER, /* REMOVE */
|
|
NFS_DEFAULT_TIMER, /* RMDIR */
|
|
NFS_DEFAULT_TIMER, /* RENAME */
|
|
NFS_DEFAULT_TIMER, /* LINK */
|
|
NFS_READ_TIMER, /* READDIR */
|
|
NFS_READ_TIMER, /* READDIRPLUS */
|
|
NFS_DEFAULT_TIMER, /* FSSTAT */
|
|
NFS_DEFAULT_TIMER, /* FSINFO */
|
|
NFS_DEFAULT_TIMER, /* PATHCONF */
|
|
NFS_DEFAULT_TIMER, /* COMMIT */
|
|
NFS_DEFAULT_TIMER, /* NOOP */
|
|
};
|
|
|
|
/*
|
|
* Choose the correct RTT timer for this NFS procedure.
|
|
*/
|
|
static inline enum nfs_rto_timer_t
|
|
nfs_rto_timer(u_int32_t procnum)
|
|
{
|
|
|
|
return (nfs_proct[procnum]);
|
|
}
|
|
|
|
/*
|
|
* Initialize the RTT estimator state for a new mount point.
|
|
*/
|
|
static void
|
|
nfs_init_rtt(struct nfsmount *nmp)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NFS_MAX_TIMER; i++) {
|
|
nmp->nm_timers[i].rt_srtt = hz;
|
|
nmp->nm_timers[i].rt_deviate = 0;
|
|
nmp->nm_timers[i].rt_rtxcur = hz;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize sockets and congestion for a new NFS connection.
|
|
* We do not free the sockaddr if error.
|
|
*/
|
|
int
|
|
nfs_connect(struct nfsmount *nmp)
|
|
{
|
|
int rcvreserve, sndreserve;
|
|
int pktscale;
|
|
struct sockaddr *saddr;
|
|
struct ucred *origcred;
|
|
struct thread *td = curthread;
|
|
CLIENT *client;
|
|
struct netconfig *nconf;
|
|
rpcvers_t vers;
|
|
int one = 1, retries;
|
|
struct timeval timo;
|
|
|
|
/*
|
|
* We need to establish the socket using the credentials of
|
|
* the mountpoint. Some parts of this process (such as
|
|
* sobind() and soconnect()) will use the curent thread's
|
|
* credential instead of the socket credential. To work
|
|
* around this, temporarily change the current thread's
|
|
* credential to that of the mountpoint.
|
|
*
|
|
* XXX: It would be better to explicitly pass the correct
|
|
* credential to sobind() and soconnect().
|
|
*/
|
|
origcred = td->td_ucred;
|
|
td->td_ucred = nmp->nm_mountp->mnt_cred;
|
|
saddr = nmp->nm_nam;
|
|
|
|
vers = NFS_VER2;
|
|
if (nmp->nm_flag & NFSMNT_NFSV3)
|
|
vers = NFS_VER3;
|
|
else if (nmp->nm_flag & NFSMNT_NFSV4)
|
|
vers = NFS_VER4;
|
|
if (saddr->sa_family == AF_INET)
|
|
if (nmp->nm_sotype == SOCK_DGRAM)
|
|
nconf = getnetconfigent("udp");
|
|
else
|
|
nconf = getnetconfigent("tcp");
|
|
else
|
|
if (nmp->nm_sotype == SOCK_DGRAM)
|
|
nconf = getnetconfigent("udp6");
|
|
else
|
|
nconf = getnetconfigent("tcp6");
|
|
|
|
/*
|
|
* Get buffer reservation size from sysctl, but impose reasonable
|
|
* limits.
|
|
*/
|
|
pktscale = nfs_bufpackets;
|
|
if (pktscale < 2)
|
|
pktscale = 2;
|
|
if (pktscale > 64)
|
|
pktscale = 64;
|
|
mtx_lock(&nmp->nm_mtx);
|
|
if (nmp->nm_sotype == SOCK_DGRAM) {
|
|
sndreserve = (nmp->nm_wsize + NFS_MAXPKTHDR) * pktscale;
|
|
rcvreserve = (max(nmp->nm_rsize, nmp->nm_readdirsize) +
|
|
NFS_MAXPKTHDR) * pktscale;
|
|
} else if (nmp->nm_sotype == SOCK_SEQPACKET) {
|
|
sndreserve = (nmp->nm_wsize + NFS_MAXPKTHDR) * pktscale;
|
|
rcvreserve = (max(nmp->nm_rsize, nmp->nm_readdirsize) +
|
|
NFS_MAXPKTHDR) * pktscale;
|
|
} else {
|
|
if (nmp->nm_sotype != SOCK_STREAM)
|
|
panic("nfscon sotype");
|
|
sndreserve = (nmp->nm_wsize + NFS_MAXPKTHDR +
|
|
sizeof (u_int32_t)) * pktscale;
|
|
rcvreserve = (nmp->nm_rsize + NFS_MAXPKTHDR +
|
|
sizeof (u_int32_t)) * pktscale;
|
|
}
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
|
|
client = clnt_reconnect_create(nconf, saddr, NFS_PROG, vers,
|
|
sndreserve, rcvreserve);
|
|
CLNT_CONTROL(client, CLSET_WAITCHAN, "nfsreq");
|
|
if (nmp->nm_flag & NFSMNT_INT)
|
|
CLNT_CONTROL(client, CLSET_INTERRUPTIBLE, &one);
|
|
if (nmp->nm_flag & NFSMNT_RESVPORT)
|
|
CLNT_CONTROL(client, CLSET_PRIVPORT, &one);
|
|
if ((nmp->nm_flag & NFSMNT_SOFT) != 0) {
|
|
if (nmp->nm_sotype == SOCK_DGRAM)
|
|
/*
|
|
* For UDP, the large timeout for a reconnect will
|
|
* be set to "nm_retry * nm_timeo / 2", so we only
|
|
* want to do 2 reconnect timeout retries.
|
|
*/
|
|
retries = 2;
|
|
else
|
|
retries = nmp->nm_retry;
|
|
} else
|
|
retries = INT_MAX;
|
|
CLNT_CONTROL(client, CLSET_RETRIES, &retries);
|
|
|
|
/*
|
|
* For UDP, there are 2 timeouts:
|
|
* - CLSET_RETRY_TIMEOUT sets the initial timeout for the timer
|
|
* that does a retransmit of an RPC request using the same socket
|
|
* and xid. This is what you normally want to do, since NFS
|
|
* servers depend on "same xid" for their Duplicate Request Cache.
|
|
* - timeout specified in CLNT_CALL_MBUF(), which specifies when
|
|
* retransmits on the same socket should fail and a fresh socket
|
|
* created. Each of these timeouts counts as one CLSET_RETRIES,
|
|
* as set above.
|
|
* Set the initial retransmit timeout for UDP. This timeout doesn't
|
|
* exist for TCP and the following call just fails, which is ok.
|
|
*/
|
|
timo.tv_sec = nmp->nm_timeo / NFS_HZ;
|
|
timo.tv_usec = (nmp->nm_timeo % NFS_HZ) * 1000000 / NFS_HZ;
|
|
CLNT_CONTROL(client, CLSET_RETRY_TIMEOUT, &timo);
|
|
|
|
mtx_lock(&nmp->nm_mtx);
|
|
if (nmp->nm_client) {
|
|
/*
|
|
* Someone else already connected.
|
|
*/
|
|
CLNT_RELEASE(client);
|
|
} else
|
|
nmp->nm_client = client;
|
|
|
|
/*
|
|
* Protocols that do not require connections may be optionally left
|
|
* unconnected for servers that reply from a port other than NFS_PORT.
|
|
*/
|
|
if (!(nmp->nm_flag & NFSMNT_NOCONN)) {
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
CLNT_CONTROL(client, CLSET_CONNECT, &one);
|
|
} else
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
|
|
/* Restore current thread's credentials. */
|
|
td->td_ucred = origcred;
|
|
|
|
mtx_lock(&nmp->nm_mtx);
|
|
/* Initialize other non-zero congestion variables. */
|
|
nfs_init_rtt(nmp);
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* NFS disconnect. Clean up and unlink.
|
|
*/
|
|
void
|
|
nfs_disconnect(struct nfsmount *nmp)
|
|
{
|
|
CLIENT *client;
|
|
|
|
mtx_lock(&nmp->nm_mtx);
|
|
if (nmp->nm_client) {
|
|
client = nmp->nm_client;
|
|
nmp->nm_client = NULL;
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
rpc_gss_secpurge_call(client);
|
|
CLNT_CLOSE(client);
|
|
CLNT_RELEASE(client);
|
|
} else
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
}
|
|
|
|
void
|
|
nfs_safedisconnect(struct nfsmount *nmp)
|
|
{
|
|
|
|
nfs_disconnect(nmp);
|
|
}
|
|
|
|
static AUTH *
|
|
nfs_getauth(struct nfsmount *nmp, struct ucred *cred)
|
|
{
|
|
rpc_gss_service_t svc;
|
|
AUTH *auth;
|
|
|
|
switch (nmp->nm_secflavor) {
|
|
case RPCSEC_GSS_KRB5:
|
|
case RPCSEC_GSS_KRB5I:
|
|
case RPCSEC_GSS_KRB5P:
|
|
if (!nmp->nm_mech_oid)
|
|
if (!rpc_gss_mech_to_oid_call("kerberosv5",
|
|
&nmp->nm_mech_oid))
|
|
return (NULL);
|
|
if (nmp->nm_secflavor == RPCSEC_GSS_KRB5)
|
|
svc = rpc_gss_svc_none;
|
|
else if (nmp->nm_secflavor == RPCSEC_GSS_KRB5I)
|
|
svc = rpc_gss_svc_integrity;
|
|
else
|
|
svc = rpc_gss_svc_privacy;
|
|
auth = rpc_gss_secfind_call(nmp->nm_client, cred,
|
|
nmp->nm_principal, nmp->nm_mech_oid, svc);
|
|
if (auth)
|
|
return (auth);
|
|
/* fallthrough */
|
|
case AUTH_SYS:
|
|
default:
|
|
return (authunix_create(cred));
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Callback from the RPC code to generate up/down notifications.
|
|
*/
|
|
|
|
struct nfs_feedback_arg {
|
|
struct nfsmount *nf_mount;
|
|
int nf_lastmsg; /* last tprintf */
|
|
int nf_tprintfmsg;
|
|
struct thread *nf_td;
|
|
};
|
|
|
|
static void
|
|
nfs_feedback(int type, int proc, void *arg)
|
|
{
|
|
struct nfs_feedback_arg *nf = (struct nfs_feedback_arg *) arg;
|
|
struct nfsmount *nmp = nf->nf_mount;
|
|
struct timeval now;
|
|
|
|
getmicrouptime(&now);
|
|
|
|
switch (type) {
|
|
case FEEDBACK_REXMIT2:
|
|
case FEEDBACK_RECONNECT:
|
|
if (nf->nf_lastmsg + nmp->nm_tprintf_delay < now.tv_sec) {
|
|
nfs_down(nmp, nf->nf_td,
|
|
"not responding", 0, NFSSTA_TIMEO);
|
|
nf->nf_tprintfmsg = TRUE;
|
|
nf->nf_lastmsg = now.tv_sec;
|
|
}
|
|
break;
|
|
|
|
case FEEDBACK_OK:
|
|
nfs_up(nf->nf_mount, nf->nf_td,
|
|
"is alive again", NFSSTA_TIMEO, nf->nf_tprintfmsg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* nfs_request - goes something like this
|
|
* - fill in request struct
|
|
* - links it into list
|
|
* - calls nfs_send() for first transmit
|
|
* - calls nfs_receive() to get reply
|
|
* - break down rpc header and return with nfs reply pointed to
|
|
* by mrep or error
|
|
* nb: always frees up mreq mbuf list
|
|
*/
|
|
int
|
|
nfs_request(struct vnode *vp, struct mbuf *mreq, int procnum,
|
|
struct thread *td, struct ucred *cred, struct mbuf **mrp,
|
|
struct mbuf **mdp, caddr_t *dposp)
|
|
{
|
|
struct mbuf *mrep;
|
|
u_int32_t *tl;
|
|
struct nfsmount *nmp;
|
|
struct mbuf *md;
|
|
time_t waituntil;
|
|
caddr_t dpos;
|
|
int error = 0, timeo;
|
|
struct timeval now;
|
|
AUTH *auth = NULL;
|
|
enum nfs_rto_timer_t timer;
|
|
struct nfs_feedback_arg nf;
|
|
struct rpc_callextra ext;
|
|
enum clnt_stat stat;
|
|
struct timeval timo;
|
|
|
|
/* Reject requests while attempting a forced unmount. */
|
|
if (vp->v_mount->mnt_kern_flag & MNTK_UNMOUNTF) {
|
|
m_freem(mreq);
|
|
return (ESTALE);
|
|
}
|
|
nmp = VFSTONFS(vp->v_mount);
|
|
bzero(&nf, sizeof(struct nfs_feedback_arg));
|
|
nf.nf_mount = nmp;
|
|
nf.nf_td = td;
|
|
getmicrouptime(&now);
|
|
nf.nf_lastmsg = now.tv_sec -
|
|
((nmp->nm_tprintf_delay) - (nmp->nm_tprintf_initial_delay));
|
|
|
|
/*
|
|
* XXX if not already connected call nfs_connect now. Longer
|
|
* term, change nfs_mount to call nfs_connect unconditionally
|
|
* and let clnt_reconnect_create handle reconnects.
|
|
*/
|
|
if (!nmp->nm_client)
|
|
nfs_connect(nmp);
|
|
|
|
auth = nfs_getauth(nmp, cred);
|
|
if (!auth) {
|
|
m_freem(mreq);
|
|
return (EACCES);
|
|
}
|
|
bzero(&ext, sizeof(ext));
|
|
ext.rc_auth = auth;
|
|
|
|
ext.rc_feedback = nfs_feedback;
|
|
ext.rc_feedback_arg = &nf;
|
|
|
|
/*
|
|
* Use a conservative timeout for RPCs other than getattr,
|
|
* lookup, read or write. The justification for doing "other"
|
|
* this way is that these RPCs happen so infrequently that
|
|
* timer est. would probably be stale. Also, since many of
|
|
* these RPCs are non-idempotent, a conservative timeout is
|
|
* desired.
|
|
*/
|
|
timer = nfs_rto_timer(procnum);
|
|
if (timer != NFS_DEFAULT_TIMER)
|
|
ext.rc_timers = &nmp->nm_timers[timer - 1];
|
|
else
|
|
ext.rc_timers = NULL;
|
|
|
|
#ifdef KDTRACE_HOOKS
|
|
if (dtrace_nfsclient_nfs23_start_probe != NULL) {
|
|
uint32_t probe_id;
|
|
int probe_procnum;
|
|
|
|
if (nmp->nm_flag & NFSMNT_NFSV3) {
|
|
probe_id = nfsclient_nfs3_start_probes[procnum];
|
|
probe_procnum = procnum;
|
|
} else {
|
|
probe_id = nfsclient_nfs2_start_probes[procnum];
|
|
probe_procnum = nfsv2_procid[procnum];
|
|
}
|
|
if (probe_id != 0)
|
|
(dtrace_nfsclient_nfs23_start_probe)(probe_id, vp,
|
|
mreq, cred, probe_procnum);
|
|
}
|
|
#endif
|
|
|
|
nfsstats.rpcrequests++;
|
|
tryagain:
|
|
/*
|
|
* This timeout specifies when a new socket should be created,
|
|
* along with new xid values. For UDP, this should be done
|
|
* infrequently, since retransmits of RPC requests should normally
|
|
* use the same xid.
|
|
*/
|
|
if (nmp->nm_sotype == SOCK_DGRAM) {
|
|
if ((nmp->nm_flag & NFSMNT_SOFT) != 0) {
|
|
/*
|
|
* CLSET_RETRIES is set to 2, so this should be half
|
|
* of the total timeout required.
|
|
*/
|
|
timeo = nmp->nm_retry * nmp->nm_timeo / 2;
|
|
if (timeo < 1)
|
|
timeo = 1;
|
|
timo.tv_sec = timeo / NFS_HZ;
|
|
timo.tv_usec = (timeo % NFS_HZ) * 1000000 / NFS_HZ;
|
|
} else {
|
|
/* For UDP hard mounts, use a large value. */
|
|
timo.tv_sec = NFS_MAXTIMEO / NFS_HZ;
|
|
timo.tv_usec = 0;
|
|
}
|
|
} else {
|
|
timo.tv_sec = nmp->nm_timeo / NFS_HZ;
|
|
timo.tv_usec = (nmp->nm_timeo % NFS_HZ) * 1000000 / NFS_HZ;
|
|
}
|
|
mrep = NULL;
|
|
stat = CLNT_CALL_MBUF(nmp->nm_client, &ext,
|
|
(nmp->nm_flag & NFSMNT_NFSV3) ? procnum : nfsv2_procid[procnum],
|
|
mreq, &mrep, timo);
|
|
|
|
/*
|
|
* If there was a successful reply and a tprintf msg.
|
|
* tprintf a response.
|
|
*/
|
|
if (stat == RPC_SUCCESS)
|
|
error = 0;
|
|
else if (stat == RPC_TIMEDOUT)
|
|
error = ETIMEDOUT;
|
|
else if (stat == RPC_VERSMISMATCH)
|
|
error = EOPNOTSUPP;
|
|
else if (stat == RPC_PROGVERSMISMATCH)
|
|
error = EPROTONOSUPPORT;
|
|
else
|
|
error = EACCES;
|
|
if (error)
|
|
goto nfsmout;
|
|
|
|
KASSERT(mrep != NULL, ("mrep shouldn't be NULL if no error\n"));
|
|
|
|
/*
|
|
* Search for any mbufs that are not a multiple of 4 bytes long
|
|
* or with m_data not longword aligned.
|
|
* These could cause pointer alignment problems, so copy them to
|
|
* well aligned mbufs.
|
|
*/
|
|
error = nfs_realign(&mrep, M_DONTWAIT);
|
|
if (error == ENOMEM) {
|
|
m_freem(mrep);
|
|
AUTH_DESTROY(auth);
|
|
return (error);
|
|
}
|
|
|
|
md = mrep;
|
|
dpos = mtod(mrep, caddr_t);
|
|
tl = nfsm_dissect(u_int32_t *, NFSX_UNSIGNED);
|
|
if (*tl != 0) {
|
|
error = fxdr_unsigned(int, *tl);
|
|
if ((nmp->nm_flag & NFSMNT_NFSV3) &&
|
|
error == NFSERR_TRYLATER) {
|
|
m_freem(mrep);
|
|
error = 0;
|
|
waituntil = time_second + nfs3_jukebox_delay;
|
|
while (time_second < waituntil)
|
|
(void)tsleep(&fake_wchan, PSOCK, "nqnfstry",
|
|
hz);
|
|
goto tryagain;
|
|
}
|
|
/*
|
|
* Make sure NFSERR_RETERR isn't bogusly set by a server
|
|
* such as amd. (No actual NFS error has bit 31 set.)
|
|
*/
|
|
error &= ~NFSERR_RETERR;
|
|
|
|
/*
|
|
* If the File Handle was stale, invalidate the lookup
|
|
* cache, just in case.
|
|
*/
|
|
if (error == ESTALE)
|
|
nfs_purgecache(vp);
|
|
/*
|
|
* Skip wcc data on NFS errors for now. NetApp filers
|
|
* return corrupt postop attrs in the wcc data for NFS
|
|
* err EROFS. Not sure if they could return corrupt
|
|
* postop attrs for others errors.
|
|
*/
|
|
if ((nmp->nm_flag & NFSMNT_NFSV3) &&
|
|
!nfs_skip_wcc_data_onerr) {
|
|
*mrp = mrep;
|
|
*mdp = md;
|
|
*dposp = dpos;
|
|
error |= NFSERR_RETERR;
|
|
} else
|
|
m_freem(mrep);
|
|
goto nfsmout;
|
|
}
|
|
|
|
#ifdef KDTRACE_HOOKS
|
|
if (dtrace_nfsclient_nfs23_done_probe != NULL) {
|
|
uint32_t probe_id;
|
|
int probe_procnum;
|
|
|
|
if (nmp->nm_flag & NFSMNT_NFSV3) {
|
|
probe_id = nfsclient_nfs3_done_probes[procnum];
|
|
probe_procnum = procnum;
|
|
} else {
|
|
probe_id = nfsclient_nfs2_done_probes[procnum];
|
|
probe_procnum = (nmp->nm_flag & NFSMNT_NFSV3) ?
|
|
procnum : nfsv2_procid[procnum];
|
|
}
|
|
if (probe_id != 0)
|
|
(dtrace_nfsclient_nfs23_done_probe)(probe_id, vp,
|
|
mreq, cred, probe_procnum, 0);
|
|
}
|
|
#endif
|
|
m_freem(mreq);
|
|
*mrp = mrep;
|
|
*mdp = md;
|
|
*dposp = dpos;
|
|
AUTH_DESTROY(auth);
|
|
return (0);
|
|
|
|
nfsmout:
|
|
#ifdef KDTRACE_HOOKS
|
|
if (dtrace_nfsclient_nfs23_done_probe != NULL) {
|
|
uint32_t probe_id;
|
|
int probe_procnum;
|
|
|
|
if (nmp->nm_flag & NFSMNT_NFSV3) {
|
|
probe_id = nfsclient_nfs3_done_probes[procnum];
|
|
probe_procnum = procnum;
|
|
} else {
|
|
probe_id = nfsclient_nfs2_done_probes[procnum];
|
|
probe_procnum = (nmp->nm_flag & NFSMNT_NFSV3) ?
|
|
procnum : nfsv2_procid[procnum];
|
|
}
|
|
if (probe_id != 0)
|
|
(dtrace_nfsclient_nfs23_done_probe)(probe_id, vp,
|
|
mreq, cred, probe_procnum, error);
|
|
}
|
|
#endif
|
|
m_freem(mreq);
|
|
if (auth)
|
|
AUTH_DESTROY(auth);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Mark all of an nfs mount's outstanding requests with R_SOFTTERM and
|
|
* wait for all requests to complete. This is used by forced unmounts
|
|
* to terminate any outstanding RPCs.
|
|
*/
|
|
int
|
|
nfs_nmcancelreqs(struct nfsmount *nmp)
|
|
{
|
|
|
|
if (nmp->nm_client)
|
|
CLNT_CLOSE(nmp->nm_client);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Any signal that can interrupt an NFS operation in an intr mount
|
|
* should be added to this set. SIGSTOP and SIGKILL cannot be masked.
|
|
*/
|
|
int nfs_sig_set[] = {
|
|
SIGINT,
|
|
SIGTERM,
|
|
SIGHUP,
|
|
SIGKILL,
|
|
SIGSTOP,
|
|
SIGQUIT
|
|
};
|
|
|
|
/*
|
|
* Check to see if one of the signals in our subset is pending on
|
|
* the process (in an intr mount).
|
|
*/
|
|
static int
|
|
nfs_sig_pending(sigset_t set)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0 ; i < sizeof(nfs_sig_set)/sizeof(int) ; i++)
|
|
if (SIGISMEMBER(set, nfs_sig_set[i]))
|
|
return (1);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* The set/restore sigmask functions are used to (temporarily) overwrite
|
|
* the process p_sigmask during an RPC call (for example). These are also
|
|
* used in other places in the NFS client that might tsleep().
|
|
*/
|
|
void
|
|
nfs_set_sigmask(struct thread *td, sigset_t *oldset)
|
|
{
|
|
sigset_t newset;
|
|
int i;
|
|
struct proc *p;
|
|
|
|
SIGFILLSET(newset);
|
|
if (td == NULL)
|
|
td = curthread; /* XXX */
|
|
p = td->td_proc;
|
|
/* Remove the NFS set of signals from newset. */
|
|
PROC_LOCK(p);
|
|
mtx_lock(&p->p_sigacts->ps_mtx);
|
|
for (i = 0 ; i < sizeof(nfs_sig_set)/sizeof(int) ; i++) {
|
|
/*
|
|
* But make sure we leave the ones already masked
|
|
* by the process, i.e. remove the signal from the
|
|
* temporary signalmask only if it wasn't already
|
|
* in p_sigmask.
|
|
*/
|
|
if (!SIGISMEMBER(td->td_sigmask, nfs_sig_set[i]) &&
|
|
!SIGISMEMBER(p->p_sigacts->ps_sigignore, nfs_sig_set[i]))
|
|
SIGDELSET(newset, nfs_sig_set[i]);
|
|
}
|
|
mtx_unlock(&p->p_sigacts->ps_mtx);
|
|
PROC_UNLOCK(p);
|
|
kern_sigprocmask(td, SIG_SETMASK, &newset, oldset, 0);
|
|
}
|
|
|
|
void
|
|
nfs_restore_sigmask(struct thread *td, sigset_t *set)
|
|
{
|
|
if (td == NULL)
|
|
td = curthread; /* XXX */
|
|
kern_sigprocmask(td, SIG_SETMASK, set, NULL, 0);
|
|
}
|
|
|
|
/*
|
|
* NFS wrapper to msleep(), that shoves a new p_sigmask and restores the
|
|
* old one after msleep() returns.
|
|
*/
|
|
int
|
|
nfs_msleep(struct thread *td, void *ident, struct mtx *mtx, int priority,
|
|
char *wmesg, int timo)
|
|
{
|
|
sigset_t oldset;
|
|
int error;
|
|
struct proc *p;
|
|
|
|
if ((priority & PCATCH) == 0)
|
|
return msleep(ident, mtx, priority, wmesg, timo);
|
|
if (td == NULL)
|
|
td = curthread; /* XXX */
|
|
nfs_set_sigmask(td, &oldset);
|
|
error = msleep(ident, mtx, priority, wmesg, timo);
|
|
nfs_restore_sigmask(td, &oldset);
|
|
p = td->td_proc;
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Test for a termination condition pending on the process.
|
|
* This is used for NFSMNT_INT mounts.
|
|
*/
|
|
int
|
|
nfs_sigintr(struct nfsmount *nmp, struct thread *td)
|
|
{
|
|
struct proc *p;
|
|
sigset_t tmpset;
|
|
|
|
/* Terminate all requests while attempting a forced unmount. */
|
|
if (nmp->nm_mountp->mnt_kern_flag & MNTK_UNMOUNTF)
|
|
return (EIO);
|
|
if (!(nmp->nm_flag & NFSMNT_INT))
|
|
return (0);
|
|
if (td == NULL)
|
|
return (0);
|
|
p = td->td_proc;
|
|
PROC_LOCK(p);
|
|
tmpset = p->p_siglist;
|
|
SIGSETOR(tmpset, td->td_siglist);
|
|
SIGSETNAND(tmpset, td->td_sigmask);
|
|
mtx_lock(&p->p_sigacts->ps_mtx);
|
|
SIGSETNAND(tmpset, p->p_sigacts->ps_sigignore);
|
|
mtx_unlock(&p->p_sigacts->ps_mtx);
|
|
if ((SIGNOTEMPTY(p->p_siglist) || SIGNOTEMPTY(td->td_siglist))
|
|
&& nfs_sig_pending(tmpset)) {
|
|
PROC_UNLOCK(p);
|
|
return (EINTR);
|
|
}
|
|
PROC_UNLOCK(p);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
nfs_msg(struct thread *td, const char *server, const char *msg, int error)
|
|
{
|
|
struct proc *p;
|
|
|
|
p = td ? td->td_proc : NULL;
|
|
if (error)
|
|
tprintf(p, LOG_INFO, "nfs server %s: %s, error %d\n", server,
|
|
msg, error);
|
|
else
|
|
tprintf(p, LOG_INFO, "nfs server %s: %s\n", server, msg);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
nfs_down(struct nfsmount *nmp, struct thread *td, const char *msg,
|
|
int error, int flags)
|
|
{
|
|
if (nmp == NULL)
|
|
return;
|
|
mtx_lock(&nmp->nm_mtx);
|
|
if ((flags & NFSSTA_TIMEO) && !(nmp->nm_state & NFSSTA_TIMEO)) {
|
|
nmp->nm_state |= NFSSTA_TIMEO;
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid,
|
|
VQ_NOTRESP, 0);
|
|
} else
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
mtx_lock(&nmp->nm_mtx);
|
|
if ((flags & NFSSTA_LOCKTIMEO) &&
|
|
!(nmp->nm_state & NFSSTA_LOCKTIMEO)) {
|
|
nmp->nm_state |= NFSSTA_LOCKTIMEO;
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid,
|
|
VQ_NOTRESPLOCK, 0);
|
|
} else
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
nfs_msg(td, nmp->nm_mountp->mnt_stat.f_mntfromname, msg, error);
|
|
}
|
|
|
|
static void
|
|
nfs_up(struct nfsmount *nmp, struct thread *td, const char *msg,
|
|
int flags, int tprintfmsg)
|
|
{
|
|
if (nmp == NULL)
|
|
return;
|
|
if (tprintfmsg)
|
|
nfs_msg(td, nmp->nm_mountp->mnt_stat.f_mntfromname, msg, 0);
|
|
|
|
mtx_lock(&nmp->nm_mtx);
|
|
if ((flags & NFSSTA_TIMEO) && (nmp->nm_state & NFSSTA_TIMEO)) {
|
|
nmp->nm_state &= ~NFSSTA_TIMEO;
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid,
|
|
VQ_NOTRESP, 1);
|
|
} else
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
|
|
mtx_lock(&nmp->nm_mtx);
|
|
if ((flags & NFSSTA_LOCKTIMEO) &&
|
|
(nmp->nm_state & NFSSTA_LOCKTIMEO)) {
|
|
nmp->nm_state &= ~NFSSTA_LOCKTIMEO;
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid,
|
|
VQ_NOTRESPLOCK, 1);
|
|
} else
|
|
mtx_unlock(&nmp->nm_mtx);
|
|
}
|