aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRick Macklem <rmacklem@FreeBSD.org>2017-07-29 19:52:47 +0000
committerRick Macklem <rmacklem@FreeBSD.org>2017-07-29 19:52:47 +0000
commit47cbff34fa9a88fc8c12c4de15882a0563a33055 (patch)
treeaa3367f7e70e9c66d04fd0ade1463d1b6ee266a1
parentd35f6548e63e5deb4e9a7ac40f517fc10b1dc7b9 (diff)
downloadsrc-47cbff34fa9a88fc8c12c4de15882a0563a33055.tar.gz
src-47cbff34fa9a88fc8c12c4de15882a0563a33055.zip
Add kernel support for the NFS client forced dismount "umount -N" option.
When an NFS mount is hung against an unresponsive NFS server, the "umount -f" option can be used to dismount the mount. Unfortunately, "umount -f" gets hung as well if a "umount" without "-f" has already been done. Usually, this is because of a vnode lock being held by the "umount" for the mounted-on vnode. This patch adds kernel code so that a new "-N" option can be added to "umount", allowing it to avoid getting hung for this case. It adds two flags. One indicates that a forced dismount is about to happen and the other is used, along with setting mnt_data == NULL, to handshake with the nfs_unmount() VFS call. It includes a slight change to the interface used between the client and common NFS modules, so I bumped __FreeBSD_version to ensure both modules are rebuilt. Tested by: pho Reviewed by: kib MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D11735
Notes
Notes: svn path=/head/; revision=321688
-rw-r--r--sys/fs/nfs/nfscl.h3
-rw-r--r--sys/fs/nfsclient/nfs_clport.c52
-rw-r--r--sys/fs/nfsclient/nfs_clvfsops.c18
-rw-r--r--sys/fs/nfsclient/nfsmount.h5
-rw-r--r--sys/nfs/nfs_nfssvc.c2
-rw-r--r--sys/nfs/nfssvc.h1
-rw-r--r--sys/sys/param.h2
7 files changed, 80 insertions, 3 deletions
diff --git a/sys/fs/nfs/nfscl.h b/sys/fs/nfs/nfscl.h
index fd893153ddcc..903efb2e468e 100644
--- a/sys/fs/nfs/nfscl.h
+++ b/sys/fs/nfs/nfscl.h
@@ -60,7 +60,8 @@ struct nfsv4node {
#define NFSCL_LEASE(r) ((r) * 2)
/* This macro checks to see if a forced dismount is about to occur. */
-#define NFSCL_FORCEDISM(m) (((m)->mnt_kern_flag & MNTK_UNMOUNTF) != 0)
+#define NFSCL_FORCEDISM(m) (((m)->mnt_kern_flag & MNTK_UNMOUNTF) != 0 || \
+ (VFSTONFS(m)->nm_privflag & NFSMNTP_FORCEDISM) != 0)
/*
* These flag bits are used for the argument to nfscl_fillsattr() to
diff --git a/sys/fs/nfsclient/nfs_clport.c b/sys/fs/nfsclient/nfs_clport.c
index e49382ea6783..c22d87227b2f 100644
--- a/sys/fs/nfsclient/nfs_clport.c
+++ b/sys/fs/nfsclient/nfs_clport.c
@@ -1311,6 +1311,8 @@ nfssvc_nfscl(struct thread *td, struct nfssvc_args *uap)
cap_rights_t rights;
char *buf;
int error;
+ struct mount *mp;
+ struct nfsmount *nmp;
if (uap->flag & NFSSVC_CBADDSOCK) {
error = copyin(uap->argp, (caddr_t)&nfscbdarg, sizeof(nfscbdarg));
@@ -1365,6 +1367,56 @@ nfssvc_nfscl(struct thread *td, struct nfssvc_args *uap)
dumpmntopts.ndmnt_blen);
free(buf, M_TEMP);
}
+ } else if (uap->flag & NFSSVC_FORCEDISM) {
+ buf = malloc(MNAMELEN + 1, M_TEMP, M_WAITOK);
+ error = copyinstr(uap->argp, buf, MNAMELEN + 1, NULL);
+ if (error == 0) {
+ nmp = NULL;
+ mtx_lock(&mountlist_mtx);
+ TAILQ_FOREACH(mp, &mountlist, mnt_list) {
+ if (strcmp(mp->mnt_stat.f_mntonname, buf) ==
+ 0 && strcmp(mp->mnt_stat.f_fstypename,
+ "nfs") == 0 && mp->mnt_data != NULL) {
+ nmp = VFSTONFS(mp);
+ mtx_lock(&nmp->nm_mtx);
+ if ((nmp->nm_privflag &
+ NFSMNTP_FORCEDISM) == 0) {
+ nmp->nm_privflag |=
+ (NFSMNTP_FORCEDISM |
+ NFSMNTP_CANCELRPCS);
+ mtx_unlock(&nmp->nm_mtx);
+ } else {
+ nmp = NULL;
+ mtx_unlock(&nmp->nm_mtx);
+ }
+ break;
+ }
+ }
+ mtx_unlock(&mountlist_mtx);
+
+ if (nmp != NULL) {
+ /*
+ * Call newnfs_nmcancelreqs() to cause
+ * any RPCs in progress on the mount point to
+ * fail.
+ * This will cause any process waiting for an
+ * RPC to complete while holding a vnode lock
+ * on the mounted-on vnode (such as "df" or
+ * a non-forced "umount") to fail.
+ * This will unlock the mounted-on vnode so
+ * a forced dismount can succeed.
+ * Then clear NFSMNTP_CANCELRPCS and wakeup(),
+ * so that nfs_unmount() can complete.
+ */
+ newnfs_nmcancelreqs(nmp);
+ mtx_lock(&nmp->nm_mtx);
+ nmp->nm_privflag &= ~NFSMNTP_CANCELRPCS;
+ wakeup(nmp);
+ mtx_unlock(&nmp->nm_mtx);
+ } else
+ error = EINVAL;
+ }
+ free(buf, M_TEMP);
} else {
error = EINVAL;
}
diff --git a/sys/fs/nfsclient/nfs_clvfsops.c b/sys/fs/nfsclient/nfs_clvfsops.c
index ba65c008fbc6..fc22c8d7e8f2 100644
--- a/sys/fs/nfsclient/nfs_clvfsops.c
+++ b/sys/fs/nfsclient/nfs_clvfsops.c
@@ -1698,6 +1698,11 @@ nfs_unmount(struct mount *mp, int mntflags)
*/
if ((mntflags & MNT_FORCE) == 0)
nfscl_umount(nmp, td);
+ else {
+ mtx_lock(&nmp->nm_mtx);
+ nmp->nm_privflag |= NFSMNTP_FORCEDISM;
+ mtx_unlock(&nmp->nm_mtx);
+ }
/* Make sure no nfsiods are assigned to this mount. */
mtx_lock(&ncl_iod_mutex);
for (i = 0; i < NFS_MAXASYNCDAEMON; i++)
@@ -1706,6 +1711,19 @@ nfs_unmount(struct mount *mp, int mntflags)
ncl_iodmount[i] = NULL;
}
mtx_unlock(&ncl_iod_mutex);
+
+ /*
+ * We can now set mnt_data to NULL and wait for
+ * nfssvc(NFSSVC_FORCEDISM) to complete.
+ */
+ mtx_lock(&mountlist_mtx);
+ mtx_lock(&nmp->nm_mtx);
+ mp->mnt_data = NULL;
+ mtx_unlock(&mountlist_mtx);
+ while ((nmp->nm_privflag & NFSMNTP_CANCELRPCS) != 0)
+ msleep(nmp, &nmp->nm_mtx, PVFS, "nfsfdism", 0);
+ mtx_unlock(&nmp->nm_mtx);
+
newnfs_disconnect(&nmp->nm_sockreq);
crfree(nmp->nm_sockreq.nr_cred);
FREE(nmp->nm_nam, M_SONAME);
diff --git a/sys/fs/nfsclient/nfsmount.h b/sys/fs/nfsclient/nfsmount.h
index aa544ac39a59..7f1422a2278f 100644
--- a/sys/fs/nfsclient/nfsmount.h
+++ b/sys/fs/nfsclient/nfsmount.h
@@ -44,6 +44,7 @@
*/
struct nfsmount {
struct nfsmount_common nm_com; /* Common fields for nlm */
+ uint32_t nm_privflag; /* Private flags */
int nm_numgrps; /* Max. size of groupslist */
u_char nm_fh[NFSX_FHMAX]; /* File handle of root dir */
int nm_fhsize; /* Size of root file handle */
@@ -99,6 +100,10 @@ struct nfsmount {
#define nm_getinfo nm_com.nmcom_getinfo
#define nm_vinvalbuf nm_com.nmcom_vinvalbuf
+/* Private flags. */
+#define NFSMNTP_FORCEDISM 0x00000001
+#define NFSMNTP_CANCELRPCS 0x00000002
+
#define NFSMNT_DIRPATH(m) (&((m)->nm_name[(m)->nm_krbnamelen + 1]))
#define NFSMNT_SRVKRBNAME(m) \
(&((m)->nm_name[(m)->nm_krbnamelen + (m)->nm_dirpathlen + 2]))
diff --git a/sys/nfs/nfs_nfssvc.c b/sys/nfs/nfs_nfssvc.c
index 1f2cdaf3f9d8..8c49a61f808c 100644
--- a/sys/nfs/nfs_nfssvc.c
+++ b/sys/nfs/nfs_nfssvc.c
@@ -92,7 +92,7 @@ sys_nfssvc(struct thread *td, struct nfssvc_args *uap)
nfsd_call_nfsserver != NULL)
error = (*nfsd_call_nfsserver)(td, uap);
else if ((uap->flag & (NFSSVC_CBADDSOCK | NFSSVC_NFSCBD |
- NFSSVC_DUMPMNTOPTS)) && nfsd_call_nfscl != NULL)
+ NFSSVC_DUMPMNTOPTS | NFSSVC_FORCEDISM)) && nfsd_call_nfscl != NULL)
error = (*nfsd_call_nfscl)(td, uap);
else if ((uap->flag & (NFSSVC_IDNAME | NFSSVC_GETSTATS |
NFSSVC_GSSDADDPORT | NFSSVC_GSSDADDFIRST | NFSSVC_GSSDDELETEALL |
diff --git a/sys/nfs/nfssvc.h b/sys/nfs/nfssvc.h
index 4b51f7b02719..61ed1a3e7a43 100644
--- a/sys/nfs/nfssvc.h
+++ b/sys/nfs/nfssvc.h
@@ -70,6 +70,7 @@
#define NFSSVC_RESUMENFSD 0x08000000
#define NFSSVC_DUMPMNTOPTS 0x10000000
#define NFSSVC_NEWSTRUCT 0x20000000
+#define NFSSVC_FORCEDISM 0x40000000
/* Argument structure for NFSSVC_DUMPMNTOPTS. */
struct nfscl_dumpmntopts {
diff --git a/sys/sys/param.h b/sys/sys/param.h
index ecd5c2c3f9b1..8e0ba789eb3a 100644
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -58,7 +58,7 @@
* in the range 5 to 9.
*/
#undef __FreeBSD_version
-#define __FreeBSD_version 1200039 /* Master, propagated to newvers */
+#define __FreeBSD_version 1200040 /* Master, propagated to newvers */
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,