From 47cbff34fa9a88fc8c12c4de15882a0563a33055 Mon Sep 17 00:00:00 2001 From: Rick Macklem Date: Sat, 29 Jul 2017 19:52:47 +0000 Subject: [PATCH] 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 --- sys/fs/nfs/nfscl.h | 3 +- sys/fs/nfsclient/nfs_clport.c | 52 +++++++++++++++++++++++++++++++++ sys/fs/nfsclient/nfs_clvfsops.c | 18 ++++++++++++ sys/fs/nfsclient/nfsmount.h | 5 ++++ sys/nfs/nfs_nfssvc.c | 2 +- sys/nfs/nfssvc.h | 1 + sys/sys/param.h | 2 +- 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,