From 1278d57acdeff88de41d27f66e38de0c1ca01fc6 Mon Sep 17 00:00:00 2001 From: Ian Dowse Date: Wed, 2 Jan 2002 00:41:26 +0000 Subject: [PATCH] Permit NFS filesystems to be forcibly unmounted when the server is down, even if there are hung processes and the mount is non- interruptible. This works by having nfs_unmount call a new function nfs_nmcancelreqs() in the FORCECLOSE case. It scans the list of outstanding requests and marks as interrupted any requests belonging to the specified mount. Then it waits up to 30 seconds for all requests to terminate. A few other changes are necessary to support this: - Unconditionally set a socket timeout so that even hard mounts are guaranteed to occasionally check the R_SOFTTERM flag on requests. For hard mounts this flag can only be set by nfs_nmcancelreqs(). - Reject requests on a mount that is currently being unmounted. - Never grant the receive lock to a request that has been cancelled. This should also avoid an old problem where a forced NFS unmount could cause a crash; it occurred when a VOP on an unlocked vnode (usually VOP_GETATTR) was in progress at the time of the forced unmount. --- sys/nfsclient/nfs.h | 1 + sys/nfsclient/nfs_socket.c | 52 +++++++++++++++++++++++++++++++++----- sys/nfsclient/nfs_vfsops.c | 6 +++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/sys/nfsclient/nfs.h b/sys/nfsclient/nfs.h index bc228ed1d5f7..ec0e6acc9be6 100644 --- a/sys/nfsclient/nfs.h +++ b/sys/nfsclient/nfs.h @@ -276,6 +276,7 @@ int nfs_loadattrcache(struct vnode **, struct mbuf **, caddr_t *, struct vattr *, int); int nfsm_mbuftouio(struct mbuf **, struct uio *, int, caddr_t *); void nfs_nhinit(void); +int nfs_nmcancelreqs(struct nfsmount *); void nfs_timer(void*); int nfs_connect(struct nfsmount *, struct nfsreq *); void nfs_disconnect(struct nfsmount *); diff --git a/sys/nfsclient/nfs_socket.c b/sys/nfsclient/nfs_socket.c index b34f739d28c2..858b14e396e2 100644 --- a/sys/nfsclient/nfs_socket.c +++ b/sys/nfsclient/nfs_socket.c @@ -250,13 +250,8 @@ nfs_connect(struct nfsmount *nmp, struct nfsreq *rep) } splx(s); } - if (nmp->nm_flag & (NFSMNT_SOFT | NFSMNT_INT)) { - so->so_rcv.sb_timeo = (5 * hz); - so->so_snd.sb_timeo = (5 * hz); - } else { - so->so_rcv.sb_timeo = 0; - so->so_snd.sb_timeo = 0; - } + so->so_rcv.sb_timeo = 5 * hz; + so->so_snd.sb_timeo = 5 * hz; /* * Get buffer reservation size from sysctl, but impose reasonable @@ -855,6 +850,11 @@ nfs_request(struct vnode *vp, struct mbuf *mrest, int procnum, int trylater_delay = NQ_TRYLATERDEL, trylater_cnt = 0; u_int32_t xid; + /* Reject requests while attempting to unmount. */ + if (vp->v_mount->mnt_kern_flag & MNTK_UNMOUNT) { + m_freem(mrest); + return (ESTALE); + } nmp = VFSTONFS(vp->v_mount); MALLOC(rep, struct nfsreq *, sizeof(struct nfsreq), M_NFSREQ, M_WAITOK); rep->r_nmp = nmp; @@ -1166,6 +1166,41 @@ nfs_timer(void *arg) nfs_timer_handle = timeout(nfs_timer, (void *)0, nfs_ticks); } +/* + * 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(nmp) + struct nfsmount *nmp; +{ + struct nfsreq *req; + int i, s; + + s = splnet(); + TAILQ_FOREACH(req, &nfs_reqq, r_chain) { + if (nmp != req->r_nmp || req->r_mrep != NULL || + (req->r_flags & R_SOFTTERM)) + continue; + nfs_softterm(req); + } + splx(s); + + for (i = 0; i < 30; i++) { + tsleep(&lbolt, PSOCK, "nfscancel", 0); + s = splnet(); + TAILQ_FOREACH(req, &nfs_reqq, r_chain) { + if (nmp == req->r_nmp) + break; + } + splx(s); + if (req == NULL) + return (0); + } + return (EBUSY); +} + /* * Flag a request as being about to terminate (due to NFSMNT_INT/NFSMNT_SOFT). * The nm_send count is decremented now to avoid deadlocks when the process in @@ -1289,6 +1324,9 @@ nfs_rcvlock(struct nfsreq *rep) slptimeo = 2 * hz; } } + /* Always fail if our request has been cancelled. */ + if (rep != NULL && (rep->r_flags & R_SOFTTERM)) + return (EINTR); *statep |= NFSSTA_RCVLOCK; return (0); } diff --git a/sys/nfsclient/nfs_vfsops.c b/sys/nfsclient/nfs_vfsops.c index 1cd6275b8f47..62d412eb06b6 100644 --- a/sys/nfsclient/nfs_vfsops.c +++ b/sys/nfsclient/nfs_vfsops.c @@ -918,6 +918,12 @@ nfs_unmount(struct mount *mp, int mntflags, struct thread *td) * - Close the socket * - Free up the data structures */ + /* In the forced case, cancel any outstanding requests. */ + if (flags & FORCECLOSE) { + error = nfs_nmcancelreqs(nmp); + if (error) + return (error); + } /* We hold 1 extra ref on the root vnode; see comment in mountnfs(). */ error = vflush(mp, 1, flags); if (error)