Blob Blame History Raw
diff --git a/.gitignore b/.gitignore
index 632609e..4bff9e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,10 +55,15 @@ support/export/mount.h
 support/export/mount_clnt.c
 support/export/mount_xdr.c
 support/include/mount.h
-utils/statd/sm_inter.h
-utils/statd/sm_inter_clnt.c
-utils/statd/sm_inter_svc.c
-utils/statd/sm_inter_xdr.c
+support/nsm/sm_inter.h
+support/nsm/sm_inter_clnt.c
+support/nsm/sm_inter_svc.c
+support/nsm/sm_inter_xdr.c
+support/include/sm_inter.h
+tests/nsm_client/nlm_sm_inter.h
+tests/nsm_client/nlm_sm_inter_clnt.c
+tests/nsm_client/nlm_sm_inter_svc.c
+tests/nsm_client/nlm_sm_inter_xdr.c
 # cscope database files
 cscope.*
 # generic editor backup et al
diff --git a/Makefile.am b/Makefile.am
index b3a6e91..ae7cd16 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,7 @@
 
 AUTOMAKE_OPTIONS = foreign
 
-SUBDIRS = tools support utils linux-nfs
+SUBDIRS = tools support utils linux-nfs tests
 
 MAINTAINERCLEANFILES = Makefile.in
 
diff --git a/aclocal/ipv6.m4 b/aclocal/ipv6.m4
index 2490f3d..5ee8fb6 100644
--- a/aclocal/ipv6.m4
+++ b/aclocal/ipv6.m4
@@ -15,8 +15,8 @@ AC_DEFUN([AC_IPV6], [
     fi
 
     dnl IPv6-enabled networking functions required for IPv6
-    AC_CHECK_FUNCS([getnameinfo bindresvport_sa], ,
-                   [AC_MSG_ERROR([Missing functions needed for IPv6.])])
+    AC_CHECK_FUNCS([getifaddrs getnameinfo bindresvport_sa], ,
+                   [AC_MSG_ERROR([Missing library functions needed for IPv6.])])
 
     dnl Need to detect presence of IPv6 networking at run time via
     dnl getaddrinfo(3); old versions of glibc do not support ADDRCONFIG
diff --git a/aclocal/libcap.m4 b/aclocal/libcap.m4
new file mode 100644
index 0000000..eabe507
--- /dev/null
+++ b/aclocal/libcap.m4
@@ -0,0 +1,15 @@
+dnl Checks for libcap.so
+dnl
+AC_DEFUN([AC_LIBCAP], [
+
+  dnl look for prctl
+  AC_CHECK_FUNC([prctl], , )
+
+  dnl look for the library; do not add to LIBS if found
+  AC_CHECK_LIB([cap], [cap_get_proc], [LIBCAP=-lcap], ,)
+  AC_SUBST(LIBCAP)
+
+  AC_CHECK_HEADERS([sys/capability.h], ,
+                   [AC_MSG_ERROR([libcap headers not found.])])
+
+])dnl
diff --git a/configure.ac b/configure.ac
index 3ad415c..1dc4249 100644
--- a/configure.ac
+++ b/configure.ac
@@ -166,6 +166,9 @@ fi
 dnl Check for TI-RPC library and headers
 AC_LIBTIRPC
 
+dnl Check for -lcap
+AC_LIBCAP
+
 # Check whether user wants TCP wrappers support
 AC_TCP_WRAPPERS
 
@@ -327,7 +330,7 @@ AC_FUNC_STAT
 AC_FUNC_VPRINTF
 AC_CHECK_FUNCS([alarm atexit dup2 fdatasync ftruncate getcwd \
                gethostbyaddr gethostbyname gethostname getmntent \
-               getnameinfo getrpcbyname \
+               getnameinfo getrpcbyname getifaddrs \
                gettimeofday hasmntopt inet_ntoa innetgr memset mkdir pathconf \
                realpath rmdir select socket strcasecmp strchr strdup \
                strerror strrchr strtol strtoul sigprocmask])
@@ -402,6 +405,7 @@ AC_CONFIG_FILES([
 	support/include/Makefile
 	support/misc/Makefile
 	support/nfs/Makefile
+	support/nsm/Makefile
 	tools/Makefile
 	tools/locktest/Makefile
 	tools/nlmtest/Makefile
@@ -416,6 +420,8 @@ AC_CONFIG_FILES([
 	utils/nfsd/Makefile
 	utils/nfsstat/Makefile
 	utils/showmount/Makefile
-	utils/statd/Makefile])
+	utils/statd/Makefile
+	tests/Makefile
+	tests/nsm_client/Makefile])
 AC_OUTPUT
 
diff --git a/support/Makefile.am b/support/Makefile.am
index aa4d692..cb37733 100644
--- a/support/Makefile.am
+++ b/support/Makefile.am
@@ -1,6 +1,6 @@
 ## Process this file with automake to produce Makefile.in
 
-SUBDIRS = export include misc nfs
+SUBDIRS = export include misc nfs nsm
 
 MAINTAINERCLEANFILES = Makefile.in
 
diff --git a/support/export/client.c b/support/export/client.c
index 5fcf355..6236561 100644
--- a/support/export/client.c
+++ b/support/export/client.c
@@ -297,7 +297,7 @@ name_cmp(char *a, char *b)
 	/* compare strings a and b, but only upto ',' in a */
 	while (*a && *b && *a != ',' && *a == *b)
 		a++, b++;
-	if (!*b && (!*a || !a == ',') )
+	if (!*b && (!*a || *a == ','))
 		return 0;
 	if (!*b) return 1;
 	if (!*a || *a == ',') return -1;
diff --git a/support/export/export.c b/support/export/export.c
index e5e6cb0..2943466 100644
--- a/support/export/export.c
+++ b/support/export/export.c
@@ -28,6 +28,22 @@ static int	export_check(nfs_export *, struct hostent *, char *);
 static nfs_export *
 		export_allowed_internal(struct hostent *hp, char *path);
 
+static void warn_duplicated_exports(nfs_export *exp, struct exportent *eep)
+{
+	if (exp->m_export.e_flags != eep->e_flags) {
+		xlog(L_ERROR, "incompatible duplicated export entries:");
+		xlog(L_ERROR, "\t%s:%s (0x%x) [IGNORED]", eep->e_hostname,
+				eep->e_path, eep->e_flags);
+		xlog(L_ERROR, "\t%s:%s (0x%x)", exp->m_export.e_hostname,
+				exp->m_export.e_path, exp->m_export.e_flags);
+	} else {
+		xlog(L_ERROR, "duplicated export entries:");
+		xlog(L_ERROR, "\t%s:%s", eep->e_hostname, eep->e_path);
+		xlog(L_ERROR, "\t%s:%s", exp->m_export.e_hostname,
+				exp->m_export.e_path);
+	}
+}
+
 int
 export_read(char *fname)
 {
@@ -36,27 +52,13 @@ export_read(char *fname)
 
 	setexportent(fname, "r");
 	while ((eep = getexportent(0,1)) != NULL) {
-	  exp = export_lookup(eep->e_hostname, eep->e_path, 0);
-	  if (!exp)
-	    export_create(eep,0);
-	  else {
-	    if (exp->m_export.e_flags != eep->e_flags) {
-	      xlog(L_ERROR, "incompatible duplicated export entries:");
-	      xlog(L_ERROR, "\t%s:%s (0x%x) [IGNORED]", eep->e_hostname,
-		   eep->e_path, eep->e_flags);
-	      xlog(L_ERROR, "\t%s:%s (0x%x)", exp->m_export.e_hostname,
-		   exp->m_export.e_path, exp->m_export.e_flags);
-	    }
-	    else {
-	      xlog(L_ERROR, "duplicated export entries:");
-	      xlog(L_ERROR, "\t%s:%s", eep->e_hostname, eep->e_path);
-	      xlog(L_ERROR, "\t%s:%s", exp->m_export.e_hostname,
-		   exp->m_export.e_path);
-	    }
-	  }
+		exp = export_lookup(eep->e_hostname, eep->e_path, 0);
+		if (!exp)
+			export_create(eep, 0);
+		else
+			warn_duplicated_exports(exp, eep);
 	}
 	endexportent();
-
 	return 0;
 }
 
diff --git a/support/export/xtab.c b/support/export/xtab.c
index 3b1dcce..2a43193 100644
--- a/support/export/xtab.c
+++ b/support/export/xtab.c
@@ -19,7 +19,9 @@
 #include "exportfs.h"
 #include "xio.h"
 #include "xlog.h"
+#include "v4root.h"
 
+int v4root_needed;
 static void cond_rename(char *newfile, char *oldfile);
 
 static int
@@ -36,6 +38,8 @@ xtab_read(char *xtab, char *lockfn, int is_export)
 	if ((lockid = xflock(lockfn, "r")) < 0)
 		return 0;
 	setexportent(xtab, "r");
+	if (is_export == 1)
+		v4root_needed = 1;
 	while ((xp = getexportent(is_export==0, 0)) != NULL) {
 		if (!(exp = export_lookup(xp->e_hostname, xp->e_path, is_export != 1)) &&
 		    !(exp = export_create(xp, is_export!=1))) {
@@ -48,6 +52,8 @@ xtab_read(char *xtab, char *lockfn, int is_export)
 		case 1:
 			exp->m_xtabent = 1;
 			exp->m_mayexport = 1;
+			if ((xp->e_flags & NFSEXP_FSID) && xp->e_fsid == 0)
+				v4root_needed = 0;
 			break;
 		case 2:
 			exp->m_exported = -1;/* may be exported */
diff --git a/support/include/Makefile.am b/support/include/Makefile.am
index f5a77ec..4b33ee9 100644
--- a/support/include/Makefile.am
+++ b/support/include/Makefile.am
@@ -9,6 +9,8 @@ noinst_HEADERS = \
 	nfs_mntent.h \
 	nfs_paths.h \
 	nfslib.h \
+	nfsrpc.h \
+	nsm.h \
 	rpcmisc.h \
 	tcpwrapper.h \
 	xio.h \
diff --git a/support/include/exportfs.h b/support/include/exportfs.h
index a5cf482..470b2ec 100644
--- a/support/include/exportfs.h
+++ b/support/include/exportfs.h
@@ -99,10 +99,19 @@ int				xtab_mount_write(void);
 int				xtab_export_write(void);
 void				xtab_append(nfs_export *);
 
+int				secinfo_addflavor(struct flav_info *, struct exportent *);
+
 int				rmtab_read(void);
 
 struct nfskey *			key_lookup(char *hname);
 
+struct export_features {
+	unsigned int flags;
+	unsigned int secinfo_flags;
+};
+
+struct export_features *get_export_features(void);
+
 /* Record export error.  */
 extern int export_errno;
 
diff --git a/support/include/ha-callout.h b/support/include/ha-callout.h
index efb70fb..1164336 100644
--- a/support/include/ha-callout.h
+++ b/support/include/ha-callout.h
@@ -53,11 +53,7 @@ ha_callout(char *event, char *arg1, char *arg2, int arg3)
 		default: pid = waitpid(pid, &ret, 0);
   	}
 	sigaction(SIGCHLD, &oldact, &newact);
-#ifdef dprintf
-	dprintf(N_DEBUG, "ha callout returned %d\n", WEXITSTATUS(ret));
-#else
 	xlog(D_GENERAL, "ha callout returned %d\n", WEXITSTATUS(ret));
-#endif
 }
 
 #endif
diff --git a/support/include/nfs/export.h b/support/include/nfs/export.h
index f7a99ba..1547a87 100644
--- a/support/include/nfs/export.h
+++ b/support/include/nfs/export.h
@@ -24,6 +24,17 @@
 #define NFSEXP_FSID		0x2000
 #define	NFSEXP_CROSSMOUNT	0x4000
 #define NFSEXP_NOACL		0x8000 /* reserved for possible ACL related use */
-#define NFSEXP_ALLFLAGS		0xFFFF
+#define NFSEXP_V4ROOT		0x10000
+/*
+ * All flags supported by the kernel before addition of the
+ * export_features interface:
+ */
+#define NFSEXP_OLDFLAGS		0x7E3F
+/*
+ * Flags that can vary per flavor, for kernels before addition of the
+ * export_features interface:
+ */
+#define NFSEXP_OLD_SECINFO_FLAGS (NFSEXP_READONLY | NFSEXP_ROOTSQUASH \
+					| NFSEXP_ALLSQUASH)
 
 #endif /* _NSF_EXPORT_H */
diff --git a/support/include/nfsrpc.h b/support/include/nfsrpc.h
index dff6af7..4db35ab 100644
--- a/support/include/nfsrpc.h
+++ b/support/include/nfsrpc.h
@@ -59,16 +59,6 @@ static inline void nfs_clear_rpc_createerr(void)
 }
 
 /*
- * Extract port value from a socket address
- */
-extern uint16_t		nfs_get_port(const struct sockaddr *);
-
-/*
- * Set port value in a socket address
- */
-extern void		nfs_set_port(struct sockaddr *, const uint16_t);
-
-/*
  * Look up an RPC program name in /etc/rpc
  */
 extern rpcprog_t	nfs_getrpcbyname(const rpcprog_t, const char *table[]);
@@ -90,6 +80,18 @@ extern CLIENT		*nfs_get_priv_rpcclient( const struct sockaddr *,
 				struct timeval *);
 
 /*
+ * Convert a netid to a protocol number and protocol family
+ */
+extern int		nfs_get_proto(const char *netid, sa_family_t *family,
+				unsigned long *protocol);
+
+/*
+ * Convert a protocol family and protocol name to a netid
+ */
+extern char		*nfs_get_netid(const sa_family_t family,
+				const unsigned long protocol);
+
+/*
  * Convert a socket address to a universal address
  */
 extern char		*nfs_sockaddr2universal(const struct sockaddr *);
@@ -158,4 +160,4 @@ extern int		nfs_rpc_ping(const struct sockaddr *sap,
 				const unsigned short protocol,
 				const struct timeval *timeout);
 
-#endif	/* __NFS_UTILS_NFSRPC_H */
+#endif	/* !__NFS_UTILS_NFSRPC_H */
diff --git a/support/include/nsm.h b/support/include/nsm.h
new file mode 100644
index 0000000..fb4d823
--- /dev/null
+++ b/support/include/nsm.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2009 Oracle.  All rights reserved.
+ *
+ * This file is part of nfs-utils.
+ *
+ * nfs-utils is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * nfs-utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * NSM for Linux.
+ */
+
+#ifndef NFS_UTILS_SUPPORT_NSM_H
+#define NFS_UTILS_SUPPORT_NSM_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdbool.h>
+
+#include <netdb.h>
+#include <time.h>
+
+#include "sm_inter.h"
+
+typedef unsigned int
+		(*nsm_populate_t)(const char *hostname,
+				const struct sockaddr *sap,
+				const struct mon *mon,
+				const time_t timestamp);
+
+/* file.c */
+
+extern _Bool	nsm_setup_pathnames(const char *progname,
+				const char *parentdir);
+extern _Bool	nsm_is_default_parentdir(void);
+extern _Bool	nsm_drop_privileges(const int pidfd);
+
+extern int	nsm_get_state(_Bool update);
+extern void	nsm_update_kernel_state(const int state);
+
+extern unsigned int
+		nsm_retire_monitored_hosts(void);
+extern unsigned int
+		nsm_load_monitor_list(nsm_populate_t func);
+extern unsigned int
+		nsm_load_notify_list(nsm_populate_t func);
+
+extern _Bool	nsm_insert_monitored_host(const char *hostname,
+			const struct sockaddr *sap, const struct mon *m);
+extern void	nsm_delete_monitored_host(const char *hostname,
+			const char *mon_name, const char *my_name);
+extern void	nsm_delete_notified_host(const char *hostname,
+			const char *mon_name, const char *my_name);
+extern size_t	nsm_priv_to_hex(const char *priv, char *buf,
+				const size_t buflen);
+
+/* rpc.c */
+
+#define NSM_MAXMSGSIZE	(2048u)
+
+extern uint32_t nsm_xmit_getport(const int sock,
+			const struct sockaddr_in *sin,
+			const unsigned long program,
+			const unsigned long version);
+extern uint32_t nsm_xmit_getaddr(const int sock,
+			const struct sockaddr_in6 *sin6,
+			const rpcprog_t program, const rpcvers_t version);
+extern uint32_t nsm_xmit_rpcbind(const int sock, const struct sockaddr *sap,
+			const rpcprog_t program, const rpcvers_t version);
+extern uint32_t nsm_xmit_notify(const int sock, const struct sockaddr *sap,
+			const socklen_t salen, const rpcprog_t program,
+			const char *mon_name, const int state);
+extern uint32_t nsm_xmit_nlmcall(const int sock, const struct sockaddr *sap,
+			const socklen_t salen, const struct mon *m,
+			const int state);
+extern uint32_t nsm_parse_reply(XDR *xdrs);
+extern unsigned long
+		nsm_recv_getport(XDR *xdrs);
+extern uint16_t nsm_recv_getaddr(XDR *xdrs);
+extern uint16_t nsm_recv_rpcbind(const sa_family_t family, XDR *xdrs);
+
+#endif	/* !NFS_UTILS_SUPPORT_NSM_H */
diff --git a/support/include/rpcmisc.h b/support/include/rpcmisc.h
index f551a85..1b8f411 100644
--- a/support/include/rpcmisc.h
+++ b/support/include/rpcmisc.h
@@ -41,7 +41,12 @@ struct rpc_dtable {
 		(xdrproc_t)xdr_##res_type, sizeof(res_type), \
 	}
 
-
+void		nfs_svc_unregister(const rpcprog_t program,
+				const rpcvers_t version);
+unsigned int	nfs_svc_create(char *name, const rpcprog_t program,
+				const rpcvers_t version,
+				void (*dispatch)(struct svc_req *, SVCXPRT *),
+				const uint16_t port);
 void		rpc_init(char *name, int prog, int vers,
 				void (*dispatch)(struct svc_req *, SVCXPRT *),
 				int defport);
diff --git a/support/include/sockaddr.h b/support/include/sockaddr.h
new file mode 100644
index 0000000..732514b
--- /dev/null
+++ b/support/include/sockaddr.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2009 Oracle.  All rights reserved.
+ *
+ * This file is part of nfs-utils.
+ *
+ * nfs-utils is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * nfs-utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFS_UTILS_SOCKADDR_H
+#define NFS_UTILS_SOCKADDR_H
+
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+/*
+ * This type is for defining buffers that contain network socket
+ * addresses.
+ *
+ * Casting a "struct sockaddr *" to the address of a "struct
+ * sockaddr_storage" breaks C aliasing rules.  The "union
+ * nfs_sockaddr" type follows C aliasing rules yet specifically
+ * allows converting pointers to it between "struct sockaddr *"
+ * and a few other network sockaddr-related pointer types.
+ *
+ * Note that this union is much smaller than a sockaddr_storage.
+ * It should be used only for AF_INET or AF_INET6 socket addresses.
+ * An AF_LOCAL sockaddr_un, for example, will clearly not fit into
+ * a buffer of this type.
+ */
+union nfs_sockaddr {
+	struct sockaddr		sa;
+	struct sockaddr_in	s4;
+	struct sockaddr_in6	s6;
+};
+
+#if SIZEOF_SOCKLEN_T - 0 == 0
+#define socklen_t unsigned int
+#endif
+
+#define SIZEOF_SOCKADDR_UNKNOWN	(socklen_t)0
+#define SIZEOF_SOCKADDR_IN	(socklen_t)sizeof(struct sockaddr_in)
+
+#ifdef IPV6_SUPPORTED
+#define SIZEOF_SOCKADDR_IN6	(socklen_t)sizeof(struct sockaddr_in6)
+#else	/* !IPV6_SUPPORTED */
+#define SIZEOF_SOCKADDR_IN6	SIZEOF_SOCKADDR_UNKNOWN
+#endif	/* !IPV6_SUPPORTED */
+
+/**
+ * nfs_sockaddr_length - return the size in bytes of a socket address
+ * @sap: pointer to socket address
+ *
+ * Returns the size in bytes of @sap, or zero if the family is
+ * not recognized.
+ */
+static inline socklen_t
+nfs_sockaddr_length(const struct sockaddr *sap)
+{
+	switch (sap->sa_family) {
+	case AF_INET:
+		return SIZEOF_SOCKADDR_IN;
+	case AF_INET6:
+		return SIZEOF_SOCKADDR_IN6;
+	}
+	return SIZEOF_SOCKADDR_UNKNOWN;
+}
+
+static inline uint16_t
+get_port4(const struct sockaddr *sap)
+{
+	const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
+	return ntohs(sin->sin_port);
+}
+
+#ifdef IPV6_SUPPORTED
+static inline uint16_t
+get_port6(const struct sockaddr *sap)
+{
+	const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap;
+	return ntohs(sin6->sin6_port);
+}
+#else	/* !IPV6_SUPPORTED */
+static inline uint16_t
+get_port6(__attribute__ ((unused)) const struct sockaddr *sap)
+{
+	return 0;
+}
+#endif	/* !IPV6_SUPPORTED */
+
+/**
+ * nfs_get_port - extract port value from a socket address
+ * @sap: pointer to socket address
+ *
+ * Returns port value in host byte order, or zero if the
+ * socket address contains an unrecognized family.
+ */
+static inline uint16_t
+nfs_get_port(const struct sockaddr *sap)
+{
+	switch (sap->sa_family) {
+	case AF_INET:
+		return get_port4(sap);
+	case AF_INET6:
+		return get_port6(sap);
+	}
+	return 0;
+}
+
+static inline void
+set_port4(struct sockaddr *sap, const uint16_t port)
+{
+	struct sockaddr_in *sin = (struct sockaddr_in *)sap;
+	sin->sin_port = htons(port);
+}
+
+#ifdef IPV6_SUPPORTED
+static inline void
+set_port6(struct sockaddr *sap, const uint16_t port)
+{
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
+	sin6->sin6_port = htons(port);
+}
+#else	/* !IPV6_SUPPORTED */
+static inline void
+set_port6(__attribute__ ((unused)) struct sockaddr *sap,
+		__attribute__ ((unused)) const uint16_t port)
+{
+}
+#endif	/* !IPV6_SUPPORTED */
+
+/**
+ * nfs_set_port - set port value in a socket address
+ * @sap: pointer to socket address
+ * @port: port value to set
+ *
+ */
+static inline void
+nfs_set_port(struct sockaddr *sap, const uint16_t port)
+{
+	switch (sap->sa_family) {
+	case AF_INET:
+		set_port4(sap, port);
+		break;
+	case AF_INET6:
+		set_port6(sap, port);
+		break;
+	}
+}
+
+/**
+ * nfs_is_v4_loopback - test to see if socket address is AF_INET loopback
+ * @sap: pointer to socket address
+ *
+ * Returns true if the socket address is the standard IPv4 loopback
+ * address; otherwise false is returned.
+ */
+static inline _Bool
+nfs_is_v4_loopback(const struct sockaddr *sap)
+{
+	const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
+
+	if (sin->sin_family != AF_INET)
+		return false;
+	if (sin->sin_addr.s_addr != htonl(INADDR_LOOPBACK))
+		return false;
+        return true;
+}
+
+static inline _Bool
+compare_sockaddr4(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+	const struct sockaddr_in *sin1 = (const struct sockaddr_in *)sa1;
+	const struct sockaddr_in *sin2 = (const struct sockaddr_in *)sa2;
+	return sin1->sin_addr.s_addr == sin2->sin_addr.s_addr;
+}
+
+#ifdef IPV6_SUPPORTED
+static inline _Bool
+compare_sockaddr6(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+	const struct sockaddr_in6 *sin1 = (const struct sockaddr_in6 *)sa1;
+	const struct sockaddr_in6 *sin2 = (const struct sockaddr_in6 *)sa2;
+
+	if ((IN6_IS_ADDR_LINKLOCAL((char *)&sin1->sin6_addr) &&
+	     IN6_IS_ADDR_LINKLOCAL((char *)&sin2->sin6_addr)) ||
+	    (IN6_IS_ADDR_SITELOCAL((char *)&sin1->sin6_addr) &&
+	     IN6_IS_ADDR_SITELOCAL((char *)&sin2->sin6_addr)))
+		if (sin1->sin6_scope_id != sin2->sin6_scope_id)
+			return false;
+
+	return IN6_ARE_ADDR_EQUAL((char *)&sin1->sin6_addr,
+					(char *)&sin2->sin6_addr);
+}
+#else	/* !IPV6_SUPPORTED */
+static inline _Bool
+compare_sockaddr6(__attribute__ ((unused)) const struct sockaddr *sa1,
+		__attribute__ ((unused)) const struct sockaddr *sa2)
+{
+	return false;
+}
+#endif	/* !IPV6_SUPPORTED */
+
+/**
+ * nfs_compare_sockaddr - compare two socket addresses for equality
+ * @sa1: pointer to a socket address
+ * @sa2: pointer to a socket address
+ *
+ * Returns true if the two socket addresses contain equivalent
+ * network addresses; otherwise false is returned.
+ */
+static inline _Bool
+nfs_compare_sockaddr(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+	if (sa1->sa_family == sa2->sa_family)
+		switch (sa1->sa_family) {
+		case AF_INET:
+			return compare_sockaddr4(sa1, sa2);
+		case AF_INET6:
+			return compare_sockaddr6(sa1, sa2);
+		}
+
+	return false;
+}
+
+#endif	/* !NFS_UTILS_SOCKADDR_H */
diff --git a/support/include/tcpwrapper.h b/support/include/tcpwrapper.h
index 98cf806..f735106 100644
--- a/support/include/tcpwrapper.h
+++ b/support/include/tcpwrapper.h
@@ -5,14 +5,8 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-extern int verboselog;
-
-extern int allow_severity;
-extern int deny_severity;
-
-extern int good_client(char *daemon, struct sockaddr_in *addr);
-extern int from_local (struct sockaddr_in *addr);
-extern int check_default(char *daemon, struct sockaddr_in *addr,
-			 u_long proc, u_long prog);
+extern int from_local(const struct sockaddr *sap);
+extern int check_default(char *name, struct sockaddr *sap,
+			const unsigned long program);
 
 #endif /* TCP_WRAPPER_H */
diff --git a/support/include/v4root.h b/support/include/v4root.h
new file mode 100644
index 0000000..706c15c
--- /dev/null
+++ b/support/include/v4root.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2009 Red Hat <nfs@redhat.com>
+ * support/include/v4root.h
+ *
+ * Support routines for dynamic pseudo roots.
+ *
+ */
+
+#ifndef V4ROOT_H
+#define V4ROOT_H
+
+extern int v4root_needed;
+extern void v4root_set(void);
+
+#endif /* V4ROOT_H */
diff --git a/support/misc/from_local.c b/support/misc/from_local.c
index 89ccc4a..e2de969 100644
--- a/support/misc/from_local.c
+++ b/support/misc/from_local.c
@@ -37,32 +37,100 @@
 static char sccsid[] = "@(#) from_local.c 1.3 96/05/31 15:52:57";
 #endif
 
-#ifdef TEST
-#undef perror
+#ifdef HAVE_CONFIG_H
+#include <config.h>
 #endif
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <netdb.h>
 #include <netinet/in.h>
 #include <net/if.h>
 #include <sys/ioctl.h>
-#include <syslog.h>
 #include <stdlib.h>
 #include <string.h>
 
+#include "sockaddr.h"
+#include "tcpwrapper.h"
+#include "xlog.h"
+
 #ifndef TRUE
 #define	TRUE	1
 #define FALSE	0
 #endif
 
- /*
-  * With virtual hosting, each hardware network interface can have multiple
-  * network addresses. On such machines the number of machine addresses can
-  * be surprisingly large.
-  */
+#ifdef HAVE_GETIFADDRS
+
+#include <ifaddrs.h>
+#include <time.h>
+
+/**
+ * from_local - determine whether request comes from the local system
+ * @sap: pointer to socket address to check
+ *
+ * With virtual hosting, each hardware network interface can have
+ * multiple network addresses. On such machines the number of machine
+ * addresses can be surprisingly large.
+ *
+ * We also expect the local network configuration to change over time,
+ * so call getifaddrs(3) more than once, but not too often.
+ *
+ * Returns TRUE if the sockaddr contains an address of one of the local
+ * network interfaces.  Otherwise FALSE is returned.
+ */
+int
+from_local(const struct sockaddr *sap)
+{
+	static struct ifaddrs *ifaddr = NULL;
+	static time_t last_update = 0;
+	struct ifaddrs *ifa;
+	unsigned int count;
+	time_t now;
+
+	if (time(&now) == ((time_t)-1)) {
+		xlog(L_ERROR, "%s: time(2): %m", __func__);
+
+		/* If we don't know what time it is, use the
+		 * existing ifaddr list, if one exists  */
+		now = last_update;
+		if (ifaddr == NULL)
+			now++;
+	}
+	if (now != last_update) {
+		xlog(D_GENERAL, "%s: updating local if addr list", __func__);
+
+		if (ifaddr)
+			freeifaddrs(ifaddr);
+
+		if (getifaddrs(&ifaddr) == -1) {
+			xlog(L_ERROR, "%s: getifaddrs(3): %m", __func__);
+			return FALSE;
+		}
+
+		last_update = now;
+	}
+
+	count = 0;
+	for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
+		if ((ifa->ifa_flags & IFF_UP) &&
+		    nfs_compare_sockaddr(sap, ifa->ifa_addr)) {
+			xlog(D_GENERAL, "%s: incoming address matches "
+					"local interface address", __func__);
+			return TRUE;
+		} else
+			count++;
+	}
+
+	xlog(D_GENERAL, "%s: checked %u local if addrs; "
+			"incoming address not found", __func__, count);
+	return FALSE;
+}
+
+#else	/* !HAVE_GETIFADDRS */
+
 static int num_local;
 static int num_addrs;
 static struct in_addr *addrs;
@@ -81,7 +149,7 @@ static int grow_addrs(void)
     new_num = (addrs == 0) ? 1 : num_addrs + num_addrs;
     new_addrs = (struct in_addr *) malloc(sizeof(*addrs) * new_num);
     if (new_addrs == 0) {
-	perror("portmap: out of memory");
+	xlog_warn("%s: out of memory", __func__);
 	return (0);
     } else {
 	if (addrs != 0) {
@@ -112,13 +180,13 @@ find_local(void)
      */
 
     if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
-	perror("socket");
+	xlog_warn("%s: socket(2): %m", __func__);
 	return (0);
     }
     ifc.ifc_len = sizeof(buf);
     ifc.ifc_buf = buf;
     if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) {
-	perror("SIOCGIFCONF");
+	xlog_warn("%s: ioctl(SIOCGIFCONF): %m", __func__);
 	(void) close(sock);
 	return (0);
     }
@@ -130,10 +198,10 @@ find_local(void)
 	if (ifr->ifr_addr.sa_family == AF_INET) {	/* IP net interface */
 	    ifreq = *ifr;
 	    if (ioctl(sock, SIOCGIFFLAGS, (char *) &ifreq) < 0) {
-		perror("SIOCGIFFLAGS");
+		xlog_warn("%s: ioctl(SIOCGIFFLAGS): %m", __func__);
 	    } else if (ifreq.ifr_flags & IFF_UP) {	/* active interface */
 		if (ioctl(sock, SIOCGIFADDR, (char *) &ifreq) < 0) {
-		    perror("SIOCGIFADDR");
+		    xlog_warn("%s: ioctl(SIOCGIFADDR): %m", __func__);
 		} else {
 		    if (num_local >= num_addrs)
 			if (grow_addrs() == 0)
@@ -153,14 +221,28 @@ find_local(void)
     return (num_local);
 }
 
-/* from_local - determine whether request comes from the local system */
+/**
+ * from_local - determine whether request comes from the local system
+ * @sap: pointer to socket address to check
+ *
+ * With virtual hosting, each hardware network interface can have
+ * multiple network addresses. On such machines the number of machine
+ * addresses can be surprisingly large.
+ *
+ * Returns TRUE if the sockaddr contains an address of one of the local
+ * network interfaces.  Otherwise FALSE is returned.
+ */
 int
-from_local(struct sockaddr_in *addr)
+from_local(const struct sockaddr *sap)
 {
+    const struct sockaddr_in *addr = (const struct sockaddr_in *)sap;
     int     i;
 
+    if (sap->sa_family != AF_INET)
+	return (FALSE);
+
     if (addrs == 0 && find_local() == 0)
-	syslog(LOG_ERR, "cannot find any active local network interfaces");
+	xlog(L_ERROR, "Cannot find any active local network interfaces");
 
     for (i = 0; i < num_local; i++) {
 	if (memcmp((char *) &(addr->sin_addr), (char *) &(addrs[i]),
@@ -172,9 +254,8 @@ from_local(struct sockaddr_in *addr)
 
 #ifdef TEST
 
-main()
+int main(void)
 {
-    char   *inet_ntoa();
     int     i;
 
     find_local();
@@ -182,4 +263,6 @@ main()
 	printf("%s\n", inet_ntoa(addrs[i]));
 }
 
-#endif
+#endif	/* TEST */
+
+#endif	/* !HAVE_GETIFADDRS */
diff --git a/support/misc/tcpwrapper.c b/support/misc/tcpwrapper.c
index 1da6020..06b0a46 100644
--- a/support/misc/tcpwrapper.c
+++ b/support/misc/tcpwrapper.c
@@ -34,13 +34,12 @@
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
+
 #ifdef HAVE_LIBWRAP
-#include <tcpwrapper.h>
 #include <unistd.h>
 #include <string.h>
 #include <rpc/rpc.h>
 #include <rpc/pmap_prot.h>
-#include <syslog.h>
 #include <netdb.h>
 #include <pwd.h>
 #include <sys/types.h>
@@ -49,108 +48,146 @@
 #include <sys/stat.h>
 #include <tcpd.h>
 
+#include "sockaddr.h"
+#include "tcpwrapper.h"
 #include "xlog.h"
 
 #ifdef SYSV40
 #include <netinet/in.h>
 #include <rpc/rpcent.h>
-#endif
+#endif	/* SYSV40 */
 
-static void logit(int severity, struct sockaddr_in *addr,
-		  u_long procnum, u_long prognum, char *text);
-static int check_files(void);
+#define ALLOW 1
+#define DENY 0
 
-/*
- * These need to exist since they are externed 
- * public header files.
- */
-int     verboselog = 0;
-int     allow_severity = LOG_INFO;
-int     deny_severity = LOG_WARNING;
+#ifdef IPV6_SUPPORTED
+static void
+present_address(const struct sockaddr *sap, char *buf, const size_t buflen)
+{
+	const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
+	const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap;
+	socklen_t len = (socklen_t)buflen;
+
+	switch (sap->sa_family) {
+	case AF_INET:
+		if (inet_ntop(AF_INET, &sin->sin_addr, buf, len) != 0)
+			return;
+	case AF_INET6:
+		if (inet_ntop(AF_INET6, &sin6->sin6_addr, buf, len) != 0)
+			return;
+	}
 
-#define log_bad_host(addr, proc, prog) \
-  logit(deny_severity, addr, proc, prog, "request from unauthorized host")
+	memset(buf, 0, buflen);
+	strncpy(buf, "unrecognized caller", buflen);
+}
+#else	/* !IPV6_SUPPORTED */
+static void
+present_address(const struct sockaddr *sap, char *buf, const size_t buflen)
+{
+	const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
+	socklen_t len = (socklen_t)buflen;
 
-#define ALLOW 1
-#define DENY 0
+	if (sap->sa_family == AF_INET)
+		if (inet_ntop(AF_INET, &sin->sin_addr, buf, len) != 0)
+			return;
+
+	memset(buf, 0, buflen);
+	strncpy(buf, "unrecognized caller", (size_t)buflen);
+}
+#endif	/* !IPV6_SUPPORTED */
 
 typedef struct _haccess_t {
-	TAILQ_ENTRY(_haccess_t) list;
-	int access;
-    struct in_addr addr;
+	TAILQ_ENTRY(_haccess_t)	list;
+	int			allowed;
+	union nfs_sockaddr	address;
 } haccess_t;
 
 #define HASH_TABLE_SIZE 1021
 typedef struct _hash_head {
 	TAILQ_HEAD(host_list, _haccess_t) h_head;
 } hash_head;
-hash_head haccess_tbl[HASH_TABLE_SIZE];
-static haccess_t *haccess_lookup(struct sockaddr_in *addr, u_long);
-static void haccess_add(struct sockaddr_in *addr, u_long, int);
 
-inline unsigned int strtoint(char *str)
+static hash_head haccess_tbl[HASH_TABLE_SIZE];
+
+static unsigned long
+strtoint(const char *str)
 {
-	unsigned int n = 0;
-	int len = strlen(str);
-	int i;
+	unsigned long i, n = 0;
+	size_t len = strlen(str);
 
-	for (i=0; i < len; i++)
-		n+=((int)str[i])*i;
+	for (i = 0; i < len; i++)
+		n += (unsigned char)str[i] * i;
 
 	return n;
 }
-static inline int hashint(unsigned int num)
+
+static unsigned int
+hashint(const unsigned long num)
 {
-	return num % HASH_TABLE_SIZE;
+	return (unsigned int)(num % HASH_TABLE_SIZE);
 }
-#define HASH(_addr, _prog) \
-	hashint((strtoint((_addr))+(_prog)))
 
-void haccess_add(struct sockaddr_in *addr, u_long prog, int access)
+static unsigned int
+HASH(const char *addr, const unsigned long program)
+{
+	return hashint(strtoint(addr) + program);
+}
+
+static void
+haccess_add(const struct sockaddr *sap, const char *address,
+		const unsigned long program, const int allowed)
 {
 	hash_head *head;
- 	haccess_t *hptr;
-	int hash;
+	haccess_t *hptr;
+	unsigned int hash;
 
 	hptr = (haccess_t *)malloc(sizeof(haccess_t));
 	if (hptr == NULL)
 		return;
 
-	hash = HASH(inet_ntoa(addr->sin_addr), prog);
+	hash = HASH(address, program);
 	head = &(haccess_tbl[hash]);
 
-	hptr->access = access;
-	hptr->addr.s_addr = addr->sin_addr.s_addr;
+	hptr->allowed = allowed;
+	memcpy(&hptr->address, sap, (size_t)nfs_sockaddr_length(sap));
 
 	if (TAILQ_EMPTY(&head->h_head))
 		TAILQ_INSERT_HEAD(&head->h_head, hptr, list);
 	else
 		TAILQ_INSERT_TAIL(&head->h_head, hptr, list);
 }
-haccess_t *haccess_lookup(struct sockaddr_in *addr, u_long prog)
+
+static haccess_t *
+haccess_lookup(const struct sockaddr *sap, const char *address,
+		const unsigned long program)
 {
 	hash_head *head;
- 	haccess_t *hptr;
-	int hash;
+	haccess_t *hptr;
+	unsigned int hash;
 
-	hash = HASH(inet_ntoa(addr->sin_addr), prog);
+	hash = HASH(address, program);
 	head = &(haccess_tbl[hash]);
 
 	TAILQ_FOREACH(hptr, &head->h_head, list) {
-		if (hptr->addr.s_addr == addr->sin_addr.s_addr)
+		if (nfs_compare_sockaddr(&hptr->address.sa, sap))
 			return hptr;
 	}
 	return NULL;
 }
 
-int
-good_client(daemon, addr)
-char *daemon;
-struct sockaddr_in *addr;
+static void
+logit(const char *address)
+{
+	xlog_warn("connect from %s denied: request from unauthorized host",
+			address);
+}
+
+static int
+good_client(char *name, struct sockaddr *sap)
 {
 	struct request_info req;
 
-	request_init(&req, RQ_DAEMON, daemon, RQ_CLIENT_SIN, addr, 0);
+	request_init(&req, RQ_DAEMON, name, RQ_CLIENT_SIN, sap, 0);
 	sock_methods(&req);
 
 	if (hosts_access(&req)) 
@@ -159,9 +196,8 @@ struct sockaddr_in *addr;
 	return DENY;
 }
 
-/* check_files - check to see if either access files have changed */
-
-static int check_files()
+static int
+check_files(void)
 {
 	static time_t allow_mtime, deny_mtime;
 	struct stat astat, dstat;
@@ -186,45 +222,48 @@ static int check_files()
 	return changed;
 }
 
-/* check_default - additional checks for NULL, DUMP, GETPORT and unknown */
-
+/**
+ * check_default - additional checks for NULL, DUMP, GETPORT and unknown
+ * @name: pointer to '\0'-terminated ASCII string containing name of the
+ *		daemon requesting the access check
+ * @sap: pointer to sockaddr containing network address of caller
+ * @program: RPC program number caller is attempting to access
+ *
+ * Returns TRUE if the caller is allowed access; otherwise FALSE is returned.
+ */
 int
-check_default(daemon, addr, proc, prog)
-char *daemon;
-struct sockaddr_in *addr;
-u_long  proc;
-u_long  prog;
+check_default(char *name, struct sockaddr *sap, const unsigned long program)
 {
 	haccess_t *acc = NULL;
 	int changed = check_files();
+	char buf[INET6_ADDRSTRLEN];
+
+	present_address(sap, buf, sizeof(buf));
 
-	acc = haccess_lookup(addr, prog);
-	if (acc && changed == 0)
-		return (acc->access);
+	acc = haccess_lookup(sap, buf, program);
+	if (acc != NULL && changed == 0) {
+		xlog(D_GENERAL, "%s: access by %s %s (cached)", __func__,
+			buf, acc->allowed ? "ALLOWED" : "DENIED");
+		return acc->allowed;
+	}
 
-	if (!(from_local(addr) || good_client(daemon, addr))) {
-		log_bad_host(addr, proc, prog);
-		if (acc)
-			acc->access = FALSE;
-		else 
-			haccess_add(addr, prog, FALSE);
+	if (!(from_local(sap) || good_client(name, sap))) {
+		logit(buf);
+		if (acc != NULL)
+			acc->allowed = FALSE;
+		else
+			haccess_add(sap, buf, program, FALSE);
+		xlog(D_GENERAL, "%s: access by %s DENIED", __func__, buf);
 		return (FALSE);
 	}
 
-	if (acc)
-		acc->access = TRUE;
-	else 
-		haccess_add(addr, prog, TRUE);
+	if (acc != NULL)
+		acc->allowed = TRUE;
+	else
+		haccess_add(sap, buf, program, TRUE);
+	xlog(D_GENERAL, "%s: access by %s ALLOWED", __func__, buf);
 
-    return (TRUE);
+	return (TRUE);
 }
 
-/* logit - report events of interest via the syslog daemon */
-
-static void logit(int severity, struct sockaddr_in *addr,
-		  u_long procnum, u_long prognum, char *text)
-{
-	syslog(severity, "connect from %s denied: %s",
-	       inet_ntoa(addr->sin_addr), text);
-}
-#endif
+#endif	/* HAVE_LIBWRAP */
diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am
index e9462fc..60400b2 100644
--- a/support/nfs/Makefile.am
+++ b/support/nfs/Makefile.am
@@ -4,7 +4,8 @@ noinst_LIBRARIES = libnfs.a
 libnfs_a_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \
 		   xlog.c xcommon.c wildmat.c nfsclient.c \
 		   nfsexport.c getfh.c nfsctl.c rpc_socket.c getport.c \
-		   svc_socket.c cacheio.c closeall.c nfs_mntent.c conffile.c
+		   svc_socket.c cacheio.c closeall.c nfs_mntent.c conffile.c \
+		   svc_create.c
 
 MAINTAINERCLEANFILES = Makefile.in
 
diff --git a/support/nfs/exports.c b/support/nfs/exports.c
index 1aaebf4..a93941c 100644
--- a/support/nfs/exports.c
+++ b/support/nfs/exports.c
@@ -84,6 +84,31 @@ setexportent(char *fname, char *type)
 	first = 1;
 }
 
+static void init_exportent (struct exportent *ee, int fromkernel)
+{
+	ee->e_flags = EXPORT_DEFAULT_FLAGS;
+	/* some kernels assume the default is sync rather than
+	 * async.  More recent kernels always report one or other,
+	 * but this test makes sure we assume same as kernel
+	 * Ditto for wgather
+	 */
+	if (fromkernel) {
+		ee->e_flags &= ~NFSEXP_ASYNC;
+		ee->e_flags &= ~NFSEXP_GATHERED_WRITES;
+	}
+	ee->e_anonuid = 65534;
+	ee->e_anongid = 65534;
+	ee->e_squids = NULL;
+	ee->e_sqgids = NULL;
+	ee->e_mountpoint = NULL;
+	ee->e_fslocmethod = FSLOC_NONE;
+	ee->e_fslocdata = NULL;
+	ee->e_secinfo[0].flav = NULL;
+	ee->e_nsquids = 0;
+	ee->e_nsqgids = 0;
+	ee->e_uuid = NULL;
+}
+
 struct exportent *
 getexportent(int fromkernel, int fromexports)
 {
@@ -102,26 +127,7 @@ getexportent(int fromkernel, int fromexports)
 		has_default_opts = 0;
 		has_default_subtree_opts = 0;
 	
-		def_ee.e_flags = EXPORT_DEFAULT_FLAGS;
-		/* some kernels assume the default is sync rather than
-		 * async.  More recent kernels always report one or other,
-		 * but this test makes sure we assume same as kernel
-		 * Ditto for wgather
-		 */
-		if (fromkernel) {
-			def_ee.e_flags &= ~NFSEXP_ASYNC;
-			def_ee.e_flags &= ~NFSEXP_GATHERED_WRITES;
-		}
-		def_ee.e_anonuid = 65534;
-		def_ee.e_anongid = 65534;
-		def_ee.e_squids = NULL;
-		def_ee.e_sqgids = NULL;
-		def_ee.e_mountpoint = NULL;
-		def_ee.e_fslocmethod = FSLOC_NONE;
-		def_ee.e_fslocdata = NULL;
-		def_ee.e_secinfo[0].flav = NULL;
-		def_ee.e_nsquids = 0;
-		def_ee.e_nsqgids = 0;
+		init_exportent(&def_ee, fromkernel);
 
 		ok = getpath(def_ee.e_path, sizeof(def_ee.e_path));
 		if (ok <= 0)
@@ -334,18 +340,7 @@ mkexportent(char *hname, char *path, char *options)
 {
 	static struct exportent	ee;
 
-	ee.e_flags = EXPORT_DEFAULT_FLAGS;
-	ee.e_anonuid = 65534;
-	ee.e_anongid = 65534;
-	ee.e_squids = NULL;
-	ee.e_sqgids = NULL;
-	ee.e_mountpoint = NULL;
-	ee.e_fslocmethod = FSLOC_NONE;
-	ee.e_fslocdata = NULL;
-	ee.e_secinfo[0].flav = NULL;
-	ee.e_nsquids = 0;
-	ee.e_nsqgids = 0;
-	ee.e_uuid = NULL;
+	init_exportent(&ee, 0);
 
 	xfree(ee.e_hostname);
 	ee.e_hostname = xstrdup(hname);
@@ -385,7 +380,7 @@ static int valid_uuid(char *uuid)
  * do nothing if it's already there.  Returns the index of flavor
  * in the resulting array in any case.
  */
-static int secinfo_addflavor(struct flav_info *flav, struct exportent *ep)
+int secinfo_addflavor(struct flav_info *flav, struct exportent *ep)
 {
 	struct sec_entry *p;
 
@@ -467,9 +462,20 @@ static void clearflags(int mask, unsigned int active, struct exportent *ep)
 	}
 }
 
-/* options that can vary per flavor: */
-#define NFSEXP_SECINFO_FLAGS (NFSEXP_READONLY | NFSEXP_ROOTSQUASH \
-					| NFSEXP_ALLSQUASH)
+/*
+ * For those flags which are not allowed to vary by pseudoflavor,
+ * ensure that the export flags agree with the flags on each
+ * pseudoflavor:
+ */
+static void fix_pseudoflavor_flags(struct exportent *ep)
+{
+	struct export_features *ef;
+	struct sec_entry *p;
+
+	ef = get_export_features();
+	for (p = ep->e_secinfo; p->flav; p++)
+		p->flags |= ep->e_flags & ~ef->secinfo_flags;
+}
 
 /*
  * Parse option string pointed to by cp and set mount options accordingly.
@@ -477,7 +483,6 @@ static void clearflags(int mask, unsigned int active, struct exportent *ep)
 static int
 parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr)
 {
-	struct sec_entry *p;
 	int	had_subtree_opt = 0;
 	char 	*flname = efname?efname:"command line";
 	int	flline = efp?efp->x_line:0;
@@ -507,25 +512,25 @@ parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr)
 		else if (strcmp(opt, "rw") == 0)
 			clearflags(NFSEXP_READONLY, active, ep);
 		else if (!strcmp(opt, "secure"))
-			ep->e_flags &= ~NFSEXP_INSECURE_PORT;
+			clearflags(NFSEXP_INSECURE_PORT, active, ep);
 		else if (!strcmp(opt, "insecure"))
-			ep->e_flags |= NFSEXP_INSECURE_PORT;
+			setflags(NFSEXP_INSECURE_PORT, active, ep);
 		else if (!strcmp(opt, "sync"))
-			ep->e_flags &= ~NFSEXP_ASYNC;
+			clearflags(NFSEXP_ASYNC, active, ep);
 		else if (!strcmp(opt, "async"))
-			ep->e_flags |= NFSEXP_ASYNC;
+			setflags(NFSEXP_ASYNC, active, ep);
 		else if (!strcmp(opt, "nohide"))
-			ep->e_flags |= NFSEXP_NOHIDE;
+			setflags(NFSEXP_NOHIDE, active, ep);
 		else if (!strcmp(opt, "hide"))
-			ep->e_flags &= ~NFSEXP_NOHIDE;
+			clearflags(NFSEXP_NOHIDE, active, ep);
 		else if (!strcmp(opt, "crossmnt"))
-			ep->e_flags |= NFSEXP_CROSSMOUNT;
+			setflags(NFSEXP_CROSSMOUNT, active, ep);
 		else if (!strcmp(opt, "nocrossmnt"))
-			ep->e_flags &= ~NFSEXP_CROSSMOUNT;
+			clearflags(NFSEXP_CROSSMOUNT, active, ep);
 		else if (!strcmp(opt, "wdelay"))
-			ep->e_flags |= NFSEXP_GATHERED_WRITES;
+			setflags(NFSEXP_GATHERED_WRITES, active, ep);
 		else if (!strcmp(opt, "no_wdelay"))
-			ep->e_flags &= ~NFSEXP_GATHERED_WRITES;
+			clearflags(NFSEXP_GATHERED_WRITES, active, ep);
 		else if (strcmp(opt, "root_squash") == 0)
 			setflags(NFSEXP_ROOTSQUASH, active, ep);
 		else if (!strcmp(opt, "no_root_squash"))
@@ -536,22 +541,22 @@ parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr)
 			clearflags(NFSEXP_ALLSQUASH, active, ep);
 		else if (strcmp(opt, "subtree_check") == 0) {
 			had_subtree_opt = 1;
-			ep->e_flags &= ~NFSEXP_NOSUBTREECHECK;
+			clearflags(NFSEXP_NOSUBTREECHECK, active, ep);
 		} else if (strcmp(opt, "no_subtree_check") == 0) {
 			had_subtree_opt = 1;
-			ep->e_flags |= NFSEXP_NOSUBTREECHECK;
+			setflags(NFSEXP_NOSUBTREECHECK, active, ep);
 		} else if (strcmp(opt, "auth_nlm") == 0)
-			ep->e_flags &= ~NFSEXP_NOAUTHNLM;
+			clearflags(NFSEXP_NOAUTHNLM, active, ep);
 		else if (strcmp(opt, "no_auth_nlm") == 0)
-			ep->e_flags |= NFSEXP_NOAUTHNLM;
+			setflags(NFSEXP_NOAUTHNLM, active, ep);
 		else if (strcmp(opt, "secure_locks") == 0)
-			ep->e_flags &= ~NFSEXP_NOAUTHNLM;
+			clearflags(NFSEXP_NOAUTHNLM, active, ep);
 		else if (strcmp(opt, "insecure_locks") == 0)
-			ep->e_flags |= NFSEXP_NOAUTHNLM;
+			setflags(NFSEXP_NOAUTHNLM, active, ep);
 		else if (strcmp(opt, "acl") == 0)
-			ep->e_flags &= ~NFSEXP_NOACL;
+			clearflags(NFSEXP_NOACL, active, ep);
 		else if (strcmp(opt, "no_acl") == 0)
-			ep->e_flags |= NFSEXP_NOACL;
+			setflags(NFSEXP_NOACL, active, ep);
 		else if (strncmp(opt, "anonuid=", 8) == 0) {
 			char *oe;
 			ep->e_anonuid = strtol(opt+8, &oe, 10);
@@ -583,11 +588,11 @@ bad_option:
 			char *oe;
 			if (strcmp(opt+5, "root") == 0) {
 				ep->e_fsid = 0;
-				ep->e_flags |= NFSEXP_FSID;
+				setflags(NFSEXP_FSID, active, ep);
 			} else {
 				ep->e_fsid = strtoul(opt+5, &oe, 0);
 				if (opt[5]!='\0' && *oe == '\0') 
-					ep->e_flags |= NFSEXP_FSID;
+					setflags(NFSEXP_FSID, active, ep);
 				else if (valid_uuid(opt+5))
 					ep->e_uuid = strdup(opt+5);
 				else {
@@ -628,22 +633,15 @@ bad_option:
 		} else {
 			xlog(L_ERROR, "%s:%d: unknown keyword \"%s\"\n",
 					flname, flline, opt);
-			ep->e_flags |= NFSEXP_ALLSQUASH | NFSEXP_READONLY;
+			setflags(NFSEXP_ALLSQUASH | NFSEXP_READONLY, active, ep);
 			goto bad_option;
 		}
 		free(opt);
 		while (isblank(*cp))
 			cp++;
 	}
-	/*
-	 * Turn on nohide which will allow this export to cross over
-	 * the 'mount --bind' mount point.
-	 */
-	if (ep->e_fslocdata)
-		ep->e_flags |= NFSEXP_NOHIDE;
 
-	for (p = ep->e_secinfo; p->flav; p++)
-		p->flags |= ep->e_flags & ~NFSEXP_SECINFO_FLAGS;
+	fix_pseudoflavor_flags(ep);
 	ep->e_squids = squids;
 	ep->e_sqgids = sqgids;
 	ep->e_nsquids = nsquids;
@@ -760,4 +758,34 @@ syntaxerr(char *msg)
 	xlog(L_ERROR, "%s:%d: syntax error: %s",
 			efname, efp?efp->x_line:0, msg);
 }
-
+struct export_features *get_export_features(void)
+{
+	static char *path = "/proc/fs/nfsd/export_features";
+	static struct export_features ef;
+	static int cached = 0;
+	char buf[50];
+	int c;
+	int fd;
+
+	if (cached)
+		return &ef;
+
+	ef.flags = NFSEXP_OLDFLAGS;
+	ef.secinfo_flags = NFSEXP_OLD_SECINFO_FLAGS;
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1)
+		goto good;
+	fd = read(fd, buf, 50);
+	if (fd == -1)
+		goto err;
+	c = sscanf(buf, "%x %x", &ef.flags, &ef.secinfo_flags);
+	if (c != 2)
+		goto err;
+good:
+	cached = 1;
+	return &ef;
+err:
+	xlog(L_WARNING, "unexpected error reading %s", path);
+	return &ef;
+}
diff --git a/support/nfs/getport.c b/support/nfs/getport.c
index 4bdf556..c930539 100644
--- a/support/nfs/getport.c
+++ b/support/nfs/getport.c
@@ -45,6 +45,7 @@
 #include <rpc/rpcb_prot.h>
 #endif
 
+#include "sockaddr.h"
 #include "nfsrpc.h"
 
 /*
@@ -199,7 +200,63 @@ static CLIENT *nfs_gp_get_rpcbclient(struct sockaddr *sap,
 	return clnt;
 }
 
-/*
+/**
+ * nfs_get_proto - Convert a netid to an address family and protocol number
+ * @netid: C string containing a netid
+ * @family: OUT: address family
+ * @protocol: OUT: protocol number
+ *
+ * Returns 1 and fills in @protocol if the netid was recognized;
+ * otherwise zero is returned.
+ */
+#ifdef HAVE_LIBTIRPC
+int
+nfs_get_proto(const char *netid, sa_family_t *family, unsigned long *protocol)
+{
+	struct netconfig *nconf;
+	struct protoent *proto;
+
+	nconf = getnetconfigent(netid);
+	if (nconf == NULL)
+		return 0;
+
+	proto = getprotobyname(nconf->nc_proto);
+	if (proto == NULL) {
+		freenetconfigent(nconf);
+		return 0;
+	}
+
+	*family = AF_UNSPEC;
+	if (strcmp(nconf->nc_protofmly, NC_INET) == 0)
+		*family = AF_INET;
+	if (strcmp(nconf->nc_protofmly, NC_INET6) == 0)
+		*family = AF_INET6;
+	freenetconfigent(nconf);
+
+	*protocol = (unsigned long)proto->p_proto;
+	return 1;
+}
+#else	/* !HAVE_LIBTIRPC */
+int
+nfs_get_proto(const char *netid, sa_family_t *family, unsigned long *protocol)
+{
+	struct protoent *proto;
+
+	proto = getprotobyname(netid);
+	if (proto == NULL)
+		return 0;
+
+	*family = AF_INET;
+	*protocol = (unsigned long)proto->p_proto;
+	return 1;
+}
+#endif /* !HAVE_LIBTIRPC */
+
+/**
+ * nfs_get_netid - Convert a protocol family and protocol name to a netid
+ * @family: protocol family
+ * @protocol: protocol number
+ *
  * One of the arguments passed when querying remote rpcbind services
  * via rpcbind v3 or v4 is a netid string.  This replaces the pm_prot
  * field used in legacy PMAP_GETPORT calls.
@@ -213,13 +270,12 @@ static CLIENT *nfs_gp_get_rpcbclient(struct sockaddr *sap,
  * first entry that matches @family and @protocol and whose netid string
  * fits in the provided buffer.
  *
- * Returns a '\0'-terminated string if successful; otherwise NULL.
+ * Returns a '\0'-terminated string if successful.  Caller must
+ * free the returned string.  Otherwise NULL is returned, and
  * rpc_createerr.cf_stat is set to reflect the error.
  */
 #ifdef HAVE_LIBTIRPC
-
-static char *nfs_gp_get_netid(const sa_family_t family,
-			      const unsigned short protocol)
+char *nfs_get_netid(const sa_family_t family, const unsigned long protocol)
 {
 	char *nc_protofmly, *nc_proto, *nc_netid;
 	struct netconfig *nconf;
@@ -255,6 +311,9 @@ static char *nfs_gp_get_netid(const sa_family_t family,
 
 		nc_netid = strdup(nconf->nc_netid);
 		endnetconfig(handle);
+
+		if (nc_netid == NULL)
+			rpc_createerr.cf_stat = RPC_SYSTEMERROR;
 		return nc_netid;
 	}
 	endnetconfig(handle);
@@ -263,8 +322,28 @@ out:
 	rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
 	return NULL;
 }
+#else	/* !HAVE_LIBTIRPC */
+char *nfs_get_netid(const sa_family_t family, const unsigned long protocol)
+{
+	struct protoent *proto;
+	char *netid;
 
-#endif	/* HAVE_LIBTIRPC */
+	if (family != AF_INET)
+		goto out;
+	proto = getprotobynumber((int)protocol);
+	if (proto == NULL)
+		goto out;
+
+	netid = strdup(proto->p_name);
+	if (netid == NULL)
+		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+	return netid;
+
+out:
+	rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+	return NULL;
+}
+#endif	/* !HAVE_LIBTIRPC */
 
 /*
  * Extract a port number from a universal address, and terminate the
@@ -421,7 +500,7 @@ static int nfs_gp_init_rpcb_parms(const struct sockaddr *sap,
 {
 	char *netid, *addr;
 
-	netid = nfs_gp_get_netid(sap->sa_family, protocol);
+	netid = nfs_get_netid(sap->sa_family, protocol);
 	if (netid == NULL)
 		return 0;
 
@@ -627,8 +706,8 @@ int nfs_rpc_ping(const struct sockaddr *sap, const socklen_t salen,
 		 const rpcprog_t program, const rpcvers_t version,
 		 const unsigned short protocol, const struct timeval *timeout)
 {
-	struct sockaddr_storage address;
-	struct sockaddr *saddr = (struct sockaddr *)&address;
+	union nfs_sockaddr address;
+	struct sockaddr *saddr = &address.sa;
 	CLIENT *client;
 	struct timeval tout = { -1, 0 };
 	int result = 0;
@@ -696,8 +775,8 @@ unsigned short nfs_getport(const struct sockaddr *sap,
 			   const rpcvers_t version,
 			   const unsigned short protocol)
 {
-	struct sockaddr_storage address;
-	struct sockaddr *saddr = (struct sockaddr *)&address;
+	union nfs_sockaddr address;
+	struct sockaddr *saddr = &address.sa;
 	struct timeval timeout = { -1, 0 };
 	unsigned short port = 0;
 	CLIENT *client;
@@ -755,8 +834,8 @@ int nfs_getport_ping(struct sockaddr *sap, const socklen_t salen,
 	}
 
 	if (port != 0) {
-		struct sockaddr_storage address;
-		struct sockaddr *saddr = (struct sockaddr *)&address;
+		union nfs_sockaddr address;
+		struct sockaddr *saddr = &address.sa;
 
 		memcpy(saddr, sap, (size_t)salen);
 		nfs_set_port(saddr, port);
@@ -807,8 +886,8 @@ unsigned short nfs_getlocalport(const rpcprot_t program,
 				const rpcvers_t version,
 				const unsigned short protocol)
 {
-	struct sockaddr_storage address;
-	struct sockaddr *lb_addr = (struct sockaddr *)&address;
+	union nfs_sockaddr address;
+	struct sockaddr *lb_addr = &address.sa;
 	socklen_t lb_len = sizeof(*lb_addr);
 	unsigned short port = 0;
 
@@ -891,8 +970,8 @@ unsigned short nfs_rpcb_getaddr(const struct sockaddr *sap,
 				const unsigned short protocol,
 				const struct timeval *timeout)
 {
-	struct sockaddr_storage address;
-	struct sockaddr *saddr = (struct sockaddr *)&address;
+	union nfs_sockaddr address;
+	struct sockaddr *saddr = &address.sa;
 	CLIENT *client;
 	struct rpcb parms;
 	struct timeval tout = { -1, 0 };
diff --git a/support/nfs/rpc_socket.c b/support/nfs/rpc_socket.c
index 9c20f61..0e20824 100644
--- a/support/nfs/rpc_socket.c
+++ b/support/nfs/rpc_socket.c
@@ -26,6 +26,8 @@
 
 #include <sys/types.h>
 #include <sys/time.h>
+
+#include <stdbool.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
@@ -38,6 +40,7 @@
 #include <rpc/rpc.h>
 #include <rpc/pmap_prot.h>
 
+#include "sockaddr.h"
 #include "nfsrpc.h"
 
 #ifdef HAVE_LIBTIRPC
@@ -51,6 +54,7 @@
 #define NFSRPC_TIMEOUT_UDP	(3)
 #define NFSRPC_TIMEOUT_TCP	(10)
 
+
 /*
  * Set up an RPC client for communicating via a AF_LOCAL socket.
  *
@@ -121,10 +125,10 @@ static int nfs_bind(const int sock, const sa_family_t family)
 
 	switch (family) {
 	case AF_INET:
-		return bind(sock, (struct sockaddr *)&sin,
+		return bind(sock, (struct sockaddr *)(char *)&sin,
 					(socklen_t)sizeof(sin));
 	case AF_INET6:
-		return bind(sock, (struct sockaddr *)&sin6,
+		return bind(sock, (struct sockaddr *)(char *)&sin6,
 					(socklen_t)sizeof(sin6));
 	}
 
@@ -153,9 +157,9 @@ static int nfs_bindresvport(const int sock, const sa_family_t family)
 
 	switch (family) {
 	case AF_INET:
-		return bindresvport_sa(sock, (struct sockaddr *)&sin);
+		return bindresvport_sa(sock, (struct sockaddr *)(char *)&sin);
 	case AF_INET6:
-		return bindresvport_sa(sock, (struct sockaddr *)&sin6);
+		return bindresvport_sa(sock, (struct sockaddr *)(char *)&sin6);
 	}
 
 	errno = EAFNOSUPPORT;
@@ -416,49 +420,6 @@ static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap,
 }
 
 /**
- * nfs_get_port - extract port value from a socket address
- * @sap: pointer to socket address
- *
- * Returns port value in host byte order.
- */
-uint16_t
-nfs_get_port(const struct sockaddr *sap)
-{
-       const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
-       const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap;
-
-       switch (sap->sa_family) {
-       case AF_INET:
-               return ntohs(sin->sin_port);
-       case AF_INET6:
-               return ntohs(sin6->sin6_port);
-       }
-       return 0;
-}
-
-/**
- * nfs_set_port - set port value in a socket address
- * @sap: pointer to socket address
- * @port: port value to set
- *
- */
-void
-nfs_set_port(struct sockaddr *sap, const uint16_t port)
-{
-       struct sockaddr_in *sin = (struct sockaddr_in *)sap;
-       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
-
-       switch (sap->sa_family) {
-       case AF_INET:
-               sin->sin_port = htons(port);
-               break;
-       case AF_INET6:
-               sin6->sin6_port = htons(port);
-               break;
-       }
-}
-
-/**
  * nfs_get_rpcclient - acquire an RPC client
  * @sap: pointer to socket address of RPC server
  * @salen: length of socket address
diff --git a/support/nfs/svc_create.c b/support/nfs/svc_create.c
new file mode 100644
index 0000000..59ba505
--- /dev/null
+++ b/support/nfs/svc_create.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2009 Oracle.  All rights reserved.
+ *
+ * This file is part of nfs-utils.
+ *
+ * nfs-utils is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * nfs-utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory.h>
+#include <signal.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include <netinet/in.h>
+
+#include <sys/socket.h>
+#include <sys/resource.h>
+
+#include <rpc/rpc.h>
+#include <rpc/svc.h>
+
+#ifdef HAVE_TCP_WRAPPER
+#include "tcpwrapper.h"
+#endif
+
+#include "rpcmisc.h"
+#include "xlog.h"
+
+#ifdef HAVE_LIBTIRPC
+
+/*
+ * Set up an appropriate bind address, given @port and @nconf.
+ *
+ * Returns getaddrinfo(3) results if successful.  Caller must
+ * invoke freeaddrinfo(3) on these results.
+ *
+ * Otherwise NULL is returned if an error occurs.
+ */
+__attribute_malloc__
+static struct addrinfo *
+svc_create_bindaddr(struct netconfig *nconf, const uint16_t port)
+{
+	struct addrinfo *ai = NULL;
+	struct addrinfo hint = {
+		.ai_flags	= AI_PASSIVE | AI_NUMERICSERV,
+	};
+	char buf[8];
+	int error;
+
+	if (strcmp(nconf->nc_protofmly, NC_INET) == 0)
+		hint.ai_family = AF_INET;
+#ifdef IPV6_SUPPORTED
+	else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0)
+		hint.ai_family = AF_INET6;
+#endif	/* IPV6_SUPPORTED */
+	else {
+		xlog(D_GENERAL, "Unrecognized bind address family: %s",
+			nconf->nc_protofmly);
+		return NULL;
+	}
+
+	if (strcmp(nconf->nc_proto, NC_UDP) == 0)
+		hint.ai_protocol = (int)IPPROTO_UDP;
+	else if (strcmp(nconf->nc_proto, NC_TCP) == 0)
+		hint.ai_protocol = (int)IPPROTO_TCP;
+	else {
+		xlog(D_GENERAL, "Unrecognized bind address protocol: %s",
+			nconf->nc_proto);
+		return NULL;
+	}
+
+	(void)snprintf(buf, sizeof(buf), "%u", port);
+	error = getaddrinfo(NULL, buf, &hint, &ai);
+	if (error != 0) {
+		xlog(L_ERROR, "Failed to construct bind address: %s",
+			gai_strerror(error));
+		return NULL;
+	}
+
+	return ai;
+}
+
+static unsigned int
+svc_create_nconf(const char *name, const rpcprog_t program,
+		const rpcvers_t version,
+		void (*dispatch)(struct svc_req *, SVCXPRT *),
+		const uint16_t port, struct netconfig *nconf)
+{
+	struct t_bind bindaddr;
+	struct addrinfo *ai;
+	SVCXPRT	*xprt;
+
+	ai = svc_create_bindaddr(nconf, port);
+	if (ai == NULL)
+		return 0;
+
+	bindaddr.addr.buf = ai->ai_addr;
+	bindaddr.qlen = SOMAXCONN;
+
+	xprt = svc_tli_create(RPC_ANYFD, nconf, &bindaddr, 0, 0);
+	freeaddrinfo(ai);
+	if (xprt == NULL) {
+		xlog(D_GENERAL, "Failed to create listener xprt "
+				"(%s, %u, %s)", name, version, nconf->nc_netid);
+		return 0;
+	}
+
+	if (!svc_reg(xprt, program, version, dispatch, nconf)) {
+		/* svc_reg(3) destroys @xprt in this case */
+		xlog(D_GENERAL, "Failed to register (%s, %u, %s)",
+				name, version, nconf->nc_netid);
+		return 0;
+	}
+
+	return 1;
+}
+
+/**
+ * nfs_svc_create - start up RPC svc listeners
+ * @name: C string containing name of new service
+ * @program: RPC program number to register
+ * @version: RPC version number to register
+ * @dispatch: address of function that handles incoming RPC requests
+ * @port: if not zero, transport listens on this port
+ *
+ * Sets up network transports for receiving RPC requests, and starts
+ * the RPC dispatcher.  Returns the number of started network transports.
+ */
+unsigned int
+nfs_svc_create(__attribute__((unused)) char *name,
+		const rpcprog_t program, const rpcvers_t version,
+		void (*dispatch)(struct svc_req *, SVCXPRT *),
+		const uint16_t port)
+{
+	const struct sigaction create_sigaction = {
+		.sa_handler	= SIG_IGN,
+	};
+	unsigned int visible, up;
+	struct netconfig *nconf;
+	void *handlep;
+
+	/*
+	 * Ignore SIGPIPE to avoid exiting sideways when peers
+	 * close their TCP connection while we're trying to reply
+	 * to them.
+	 */
+	(void)sigaction(SIGPIPE, &create_sigaction, NULL);
+
+	handlep = setnetconfig();
+	if (handlep == NULL) {
+		xlog(L_ERROR, "Failed to access local netconfig database: %s",
+			nc_sperror());
+		return 0;
+	}
+
+	visible = 0;
+	up = 0;
+	while ((nconf = getnetconfig(handlep)) != NULL) {
+		if (!(nconf->nc_flag & NC_VISIBLE))
+			continue;
+		visible++;
+		up += svc_create_nconf(name, program, version, dispatch,
+						port, nconf);
+	}
+
+	if (visible == 0)
+		xlog(L_ERROR, "Failed to find any visible netconfig entries");
+
+	if (endnetconfig(handlep) == -1)
+		xlog(L_ERROR, "Failed to close local netconfig database: %s",
+			nc_sperror());
+
+	return up;
+}
+
+/**
+ * nfs_svc_unregister - remove service registrations from local rpcbind database
+ * @program: RPC program number to unregister
+ * @version: RPC version number to unregister
+ *
+ * Removes all registrations for [ @program, @version ] .
+ */
+void
+nfs_svc_unregister(const rpcprog_t program, const rpcvers_t version)
+{
+	if (rpcb_unset(program, version, NULL) == FALSE)
+		xlog(D_GENERAL, "Failed to unregister program %lu, version %lu",
+			(unsigned long)program, (unsigned long)version);
+}
+
+#else	/* !HAVE_LIBTIRPC */
+
+/**
+ * nfs_svc_create - start up RPC svc listeners
+ * @name: C string containing name of new service
+ * @program: RPC program number to register
+ * @version: RPC version number to register
+ * @dispatch: address of function that handles incoming RPC requests
+ * @port: if not zero, transport listens on this port
+ *
+ * Sets up network transports for receiving RPC requests, and starts
+ * the RPC dispatcher.  Returns the number of started network transports.
+ */
+unsigned int
+nfs_svc_create(char *name, const rpcprog_t program, const rpcvers_t version,
+		void (*dispatch)(struct svc_req *, SVCXPRT *),
+		const uint16_t port)
+{
+	rpc_init(name, (int)program, (int)version, dispatch, (int)port);
+	return 1;
+}
+
+/**
+ * nfs_svc_unregister - remove service registrations from local rpcbind database
+ * @program: RPC program number to unregister
+ * @version: RPC version number to unregister
+ *
+ * Removes all registrations for [ @program, @version ] .
+ */
+void
+nfs_svc_unregister(const rpcprog_t program, const rpcvers_t version)
+{
+	if (pmap_unset((unsigned long)program, (unsigned long)version) == FALSE)
+		xlog(D_GENERAL, "Failed to unregister program %lu, version %lu",
+			(unsigned long)program, (unsigned long)version);
+}
+
+#endif	/* !HAVE_LIBTIRPC */
diff --git a/support/nsm/Makefile.am b/support/nsm/Makefile.am
new file mode 100644
index 0000000..2038e68
--- /dev/null
+++ b/support/nsm/Makefile.am
@@ -0,0 +1,45 @@
+## Process this file with automake to produce Makefile.in
+
+GENFILES_CLNT	= sm_inter_clnt.c
+GENFILES_SVC	= sm_inter_svc.c
+GENFILES_XDR	= sm_inter_xdr.c
+GENFILES_H	= sm_inter.h
+
+GENFILES	= $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H)
+
+EXTRA_DIST	= sm_inter.x
+
+noinst_LIBRARIES = libnsm.a
+libnsm_a_SOURCES = $(GENFILES) file.c rpc.c
+
+BUILT_SOURCES = $(GENFILES)
+
+if CONFIG_RPCGEN
+RPCGEN	= $(top_builddir)/tools/rpcgen/rpcgen
+$(RPCGEN):
+	make -C ../../tools/rpcgen all
+else
+RPCGEN = @RPCGEN_PATH@
+endif
+
+$(GENFILES_CLNT): %_clnt.c: %.x $(RPCGEN)
+	test -f $@ && rm -rf $@ || true
+	$(RPCGEN) -l -o $@ $<
+
+$(GENFILES_SVC): %_svc.c: %.x $(RPCGEN)
+	test -f $@ && rm -rf $@ || true
+	$(RPCGEN) -m -o $@ $<
+
+$(GENFILES_XDR): %_xdr.c: %.x $(RPCGEN)
+	test -f $@ && rm -rf $@ || true
+	$(RPCGEN) -c -o $@ $<
+
+$(GENFILES_H): %.h: %.x $(RPCGEN)
+	test -f $@ && rm -rf $@ || true
+	$(RPCGEN) -h -o $@ $<
+	rm -f $(top_builddir)/support/include/sm_inter.h
+	$(LN_S) ../nsm/sm_inter.h $(top_builddir)/support/include/sm_inter.h
+
+MAINTAINERCLEANFILES = Makefile.in
+
+CLEANFILES = $(GENFILES) $(top_builddir)/support/include/sm_inter.h
diff --git a/support/nsm/file.c b/support/nsm/file.c
new file mode 100644
index 0000000..d469219
--- /dev/null
+++ b/support/nsm/file.c
@@ -0,0 +1,1067 @@
+/*
+ * Copyright 2009 Oracle.  All rights reserved.
+ *
+ * This file is part of nfs-utils.
+ *
+ * nfs-utils is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * nfs-utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * NSM for Linux.
+ *
+ * Callback information and NSM state is stored in files, usually
+ * under /var/lib/nfs.  A database of information contained in local
+ * files stores NLM callback data and what remote peers to notify of
+ * reboots.
+ *
+ * For each monitored remote peer, a text file is created under the
+ * directory specified by NSM_MONITOR_DIR.  The name of the file
+ * is a valid DNS hostname.  The hostname string must be a valid
+ * ASCII DNS name, and must not contain slash characters, white space,
+ * or '\0' (ie. anything that might have some special meaning in a
+ * path name).
+ *
+ * The contents of each file include seven blank-separated fields of
+ * text, finished with '\n'.  The first field contains the network
+ * address of the NLM service to call back.  The current implementation
+ * supports using only IPv4 addresses, so the only contents of this
+ * field are a network order IPv4 address expressed in 8 hexadecimal
+ * characters.
+ *
+ * The next four fields are text strings of hexadecimal characters,
+ * representing:
+ *
+ * 2. A 4 byte RPC program number of the NLM service to call back
+ * 3. A 4 byte RPC version number of the NLM service to call back
+ * 4. A 4 byte RPC procedure number of the NLM service to call back
+ * 5. A 16 byte opaque cookie that the NLM service uses to identify
+ *    the monitored host
+ *
+ * The sixth field is the monitored host's mon_name, passed to statd
+ * via an SM_MON request.
+ *
+ * The seventh field is the my_name for this peer, which is the
+ * hostname of the local NLM (currently on Linux, the result of
+ * `uname -n`).  This can be used as the source address/hostname
+ * when sending SM_NOTIFY requests.
+ *
+ * The NSM protocol does not limit the contents of these strings
+ * in any way except that they must fit into 1024 bytes.  Our
+ * implementation requires that these strings not contain
+ * white space or '\0'.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <stdint.h>
+#ifndef S_SPLINT_S
+#include <unistd.h>
+#endif
+#include <libgen.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <grp.h>
+
+#include "xlog.h"
+#include "nsm.h"
+
+#define RPCARGSLEN	(4 * (8 + 1))
+#define LINELEN		(RPCARGSLEN + SM_PRIV_SIZE * 2 + 1)
+
+#define NSM_KERNEL_STATE_FILE	"/proc/sys/fs/nfs/nsm_local_state"
+
+/*
+ * Some distributions place statd's files in a subdirectory
+ */
+#define NSM_PATH_EXTENSION
+/* #define NSM_PATH_EXTENSION	"/statd" */
+
+#define NSM_DEFAULT_STATEDIR		NFS_STATEDIR NSM_PATH_EXTENSION
+
+static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR;
+
+#define NSM_MONITOR_DIR	"sm"
+#define NSM_NOTIFY_DIR	"sm.bak"
+#define NSM_STATE_FILE	"state"
+
+
+static _Bool
+error_check(const int len, const size_t buflen)
+{
+	return (len < 0) || ((size_t)len >= buflen);
+}
+
+static _Bool
+exact_error_check(const ssize_t len, const size_t buflen)
+{
+	return (len < 0) || ((size_t)len != buflen);
+}
+
+/*
+ * Returns a dynamically allocated, '\0'-terminated buffer
+ * containing an appropriate pathname, or NULL if an error
+ * occurs.  Caller must free the returned result with free(3).
+ */
+__attribute_malloc__
+static char *
+nsm_make_record_pathname(const char *directory, const char *hostname)
+{
+	const char *c;
+	size_t size;
+	char *path;
+	int len;
+
+	/*
+	 * Block hostnames that contain characters that have
+	 * meaning to the file system (like '/'), or that can
+	 * be confusing on visual inspection (like ' ').
+	 */
+	for (c = hostname; *c != '\0'; c++)
+		if (*c == '/' || isspace((int)*c) != 0) {
+			xlog(D_GENERAL, "Hostname contains invalid characters");
+			return NULL;
+		}
+
+	size = strlen(nsm_base_dirname) + strlen(directory) + strlen(hostname) + 3;
+	if (size > PATH_MAX) {
+		xlog(D_GENERAL, "Hostname results in pathname that is too long");
+		return NULL;
+	}
+
+	path = malloc(size);
+	if (path == NULL) {
+		xlog(D_GENERAL, "Failed to allocate memory for pathname");
+		return NULL;
+	}
+
+	len = snprintf(path, size, "%s/%s/%s",
+			nsm_base_dirname, directory, hostname);
+	if (error_check(len, size)) {
+		xlog(D_GENERAL, "Pathname did not fit in specified buffer");
+		free(path);
+		return NULL;
+	}
+
+	return path;
+}
+
+/*
+ * Returns a dynamically allocated, '\0'-terminated buffer
+ * containing an appropriate pathname, or NULL if an error
+ * occurs.  Caller must free the returned result with free(3).
+ */
+__attribute_malloc__
+static char *
+nsm_make_pathname(const char *directory)
+{
+	size_t size;
+	char *path;
+	int len;
+
+	size = strlen(nsm_base_dirname) + strlen(directory) + 2;
+	if (size > PATH_MAX)
+		return NULL;
+
+	path = malloc(size);
+	if (path == NULL)
+		return NULL;
+
+	len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory);
+	if (error_check(len, size)) {
+		free(path);
+		return NULL;
+	}
+
+	return path;
+}
+
+/*
+ * Returns a dynamically allocated, '\0'-terminated buffer
+ * containing an appropriate pathname, or NULL if an error
+ * occurs.  Caller must free the returned result with free(3).
+ */
+__attribute_malloc__
+static char *
+nsm_make_temp_pathname(const char *pathname)
+{
+	size_t size;
+	char *path;
+	int len;
+
+	size = strlen(pathname) + sizeof(".new") + 2;
+	if (size > PATH_MAX)
+		return NULL;
+
+	path = malloc(size);
+	if (path == NULL)
+		return NULL;
+
+	len = snprintf(path, size, "%s.new", pathname);
+	if (error_check(len, size)) {
+		free(path);
+		return NULL;
+	}
+
+	return path;
+}
+
+/*
+ * Use "mktemp, write, rename" to update the contents of a file atomically.
+ *
+ * Returns true if completely successful, or false if some error occurred.
+ */
+static _Bool
+nsm_atomic_write(const char *path, const void *buf, const size_t buflen)
+{
+	_Bool result = false;
+	ssize_t len;
+	char *temp;
+	int fd;
+
+	temp = nsm_make_temp_pathname(path);
+	if (temp == NULL) {
+		xlog(L_ERROR, "Failed to create new path for %s", path);
+		goto out;
+	}
+
+	fd = open(temp, O_CREAT | O_TRUNC | O_SYNC | O_WRONLY, 0644);
+	if (fd == -1) {
+		xlog(L_ERROR, "Failed to create %s: %m", temp);
+		goto out;
+	}
+
+	len = write(fd, buf, buflen);
+	if (exact_error_check(len, buflen)) {
+		xlog(L_ERROR, "Failed to write %s: %m", temp);
+		(void)close(fd);
+		(void)unlink(temp);
+		goto out;
+	}
+
+	if (close(fd) == -1) {
+		xlog(L_ERROR, "Failed to close %s: %m", temp);
+		(void)unlink(temp);
+		goto out;
+	}
+
+	if (rename(temp, path) == -1) {
+		xlog(L_ERROR, "Failed to rename %s -> %s: %m",
+				temp, path);
+		(void)unlink(temp);
+		goto out;
+	}
+
+	/* Ostensibly, a sync(2) is not needed here because
+	 * open(O_CREAT), write(O_SYNC), and rename(2) are
+	 * already synchronous with persistent storage, for
+	 * any file system we care about. */
+
+	result = true;
+
+out:
+	free(temp);
+	return result;
+}
+
+/**
+ * nsm_setup_pathnames - set up pathname
+ * @progname: C string containing name of program, for error messages
+ * @parentdir: C string containing pathname to on-disk state, or NULL
+ *
+ * This runs before logging is set up, so error messages are directed
+ * to stderr.
+ *
+ * Returns true and sets up our pathnames, if @parentdir was valid
+ * and usable; otherwise false is returned.
+ */
+_Bool
+nsm_setup_pathnames(const char *progname, const char *parentdir)
+{
+	static char buf[PATH_MAX];
+	struct stat st;
+	char *path;
+
+	/* First: test length of name and whether it exists */
+	if (lstat(parentdir, &st) == -1) {
+		(void)fprintf(stderr, "%s: Failed to stat %s: %s",
+				progname, parentdir, strerror(errno));
+		return false;
+	}
+
+	/* Ensure we have a clean directory pathname */
+	strncpy(buf, parentdir, sizeof(buf));
+	path = dirname(buf);
+	if (*path == '.') {
+		(void)fprintf(stderr, "%s: Unusable directory %s",
+				progname, parentdir);
+		return false;
+	}
+
+	xlog(D_CALL, "Using %s as the state directory", parentdir);
+	strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname));
+	return true;
+}
+
+/**
+ * nsm_is_default_parentdir - check if parent directory is default
+ *
+ * Returns true if the active statd parent directory, set by
+ * nsm_change_pathname(), is the same as the built-in default
+ * parent directory; otherwise false is returned.
+ */
+_Bool
+nsm_is_default_parentdir(void)
+{
+	return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0;
+}
+
+/*
+ * Clear all capabilities but CAP_NET_BIND_SERVICE.  This permits
+ * callers to acquire privileged source ports, but all other root
+ * capabilities are disallowed.
+ *
+ * Returns true if successful, or false if some error occurred.
+ */
+static _Bool
+nsm_clear_capabilities(void)
+{
+	cap_t caps;
+
+	caps = cap_from_text("cap_net_bind_service=ep");
+	if (caps == NULL) {
+		xlog(L_ERROR, "Failed to allocate capability: %m");
+		return false;
+	}
+
+	if (cap_set_proc(caps) == -1) {
+		xlog(L_ERROR, "Failed to set capability flags: %m");
+		(void)cap_free(caps);
+		return false;
+	}
+
+	(void)cap_free(caps);
+	return true;
+}
+
+/**
+ * nsm_drop_privileges - drop root privileges
+ * @pidfd: file descriptor of a pid file
+ *
+ * Returns true if successful, or false if some error occurred.
+ *
+ * Set our effective UID and GID to that of our on-disk database.
+ */
+_Bool
+nsm_drop_privileges(const int pidfd)
+{
+	struct stat st;
+
+	(void)umask(S_IRWXO);
+
+	/*
+	 * XXX: If we can't stat dirname, or if dirname is owned by
+	 *      root, we should use "statduser" instead, which is set up
+	 *      by configure.ac.  Nothing in nfs-utils seems to use
+	 *      "statduser," though.
+	 */
+	if (lstat(nsm_base_dirname, &st) == -1) {
+		xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname);
+		return false;
+	}
+
+	if (st.st_uid == 0) {
+		xlog_warn("Running as root.  "
+			"chown %s to choose different user", nsm_base_dirname);
+		return true;
+	}
+
+	if (chdir(nsm_base_dirname) == -1) {
+		xlog(L_ERROR, "Failed to change working directory to %s: %m",
+				nsm_base_dirname);
+		return false;
+	}
+
+	/*
+	 * If the pidfile happens to reside on NFS, dropping privileges
+	 * will probably cause us to lose access, even though we are
+	 * holding it open.  Chown it to prevent this.
+	 */
+	if (pidfd >= 0)
+		if (fchown(pidfd, st.st_uid, st.st_gid) == -1)
+			xlog_warn("Failed to change owner of pidfile: %m");
+
+	/*
+	 * Don't clear capabilities when dropping root.
+	 */
+        if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
+                xlog(L_ERROR, "prctl(PR_SET_KEEPCAPS) failed: %m");
+		return 0;
+	}
+
+	if (setgroups(0, NULL) == -1) {
+		xlog(L_ERROR, "Failed to drop supplementary groups: %m");
+		return false;
+	}
+
+	/*
+	 * ORDER
+	 *
+	 * setgid(2) first, as setuid(2) may remove privileges needed
+	 * to set the group id.
+	 */
+	if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) {
+		xlog(L_ERROR, "Failed to drop privileges: %m");
+		return false;
+	}
+
+	xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid);
+
+	return nsm_clear_capabilities();
+}
+
+/**
+ * nsm_get_state - retrieve on-disk NSM state number
+ *
+ * Returns an odd NSM state number read from disk, or an initial
+ * state number.  Zero is returned if some error occurs.
+ */
+int
+nsm_get_state(_Bool update)
+{
+	int fd, state = 0;
+	ssize_t result;
+	char *path = NULL;
+
+	path = nsm_make_pathname(NSM_STATE_FILE);
+	if (path == NULL) {
+		xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE);
+		goto out;
+	}
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		if (errno != ENOENT) {
+			xlog(L_ERROR, "Failed to open %s: %m", path);
+			goto out;
+		}
+
+		xlog(L_NOTICE, "Initializing NSM state");
+		state = 1;
+		update = true;
+		goto update;
+	}
+
+	result = read(fd, &state, sizeof(state));
+	if (exact_error_check(result, sizeof(state))) {
+		xlog_warn("Failed to read %s: %m", path);
+
+		xlog(L_NOTICE, "Initializing NSM state");
+		state = 1;
+		update = true;
+		goto update;
+	}
+
+	if ((state & 1) == 0)
+		state++;
+
+update:
+	(void)close(fd);
+
+	if (update) {
+		state += 2;
+		if (!nsm_atomic_write(path, &state, sizeof(state)))
+			state = 0;
+	}
+
+out:
+	free(path);
+	return state;
+}
+
+/**
+ * nsm_update_kernel_state - attempt to post new NSM state to kernel
+ * @state: NSM state number
+ *
+ */
+void
+nsm_update_kernel_state(const int state)
+{
+	ssize_t result;
+	char buf[20];
+	int fd, len;
+
+	fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY);
+	if (fd == -1) {
+		xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m");
+		return;
+	}
+
+	len = snprintf(buf, sizeof(buf), "%d", state);
+	if (error_check(len, sizeof(buf))) {
+		xlog_warn("Failed to form NSM state number string");
+		return;
+	}
+
+	result = write(fd, buf, strlen(buf));
+	if (exact_error_check(result, strlen(buf)))
+		xlog_warn("Failed to write NSM state number: %m");
+
+	if (close(fd) == -1)
+		xlog(L_ERROR, "Failed to close NSM state file "
+				NSM_KERNEL_STATE_FILE ": %m");
+}
+
+/**
+ * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/"
+ *
+ * Returns the count of host records that were moved.
+ *
+ * Note that if any error occurs during this process, some monitor
+ * records may be left in the "sm" directory.
+ */
+unsigned int
+nsm_retire_monitored_hosts(void)
+{
+	unsigned int count = 0;
+	struct dirent *de;
+	char *path;
+	DIR *dir;
+
+	path = nsm_make_pathname(NSM_MONITOR_DIR);
+	if (path == NULL) {
+		xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR);
+		return count;
+	}
+
+	dir = opendir(path);
+	free(path);
+	if (dir == NULL) {
+		xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m");
+		return count;
+	}
+
+	while ((de = readdir(dir)) != NULL) {
+		char *src, *dst;
+
+		if (de->d_type != (unsigned char)DT_REG)
+			continue;
+		if (de->d_name[0] == '.')
+			continue;
+
+		src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name);
+		if (src == NULL) {
+			xlog_warn("Bad monitor file name, skipping");
+			continue;
+		}
+
+		dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name);
+		if (dst == NULL) {
+			free(src);
+			xlog_warn("Bad notify file name, skipping");
+			continue;
+		}
+
+		if (rename(src, dst) == -1)
+			xlog_warn("Failed to rename %s -> %s: %m",
+				src, dst);
+		else {
+			xlog(D_GENERAL, "Retired record for mon_name %s",
+					de->d_name);
+			count++;
+		}
+
+		free(dst);
+		free(src);
+	}
+
+	(void)closedir(dir);
+	return count;
+}
+
+/*
+ * nsm_priv_to_hex - convert a NSM private cookie to a hex string.
+ *
+ * @priv: buffer holding the binary NSM private cookie
+ * @buf: output buffer for NULL terminated hex string
+ * @buflen: size of output buffer
+ *
+ * Returns the length of the resulting string or 0 on error
+ */
+size_t
+nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen)
+{
+	int i, len;
+	size_t remaining = buflen;
+
+	for (i = 0; i < SM_PRIV_SIZE; i++) {
+		len = snprintf(buf, remaining, "%02x",
+				(unsigned int)(0xff & priv[i]));
+		if (error_check(len, remaining))
+			return 0;
+		buf += len;
+		remaining -= (size_t)len;
+	}
+
+	return buflen - remaining;
+}
+
+/*
+ * Returns the length in bytes of the created record.
+ */
+__attribute_noinline__
+static size_t
+nsm_create_monitor_record(char *buf, const size_t buflen,
+		const struct sockaddr *sap, const struct mon *m)
+{
+	const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
+	size_t hexlen, remaining = buflen;
+	int len;
+
+	len = snprintf(buf, remaining, "%08x %08x %08x %08x ",
+			(unsigned int)sin->sin_addr.s_addr,
+			(unsigned int)m->mon_id.my_id.my_prog,
+			(unsigned int)m->mon_id.my_id.my_vers,
+			(unsigned int)m->mon_id.my_id.my_proc);
+	if (error_check(len, remaining))
+		return 0;
+	buf += len;
+	remaining -= (size_t)len;
+
+	hexlen = nsm_priv_to_hex(m->priv, buf, remaining);
+	if (hexlen == 0)
+		return 0;
+	buf += hexlen;
+	remaining -= hexlen;
+
+	len = snprintf(buf, remaining, " %s %s\n",
+			m->mon_id.mon_name, m->mon_id.my_id.my_name);
+	if (error_check(len, remaining))
+		return 0;
+	remaining -= (size_t)len;
+
+	return buflen - remaining;
+}
+
+static _Bool
+nsm_append_monitored_host(const char *path, const char *line)
+{
+	_Bool result = false;
+	char *buf = NULL;
+	struct stat stb;
+	size_t buflen;
+	ssize_t len;
+	int fd;
+
+	if (stat(path, &stb) == -1) {
+		xlog(L_ERROR, "Failed to insert: "
+			"could not stat original file %s: %m", path);
+		goto out;
+	}
+	buflen = (size_t)stb.st_size + strlen(line);
+
+	buf = malloc(buflen + 1);
+	if (buf == NULL) {
+		xlog(L_ERROR, "Failed to insert: no memory");
+		goto out;
+	}
+	memset(buf, 0, buflen + 1);
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		xlog(L_ERROR, "Failed to insert: "
+			"could not open original file %s: %m", path);
+		goto out;
+	}
+
+	len = read(fd, buf, (size_t)stb.st_size);
+	if (exact_error_check(len, (size_t)stb.st_size)) {
+		xlog(L_ERROR, "Failed to insert: "
+			"could not read original file %s: %m", path);
+		(void)close(fd);
+		goto out;
+	}
+	(void)close(fd);
+
+	strcat(buf, line);
+
+	if (nsm_atomic_write(path, buf, buflen))
+		result = true;
+
+out:
+	free(buf);
+	return result;
+}
+
+/**
+ * nsm_insert_monitored_host - write callback data for one host to disk
+ * @hostname: C string containing a hostname
+ * @sap: sockaddr containing NLM callback address
+ * @mon: SM_MON arguments to save
+ *
+ * Returns true if successful, otherwise false if some error occurs.
+ */
+_Bool
+nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap,
+		const struct mon *m)
+{
+	static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
+	char *path;
+	_Bool result = false;
+	ssize_t len;
+	size_t size;
+	int fd;
+
+	path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname);
+	if (path == NULL) {
+		xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'",
+				hostname);
+		return false;
+	}
+
+	size = nsm_create_monitor_record(buf, sizeof(buf), sap, m);
+	if (size == 0) {
+		xlog(L_ERROR, "Failed to insert: record too long");
+		goto out;
+	}
+
+	/*
+	 * If exclusive create fails, we're adding a new line to an
+	 * existing file.
+	 */
+	fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_SYNC, S_IRUSR | S_IWUSR);
+	if (fd == -1) {
+		if (errno != EEXIST) {
+			xlog(L_ERROR, "Failed to insert: creating %s: %m", path);
+			goto out;
+		}
+
+		result = nsm_append_monitored_host(path, buf);
+		goto out;
+	}
+	result = true;
+
+	len = write(fd, buf, size);
+	if (exact_error_check(len, size)) {
+		xlog_warn("Failed to insert: writing %s: %m", path);
+		(void)unlink(path);
+		result = false;
+	}
+
+	if (close(fd) == -1) {
+		xlog(L_ERROR, "Failed to insert: closing %s: %m", path);
+		(void)unlink(path);
+		result = false;
+	}
+
+out:
+	free(path);
+	return result;
+}
+
+__attribute_noinline__
+static _Bool
+nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m)
+{
+	unsigned int i, tmp;
+	int count;
+	char *c;
+
+	c = strchr(line, '\n');
+	if (c != NULL)
+		*c = '\0';
+
+	count = sscanf(line, "%8x %8x %8x %8x ",
+			(unsigned int *)&sin->sin_addr.s_addr,
+			(unsigned int *)&m->mon_id.my_id.my_prog,
+			(unsigned int *)&m->mon_id.my_id.my_vers,
+			(unsigned int *)&m->mon_id.my_id.my_proc);
+	if (count != 4)
+		return false;
+
+	c = line + RPCARGSLEN;
+	for (i = 0; i < SM_PRIV_SIZE; i++) {
+		if (sscanf(c, "%2x", &tmp) != 1)
+			return false;
+		m->priv[i] = (char)tmp;
+		c += 2;
+	}
+
+	c++;
+	m->mon_id.mon_name = c;
+	while (*c != '\0' && *c != ' ')
+		c++;
+	if (*c != '\0')
+		*c++ = '\0';
+	while (*c == ' ')
+		c++;
+	m->mon_id.my_id.my_name = c;
+
+	return true;
+}
+
+/*
+ * Stuff a 'struct mon' with callback data, and call @func.
+ *
+ * Returns the count of in-core records created.
+ */
+static unsigned int
+nsm_read_line(const char *hostname, const time_t timestamp, char *line,
+		nsm_populate_t func)
+{
+	struct sockaddr_in sin = {
+		.sin_family		= AF_INET,
+	};
+	struct mon m;
+
+	if (!nsm_parse_line(line, &sin, &m))
+		return 0;
+
+	return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp);
+}
+
+/*
+ * Given a filename, reads data from a file under NSM_MONITOR_DIR
+ * and invokes @func so caller can populate their in-core
+ * database with this data.
+ */
+static unsigned int
+nsm_load_host(const char *directory, const char *filename, nsm_populate_t func)
+{
+	char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
+	unsigned int result = 0;
+	struct stat stb;
+	char *path;
+	FILE *f;
+
+	path = nsm_make_record_pathname(directory, filename);
+	if (path == NULL)
+		goto out_err;
+
+	if (stat(path, &stb) == -1) {
+		xlog(L_ERROR, "Failed to stat %s: %m", path);
+		goto out_freepath;
+	}
+
+	f = fopen(path, "r");
+	if (f == NULL) {
+		xlog(L_ERROR, "Failed to open %s: %m", path);
+		goto out_freepath;
+	}
+
+	while (fgets(buf, (int)sizeof(buf), f) != NULL) {
+		buf[sizeof(buf) - 1] = '\0';
+		result += nsm_read_line(filename, stb.st_mtime, buf, func);
+	}
+	if (result == 0)
+		xlog(L_ERROR, "Failed to read monitor data from %s", path);
+
+	(void)fclose(f);
+
+out_freepath:
+	free(path);
+out_err:
+	return result;
+}
+
+static unsigned int
+nsm_load_dir(const char *directory, nsm_populate_t func)
+{
+	unsigned int count = 0;
+	struct dirent *de;
+	char *path;
+	DIR *dir;
+
+	path = nsm_make_pathname(directory);
+	if (path == NULL) {
+		xlog(L_ERROR, "Failed to allocate path for directory %s",
+				directory);
+		return 0;
+	}
+
+	dir = opendir(path);
+	free(path);
+	if (dir == NULL) {
+		xlog(L_ERROR, "Failed to open directory %s: %m",
+				directory);
+		return 0;
+	}
+
+	while ((de = readdir(dir)) != NULL) {
+		if (de->d_type != (unsigned char)DT_REG)
+			continue;
+		if (de->d_name[0] == '.')
+			continue;
+
+		count += nsm_load_host(directory, de->d_name, func);
+	}
+
+	(void)closedir(dir);
+	return count;
+}
+
+/**
+ * nsm_load_monitor_list - load list of hosts to monitor
+ * @func: callback function to create entry for one host
+ *
+ * Returns the count of hosts that were found in the directory.
+ */
+unsigned int
+nsm_load_monitor_list(nsm_populate_t func)
+{
+	return nsm_load_dir(NSM_MONITOR_DIR, func);
+}
+
+/**
+ * nsm_load_notify_list - load list of hosts to notify
+ * @func: callback function to create entry for one host
+ *
+ * Returns the count of hosts that were found in the directory.
+ */
+unsigned int
+nsm_load_notify_list(nsm_populate_t func)
+{
+	return nsm_load_dir(NSM_NOTIFY_DIR, func);
+}
+
+static void
+nsm_delete_host(const char *directory, const char *hostname,
+		const char *mon_name, const char *my_name)
+{
+	char line[LINELEN + 1 + SM_MAXSTRLEN + 2];
+	char *outbuf = NULL;
+	struct stat stb;
+	char *path, *next;
+	size_t remaining;
+	FILE *f;
+
+	path = nsm_make_record_pathname(directory, hostname);
+	if (path == NULL) {
+		xlog(L_ERROR, "Bad filename, not deleting");
+		return;
+	}
+
+	if (stat(path, &stb) == -1) {
+		xlog(L_ERROR, "Failed to delete: "
+			"could not stat original file %s: %m", path);
+		goto out;
+	}
+	remaining = (size_t)stb.st_size + 1;
+
+	outbuf = malloc(remaining);
+	if (outbuf == NULL) {
+		xlog(L_ERROR, "Failed to delete: no memory");
+		goto out;
+	}
+
+	f = fopen(path, "r");
+	if (f == NULL) {
+		xlog(L_ERROR, "Failed to delete: "
+			"could not open original file %s: %m", path);
+		goto out;
+	}
+
+	/*
+	 * Walk the records in the file, and copy the non-matching
+	 * ones to our output buffer.
+	 */
+	next = outbuf;
+	while (fgets(line, (int)sizeof(line), f) != NULL) {
+		struct sockaddr_in sin;
+		struct mon m;
+		size_t len;
+
+		if (!nsm_parse_line(line, &sin, &m)) {
+			xlog(L_ERROR, "Failed to delete: "
+				"could not parse original file %s", path);
+			(void)fclose(f);
+			goto out;
+		}
+
+		if (strcmp(mon_name, m.mon_id.mon_name) == 0 &&
+			 strcmp(my_name, m.mon_id.my_id.my_name) == 0)
+			continue;
+
+		/* nsm_parse_line destroys the contents of line[], so
+		 * reconstruct the copy in our output buffer. */
+		len = nsm_create_monitor_record(next, remaining,
+					(struct sockaddr *)(char *)&sin, &m);
+		if (len == 0) {
+			xlog(L_ERROR, "Failed to delete: "
+				"could not construct output record");
+			(void)fclose(f);
+			goto out;
+		}
+		next += len;
+		remaining -= len;
+	}
+
+	(void)fclose(f);
+
+	/*
+	 * If nothing was copied when we're done, then unlink the file.
+	 * Otherwise, atomically update the contents of the file.
+	 */
+	if (next != outbuf) {
+		if (!nsm_atomic_write(path, outbuf, strlen(outbuf)))
+			xlog(L_ERROR, "Failed to delete: "
+				"could not write new file %s: %m", path);
+	} else {
+		if (unlink(path) == -1)
+			xlog(L_ERROR, "Failed to delete: "
+				"could not unlink file %s: %m", path);
+	}
+
+out:
+	free(outbuf);
+	free(path);
+}
+
+/**
+ * nsm_delete_monitored_host - delete on-disk record for monitored host
+ * @hostname: '\0'-terminated C string containing hostname of record to delete
+ * @mon_name: '\0'-terminated C string containing monname of record to delete
+ * @my_name: '\0'-terminated C string containing myname of record to delete
+ *
+ */
+void
+nsm_delete_monitored_host(const char *hostname, const char *mon_name,
+		const char *my_name)
+{
+	nsm_delete_host(NSM_MONITOR_DIR, hostname, mon_name, my_name);
+}
+
+/**
+ * nsm_delete_notified_host - delete on-disk host record after notification
+ * @hostname: '\0'-terminated C string containing hostname of record to delete
+ * @mon_name: '\0'-terminated C string containing monname of record to delete
+ * @my_name: '\0'-terminated C string containing myname of record to delete
+ *
+ */
+void
+nsm_delete_notified_host(const char *hostname, const char *mon_name,
+		const char *my_name)
+{
+	nsm_delete_host(NSM_NOTIFY_DIR, hostname, mon_name, my_name);
+}
diff --git a/support/nsm/rpc.c b/support/nsm/rpc.c
new file mode 100644
index 0000000..4e5f40e
--- /dev/null
+++ b/support/nsm/rpc.c
@@ -0,0 +1,534 @@
+/*
+ * Copyright 2009 Oracle.  All rights reserved.
+ *
+ * This file is part of nfs-utils.
+ *
+ * nfs-utils is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * nfs-utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * NSM for Linux.
+ *
+ * Instead of using ONC or TI RPC library calls, statd constructs
+ * RPC calls directly in socket buffers.  This allows a single
+ * socket to be concurrently shared among several different RPC
+ * programs and versions using a simple RPC request dispatcher.
+ *
+ * This file contains the details of RPC header and call
+ * construction and reply parsing, and a method for creating a
+ * socket for use with these functions.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif	/* HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <time.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <rpc/rpc.h>
+#include <rpc/pmap_prot.h>
+#include <rpc/pmap_rmt.h>
+
+#ifdef HAVE_LIBTIRPC
+#include <netconfig.h>
+#include <rpc/rpcb_prot.h>
+#endif	/* HAVE_LIBTIRPC */
+
+#include "xlog.h"
+#include "nfsrpc.h"
+#include "nsm.h"
+#include "sm_inter.h"
+
+/*
+ * Returns a fresh XID appropriate for RPC over UDP -- never zero.
+ */
+static uint32_t
+nsm_next_xid(void)
+{
+	static uint32_t nsm_xid = 0;
+	struct timeval now;
+
+	if (nsm_xid == 0) {
+		(void)gettimeofday(&now, NULL);
+		nsm_xid = (uint32_t)getpid() ^
+				(uint32_t)now.tv_sec ^ (uint32_t)now.tv_usec;
+	}
+
+	return nsm_xid++;
+}
+
+/*
+ * Select a fresh XID and construct an RPC header in @mesg.
+ * Always use AUTH_NULL credentials and verifiers.
+ *
+ * Returns the new XID.
+ */
+static uint32_t
+nsm_init_rpc_header(const rpcprog_t program, const rpcvers_t version,
+			const rpcproc_t procedure, struct rpc_msg *mesg)
+{
+	struct call_body *cb = &mesg->rm_call;
+	uint32_t xid = nsm_next_xid();
+
+	memset(mesg, 0, sizeof(*mesg));
+
+	mesg->rm_xid = (unsigned long)xid;
+	mesg->rm_direction = CALL;
+
+	cb->cb_rpcvers = RPC_MSG_VERSION;
+	cb->cb_prog = program;
+	cb->cb_vers = version;
+	cb->cb_proc = procedure;
+
+	cb->cb_cred.oa_flavor = AUTH_NULL;
+	cb->cb_cred.oa_base = (caddr_t) NULL;
+	cb->cb_cred.oa_length = 0;
+	cb->cb_verf.oa_flavor = AUTH_NULL;
+	cb->cb_verf.oa_base = (caddr_t) NULL;
+	cb->cb_verf.oa_length = 0;
+
+	return xid;
+}
+
+/*
+ * Initialize the network send buffer and XDR memory for encoding.
+ */
+static void
+nsm_init_xdrmem(char *msgbuf, const unsigned int msgbuflen,
+		XDR *xdrp)
+{
+	memset(msgbuf, 0, (size_t)msgbuflen);
+	memset(xdrp, 0, sizeof(*xdrp));
+	xdrmem_create(xdrp, msgbuf, msgbuflen, XDR_ENCODE);
+}
+
+/*
+ * Send a completed RPC call on a socket.
+ *
+ * Returns true if all the bytes were sent successfully; otherwise
+ * false if any error occurred.
+ */
+static _Bool
+nsm_rpc_sendto(const int sock, const struct sockaddr *sap,
+			const socklen_t salen, XDR *xdrs, void *buf)
+{
+	const size_t buflen = (size_t)xdr_getpos(xdrs);
+	ssize_t err;
+
+	err = sendto(sock, buf, buflen, 0, sap, salen);
+	if ((err < 0) || ((size_t)err != buflen)) {
+		xlog(L_ERROR, "%s: sendto failed: %m", __func__);
+		return false;
+	}
+	return true;
+}
+
+/**
+ * nsm_xmit_getport - post a PMAP_GETPORT call on a socket descriptor
+ * @sock: datagram socket descriptor
+ * @sin: pointer to AF_INET socket address of server
+ * @program: RPC program number to query
+ * @version: RPC version number to query
+ *
+ * Send a PMAP_GETPORT call to the portmap daemon at @sin using
+ * socket descriptor @sock.  This request queries the RPC program
+ * [program, version, IPPROTO_UDP].
+ *
+ * NB: PMAP_GETPORT works only for IPv4 hosts.  This implementation
+ *     works only over UDP, and queries only UDP registrations.
+ *
+ * Returns the XID of the call, or zero if an error occurred.
+ */
+uint32_t
+nsm_xmit_getport(const int sock, const struct sockaddr_in *sin,
+			const unsigned long program,
+			const unsigned long version)
+{
+	char msgbuf[NSM_MAXMSGSIZE];
+	struct sockaddr_in addr;
+	struct rpc_msg mesg;
+	_Bool sent = false;
+	struct pmap parms = {
+		.pm_prog	= program,
+		.pm_vers	= version,
+		.pm_prot	= (unsigned long)IPPROTO_UDP,
+	};
+	uint32_t xid;
+	XDR xdr;
+
+	xlog(D_CALL, "Sending PMAP_GETPORT for %u, %u, udp", program, version);
+
+	nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr);
+	xid = nsm_init_rpc_header(PMAPPROG, PMAPVERS,
+					(rpcproc_t)PMAPPROC_GETPORT, &mesg);
+
+	addr = *sin;
+	addr.sin_port = htons(PMAPPORT);
+
+	if (xdr_callmsg(&xdr, &mesg) == TRUE &&
+	    xdr_pmap(&xdr, &parms) == TRUE)
+		sent = nsm_rpc_sendto(sock, (struct sockaddr *)(char *)&addr,
+					(socklen_t)sizeof(addr), &xdr, msgbuf);
+	else
+		xlog(L_ERROR, "%s: can't encode PMAP_GETPORT call", __func__);
+
+	xdr_destroy(&xdr);
+
+	if (sent == false)
+		return 0;
+	return xid;
+}
+
+/**
+ * nsm_xmit_getaddr - post an RPCB_GETADDR call on a socket descriptor
+ * @sock: datagram socket descriptor
+ * @sin: pointer to AF_INET6 socket address of server
+ * @program: RPC program number to query
+ * @version: RPC version number to query
+ *
+ * Send an RPCB_GETADDR call to the rpcbind daemon at @sap using
+ * socket descriptor @sock.  This request queries the RPC program
+ * [program, version, "udp6"].
+ *
+ * NB: RPCB_GETADDR works for both IPv4 and IPv6 hosts.  This
+ *     implementation works only over UDP and AF_INET6, and queries
+ *     only "udp6" registrations.
+ *
+ * Returns the XID of the call, or zero if an error occurred.
+ */
+#ifdef HAVE_LIBTIRPC
+uint32_t
+nsm_xmit_getaddr(const int sock, const struct sockaddr_in6 *sin6,
+			const rpcprog_t program, const rpcvers_t version)
+{
+	char msgbuf[NSM_MAXMSGSIZE];
+	struct sockaddr_in6 addr;
+	struct rpc_msg mesg;
+	_Bool sent = false;
+	struct rpcb parms = {
+		.r_prog		= program,
+		.r_vers		= version,
+		.r_netid	= "udp6",
+		.r_owner	= "",
+	};
+	uint32_t xid;
+	XDR xdr;
+
+	xlog(D_CALL, "Sending RPCB_GETADDR for %u, %u, udp6", program, version);
+
+	nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr);
+	xid = nsm_init_rpc_header(RPCBPROG, RPCBVERS,
+					(rpcproc_t)RPCBPROC_GETADDR, &mesg);
+
+	addr = *sin6;
+	addr.sin6_port = htons(PMAPPORT);
+	parms.r_addr = nfs_sockaddr2universal((struct sockaddr *)(char *)&addr);
+	if (parms.r_addr == NULL) {
+		xlog(L_ERROR, "%s: can't encode socket address", __func__);
+		return 0;
+	}
+
+	if (xdr_callmsg(&xdr, &mesg) == TRUE &&
+	    xdr_rpcb(&xdr, &parms) == TRUE)
+		sent = nsm_rpc_sendto(sock, (struct sockaddr *)(char *)&addr,
+					(socklen_t)sizeof(addr), &xdr, msgbuf);
+	else
+		xlog(L_ERROR, "%s: can't encode RPCB_GETADDR call", __func__);
+
+	xdr_destroy(&xdr);
+	free(parms.r_addr);
+
+	if (sent == false)
+		return 0;
+	return xid;
+}
+#else	/* !HAVE_LIBTIRPC */
+uint32_t
+nsm_xmit_getaddr(const int sock __attribute__((unused)),
+			const struct sockaddr_in6 *sin6 __attribute__((unused)),
+			const rpcprog_t program __attribute__((unused)),
+			const rpcvers_t version __attribute__((unused)))
+{
+	return 0;
+}
+#endif	/* !HAVE_LIBTIRPC */
+
+/**
+ * nsm_xmit_rpcbind - post an rpcbind request
+ * @sock: datagram socket descriptor
+ * @sap: pointer to socket address of server
+ * @program: RPC program number to query
+ * @version: RPC version number to query
+ *
+ * Send an rpcbind query to the rpcbind daemon at @sap using
+ * socket descriptor @sock.
+ *
+ * NB: This implementation works only over UDP, but can query IPv4 or IPv6
+ *     hosts.  It queries only UDP registrations.
+ *
+ * Returns the XID of the call, or zero if an error occurred.
+ */
+uint32_t
+nsm_xmit_rpcbind(const int sock, const struct sockaddr *sap,
+			const rpcprog_t program, const rpcvers_t version)
+{
+	switch (sap->sa_family) {
+	case AF_INET:
+		return nsm_xmit_getport(sock, (const struct sockaddr_in *)sap,
+						program, version);
+	case AF_INET6:
+		return nsm_xmit_getaddr(sock, (const struct sockaddr_in6 *)sap,
+						program, version);
+	}
+	return 0;
+}
+
+/**
+ * nsm_xmit_notify - post an NSMPROC_NOTIFY call on a socket descriptor
+ * @sock: datagram socket descriptor
+ * @sap: pointer to socket address of peer to notify (port already filled in)
+ * @salen: length of socket address
+ * @program: RPC program number to use
+ * @mon_name: mon_name of local peer (ie the rebooting system)
+ * @state: state of local peer
+ *
+ * Send an NSMPROC_NOTIFY call to the peer at @sap using socket descriptor @sock.
+ * This request notifies the peer that we have rebooted.
+ *
+ * NB: This implementation works only over UDP, but supports both AF_INET
+ *     and AF_INET6.
+ *
+ * Returns the XID of the call, or zero if an error occurred.
+ */
+uint32_t
+nsm_xmit_notify(const int sock, const struct sockaddr *sap,
+			const socklen_t salen, const rpcprog_t program,
+			const char *mon_name, const int state)
+{
+	char msgbuf[NSM_MAXMSGSIZE];
+	struct stat_chge state_change;
+	struct rpc_msg mesg;
+	_Bool sent = false;
+	uint32_t xid;
+	XDR xdr;
+
+	state_change.mon_name = strdup(mon_name);
+	if (state_change.mon_name == NULL) {
+		xlog(L_ERROR, "%s: no memory", __func__);
+		return 0;
+	}
+	state_change.state = state;
+
+	xlog(D_CALL, "Sending SM_NOTIFY for %s", mon_name);
+
+	nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr);
+	xid = nsm_init_rpc_header(program, SM_VERS, SM_NOTIFY, &mesg);
+
+	if (xdr_callmsg(&xdr, &mesg) == TRUE &&
+	    xdr_stat_chge(&xdr, &state_change) == TRUE)
+		sent = nsm_rpc_sendto(sock, sap, salen, &xdr, msgbuf);
+	else
+		xlog(L_ERROR, "%s: can't encode NSMPROC_NOTIFY call",
+				__func__);
+
+	xdr_destroy(&xdr);
+	free(state_change.mon_name);
+
+	if (sent == false)
+		return 0;
+	return xid;
+}
+
+/**
+ * nsm_xmit_nlmcall - post an unnamed call to local NLM on a socket descriptor
+ * @sock: datagram socket descriptor
+ * @sap: address/port of NLM service to contact
+ * @salen: size of @sap
+ * @m: callback data defining RPC call to make
+ * @state: state of rebooting host
+ *
+ * Send an unnamed call (previously requested via NSMPROC_MON) to the
+ * specified local UDP-based RPC service using socket descriptor @sock.
+ *
+ * NB: This implementation works only over UDP, but supports both AF_INET
+ *     and AF_INET6.
+ *
+ * Returns the XID of the call, or zero if an error occurred.
+ */
+uint32_t
+nsm_xmit_nlmcall(const int sock, const struct sockaddr *sap,
+			const socklen_t salen, const struct mon *m,
+			const int state)
+{
+	const struct my_id *id = &m->mon_id.my_id;
+	char msgbuf[NSM_MAXMSGSIZE];
+	struct status new_status;
+	struct rpc_msg mesg;
+	_Bool sent = false;
+	uint32_t xid;
+	XDR xdr;
+
+	xlog(D_CALL, "Sending NLM downcall for %s", m->mon_id.mon_name);
+
+	nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr);
+	xid = nsm_init_rpc_header((rpcprog_t)id->my_prog,
+					(rpcvers_t)id->my_vers,
+					(rpcproc_t)id->my_proc, &mesg);
+
+	new_status.mon_name = m->mon_id.mon_name;
+	new_status.state = state;
+	memcpy(&new_status.priv, &m->priv, sizeof(new_status.priv));
+
+	if (xdr_callmsg(&xdr, &mesg) == TRUE &&
+	    xdr_status(&xdr, &new_status) == TRUE)
+		sent = nsm_rpc_sendto(sock, sap, salen, &xdr, msgbuf);
+	else
+		xlog(L_ERROR, "%s: can't encode NLM downcall", __func__);
+
+	xdr_destroy(&xdr);
+
+	if (sent == false)
+		return 0;
+	return xid;
+}
+
+/**
+ * nsm_parse_reply - parse and validate the header in an RPC reply
+ * @xdrs: pointer to XDR
+ *
+ * Returns the XID of the reply, or zero if an error occurred.
+ */
+uint32_t
+nsm_parse_reply(XDR *xdrs)
+{
+	struct rpc_msg mesg = {
+		.rm_reply.rp_acpt.ar_results.proc	= (xdrproc_t)xdr_void,
+	};
+	uint32_t xid;
+
+	if (xdr_replymsg(xdrs, &mesg) == FALSE) {
+		xlog(L_ERROR, "%s: can't decode RPC reply", __func__);
+		return 0;
+	}
+	xid = (uint32_t)mesg.rm_xid;
+
+	if (mesg.rm_reply.rp_stat != MSG_ACCEPTED) {
+		xlog(L_ERROR, "%s: [0x%x] RPC status %d",
+			__func__, xid, mesg.rm_reply.rp_stat);
+		return 0;
+	}
+
+	if (mesg.rm_reply.rp_acpt.ar_stat != SUCCESS) {
+		xlog(L_ERROR, "%s: [0x%x] RPC accept status %d",
+			__func__, xid, mesg.rm_reply.rp_acpt.ar_stat);
+		return 0;
+	}
+
+	return xid;
+}
+
+/**
+ * nsm_recv_getport - parse PMAP_GETPORT reply
+ * @xdrs: pointer to XDR
+ *
+ * Returns the port number from the RPC reply, or zero
+ * if an error occurred.
+ */
+unsigned long
+nsm_recv_getport(XDR *xdrs)
+{
+	unsigned long port = 0;
+
+	if (xdr_u_long(xdrs, &port) == FALSE)
+		xlog(L_ERROR, "%s: can't decode pmap reply",
+			__func__);
+	if (port > UINT16_MAX) {
+		xlog(L_ERROR, "%s: bad port number",
+			__func__);
+		port = 0;
+	}
+
+	xlog(D_CALL, "Received PMAP_GETPORT result: %lu", port);
+	return port;
+}
+
+/**
+ * nsm_recv_getaddr - parse RPCB_GETADDR reply
+ * @xdrs: pointer to XDR
+ *
+ * Returns the port number from the RPC reply, or zero
+ * if an error occurred.
+ */
+uint16_t
+nsm_recv_getaddr(XDR *xdrs)
+{
+	char *uaddr = NULL;
+	int port;
+
+	if (xdr_wrapstring(xdrs, &uaddr) == FALSE)
+		xlog(L_ERROR, "%s: can't decode rpcb reply",
+			__func__);
+
+	if ((uaddr == NULL) || (uaddr[0] == '\0')) {
+		xlog(D_CALL, "Received RPCB_GETADDR result: "
+				"program not registered");
+		return 0;
+	}
+
+	port = nfs_universal2port(uaddr);
+
+	xdr_free((xdrproc_t)xdr_wrapstring, (char *)&uaddr);
+
+	if (port < 0 || port > UINT16_MAX) {
+		xlog(L_ERROR, "%s: bad port number",
+			__func__);
+		return 0;
+	}
+
+	xlog(D_CALL, "Received RPCB_GETADDR result: %d", port);
+	return (uint16_t)port;
+}
+
+/**
+ * nsm_recv_rpcbind - parse rpcbind reply
+ * @af: address family of reply
+ * @xdrs: pointer to XDR
+ *
+ * Returns the port number from the RPC reply, or zero
+ * if an error occurred.
+ */
+uint16_t
+nsm_recv_rpcbind(const sa_family_t family, XDR *xdrs)
+{
+	switch (family) {
+	case AF_INET:
+		return (uint16_t)nsm_recv_getport(xdrs);
+	case AF_INET6:
+		return nsm_recv_getaddr(xdrs);
+	}
+	return 0;
+}
diff --git a/support/nsm/sm_inter.x b/support/nsm/sm_inter.x
new file mode 100644
index 0000000..d8e0ad7
--- /dev/null
+++ b/support/nsm/sm_inter.x
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 1986 Sun Microsystems, Inc.
+ * Modified by Jeffrey A. Uphoff, 1995, 1997-1999.
+ * Modified by Olaf Kirch, 1996.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+/*
+ * Copyright (c) 2009, Sun Microsystems, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of Sun Microsystems, Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
+ */
+
+/*
+ * Status monitor protocol specification
+ */
+
+#ifdef RPC_CLNT
+%#include <string.h>
+#endif
+
+program SM_PROG { 
+	version SM_VERS  {
+		/* res_stat = stat_succ if status monitor agrees to monitor */
+		/* res_stat = stat_fail if status monitor cannot monitor */
+		/* if res_stat == stat_succ, state = state number of site sm_name */
+		struct sm_stat_res			 SM_STAT(struct sm_name) = 1;
+
+		/* res_stat = stat_succ if status monitor agrees to monitor */
+		/* res_stat = stat_fail if status monitor cannot monitor */
+		/* stat consists of state number of local site */
+		struct sm_stat_res			 SM_MON(struct mon) = 2;
+
+		/* stat consists of state number of local site */
+		struct sm_stat				 SM_UNMON(struct mon_id) = 3;
+
+		/* stat consists of state number of local site */
+		struct sm_stat				 SM_UNMON_ALL(struct my_id) = 4;
+
+		void					 SM_SIMU_CRASH(void) = 5;
+
+		void					 SM_NOTIFY(struct stat_chge) = 6;
+
+	} = 1;
+} = 100024;
+
+const	SM_MAXSTRLEN = 1024;
+const	SM_PRIV_SIZE = 16;
+
+struct sm_name {
+	string mon_name<SM_MAXSTRLEN>;
+};
+
+struct my_id {
+	string	 my_name<SM_MAXSTRLEN>;		/* name of the site iniates the monitoring request*/
+	int	my_prog;			/* rpc program # of the requesting process */
+	int	my_vers;			/* rpc version # of the requesting process */
+	int	my_proc;			/* rpc procedure # of the requesting process */
+};
+
+struct mon_id {
+	string	mon_name<SM_MAXSTRLEN>;		/* name of the site to be monitored */
+	struct my_id my_id;
+};
+
+
+struct mon {
+	struct mon_id mon_id;
+	opaque priv[SM_PRIV_SIZE]; 		/* private information to store at monitor for requesting process */
+};
+
+struct stat_chge {
+	string	mon_name<SM_MAXSTRLEN>;		/* name of the site that had the state change */
+	int     state;
+};
+
+/*
+ * state # of status monitor monitonically increases each time
+ * status of the site changes:
+ * an even number (>= 0) indicates the site is down and
+ * an odd number (> 0) indicates the site is up;
+ */
+struct sm_stat {
+	int state;		/* state # of status monitor */
+};
+
+enum res {
+	stat_succ = 0,		/* status monitor agrees to monitor */
+	stat_fail = 1		/* status monitor cannot monitor */
+};
+
+struct sm_stat_res {
+	res res_stat;
+	int state;
+};
+
+/* 
+ * structure of the status message sent back by the status monitor
+ * when monitor site status changes
+ */
+struct status {
+	string mon_name<SM_MAXSTRLEN>;
+	int state;
+	opaque priv[SM_PRIV_SIZE]; /* stored private information */
+};
+
+%#define SM_INTER_X
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..faa8197
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,13 @@
+## Process this file with automake to produce Makefile.in
+
+check_PROGRAMS = statdb_dump
+statdb_dump_SOURCES = statdb_dump.c
+
+statdb_dump_LDADD = ../support/nfs/libnfs.a \
+		    ../support/nsm/libnsm.a $(LIBCAP)
+
+SUBDIRS = nsm_client
+
+MAINTAINERCLEANFILES = Makefile.in
+
+TESTS = t0001-statd-basic-mon-unmon.sh
diff --git a/tests/nsm_client/Makefile.am b/tests/nsm_client/Makefile.am
new file mode 100644
index 0000000..4bf0a45
--- /dev/null
+++ b/tests/nsm_client/Makefile.am
@@ -0,0 +1,45 @@
+## Process this file with automake to produce Makefile.in
+
+GENFILES_CLNT	= nlm_sm_inter_clnt.c
+GENFILES_SVC	= nlm_sm_inter_svc.c
+GENFILES_XDR	= nlm_sm_inter_xdr.c
+GENFILES_H	= nlm_sm_inter.h
+
+GENFILES	= $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H)
+
+
+check_PROGRAMS	= nsm_client
+nsm_client_SOURCES = $(GENFILES) nsm_client.c
+
+BUILT_SOURCES = $(GENFILES)
+nsm_client_LDADD = ../../support/nfs/libnfs.a \
+		   ../../support/nsm/libnsm.a $(LIBCAP)
+
+if CONFIG_RPCGEN
+RPCGEN	= $(top_builddir)/tools/rpcgen/rpcgen
+$(RPCGEN):
+	make -C ../../tools/rpcgen all
+else
+RPCGEN = @RPCGEN_PATH@
+endif
+
+$(GENFILES_CLNT): %_clnt.c: %.x $(RPCGEN)
+	test -f $@ && rm -rf $@ || true
+	$(RPCGEN) -l -o $@ $<
+
+$(GENFILES_SVC): %_svc.c: %.x $(RPCGEN)
+	test -f $@ && rm -rf $@ || true
+	$(RPCGEN) -m -o $@ $<
+
+$(GENFILES_XDR): %_xdr.c: %.x $(RPCGEN)
+	test -f $@ && rm -rf $@ || true
+	$(RPCGEN) -c -o $@ $<
+
+$(GENFILES_H): %.h: %.x $(RPCGEN)
+	test -f $@ && rm -rf $@ || true
+	$(RPCGEN) -h -o $@ $<
+
+MAINTAINERCLEANFILES = Makefile.in
+
+CLEANFILES = $(GENFILES)
+
diff --git a/tests/nsm_client/README b/tests/nsm_client/README
new file mode 100644
index 0000000..85379dd
--- /dev/null
+++ b/tests/nsm_client/README
@@ -0,0 +1,12 @@
+The nsm_client program is intended for testing statd. It has the ability
+to act as a synthetic NSM client for sending artificial NSM calls to any
+host you choose.
+
+It also has an NLM simulator that implements the call that statd uses to
+communicate with lockd. The daemon simulator will start itself up,
+register as an NLM service and listen for "downcalls" from statd. When
+it gets one, it will log a message.
+
+Note that lockd will need to be down when using the daemon simulator. It
+also does not implement the entire NLM protocol and is only really
+useful for testing statd's downcall.
diff --git a/tests/nsm_client/nlm_sm_inter.x b/tests/nsm_client/nlm_sm_inter.x
new file mode 100644
index 0000000..95fa326
--- /dev/null
+++ b/tests/nsm_client/nlm_sm_inter.x
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, 1996.
+ * Modified by H.J. Lu, 1998.
+ * Modified by Jeff Layton, 2010.
+ *
+ * NLM similator for Linux
+ */
+
+#ifdef RPC_CLNT
+%#include <string.h>
+#endif
+
+/*
+ * statd rejects monitor registrations for any non-lockd services, so pretend
+ * to be lockd when testing. Furthermore, the only call we care about from
+ * statd is #16, which is the downcall to notify the kernel of a host's status
+ * change.
+ */
+program NLM_SM_PROG {
+	/* version 3 of the NLM protocol */
+	version NLM_SM_VERS3 {
+		void	 NLM_SM_NOTIFY(struct nlm_sm_notify) = 16;
+	} = 3;
+
+	/* version 2 of NLM protocol */
+	version NLM_SM_VERS4 {
+		void	 NLM_SM_NOTIFY(struct nlm_sm_notify) = 16;
+	} = 4;
+} = 100021;
+
+const  SM_MAXSTRLEN = 1024;
+const  SM_PRIV_SIZE = 16;
+
+/*
+ * structure of the status message sent back by the status monitor
+ * when monitor site status changes
+ */
+struct nlm_sm_notify {
+	string mon_name<SM_MAXSTRLEN>;
+	int state;
+	opaque priv[SM_PRIV_SIZE]; /* stored private information */
+};
diff --git a/tests/nsm_client/nsm_client.c b/tests/nsm_client/nsm_client.c
new file mode 100644
index 0000000..0d1159a
--- /dev/null
+++ b/tests/nsm_client/nsm_client.c
@@ -0,0 +1,465 @@
+/*
+ * nsm_client.c -- synthetic client and lockd simulator for testing statd
+ *
+ * Copyright (C) 2010  Red Hat, Jeff Layton <jlayton@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Very loosely based on "simulator.c" in the statd directory. Original
+ * copyright for that program follows:
+ *
+ * Copyright (C) 1995-1997, 1999 Jeffrey A. Uphoff
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <signal.h>
+#include <string.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+#include <rpcmisc.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "nfslib.h"
+#include "nfsrpc.h"
+#include "nsm.h"
+#include "sm_inter.h"
+#include "nlm_sm_inter.h"
+#include "sockaddr.h"
+#include "xcommon.h"
+
+static void daemon_simulator(void);
+static void sim_killer(int sig);
+static int nsm_client_crash(char *);
+static int nsm_client_mon(char *, char *, char *, char *, int, int);
+static int nsm_client_stat(char *, char *);
+static int nsm_client_notify(char *, char *, char *);
+static int nsm_client_unmon(char *, char *, char *, int, int);
+static int nsm_client_unmon_all(char *, char *, int, int);
+
+extern void nlm_sm_prog_4(struct svc_req *rqstp, register SVCXPRT *transp);
+extern void svc_exit(void);
+
+/*
+ * default to 15 retransmit interval, which seems to be the default for
+ * UDP clients w/ legacy glibc RPC
+ */
+static struct timeval retrans_interval =
+{
+	.tv_sec = 15,
+};
+
+static struct option longopts[] =
+{
+	{ "help", 0, 0, 'h' },
+	{ "host", 0, 0, 'H' },
+	{ "name", 1, 0, 'n' },
+	{ "program", 1, 0, 'P' },
+	{ "version", 1, 0, 'v' },
+	{ NULL, 0, 0, 0 },
+};
+
+static int
+usage(char *program)
+{
+	printf("Usage:\n");
+	printf("%s [options] <command> [arg]...\n", program);
+	printf("where command is one of these with the specified args:\n");
+	printf("crash\t\t\t\ttell host to simulate crash\n");
+	printf("daemon\t\t\t\t\tstart up lockd daemon simulator\n");
+	printf("notify <mon_name> <state>\tsend a reboot notification to host\n");
+	printf("stat <mon_name>\t\t\tget status of <mon_name> on host\n");
+	printf("unmon_all\t\t\ttell host to unmon everything\n");
+	printf("unmon <mon_name>\t\t\ttell host to unmon <mon_name>\n");
+	printf("mon <mon_name> <cookie>\t\ttell host to monitor <mon_name> with private <cookie>\n");
+	return 1;
+}
+
+static int
+hex2bin(char *dst, size_t dstlen, char *src)
+{
+	int i;
+	unsigned int tmp;
+
+	for (i = 0; *src && i < dstlen; i++) {
+		if (sscanf(src, "%2x", &tmp) != 1)
+			return 0;
+		dst[i] = tmp;
+		src++;
+		if (!*src)
+			break;
+		src++;
+	}
+
+	return 1;
+}
+
+static void
+bin2hex(char *dst, char *src, size_t srclen)
+{
+	int i;
+
+	for (i = 0; i < srclen; i++)
+		dst += sprintf(dst, "%02x", 0xff & src[i]);
+}
+
+int
+main(int argc, char **argv)
+{
+	int arg, err = 0;
+	int remaining_args;
+	char my_name[NI_MAXHOST], host[NI_MAXHOST];
+	char cookie[SM_PRIV_SIZE];
+	int my_prog = NLM_SM_PROG;
+	int my_vers = NLM_SM_VERS4;
+
+	my_name[0] = '\0';
+	host[0] = '\0';
+
+	while ((arg = getopt_long(argc, argv, "hHn:P:v:", longopts,
+				  NULL)) != EOF) {
+		switch (arg) {
+		case 'H':
+			strncpy(host, optarg, sizeof(host));
+		case 'n':
+			strncpy(my_name, optarg, sizeof(my_name));
+		case 'P':
+			my_prog = atoi(optarg);
+		case 'v':
+			my_vers = atoi(optarg);
+		}
+	}
+
+	remaining_args = argc - optind;
+	if (remaining_args <= 0)
+		usage(argv[0]);
+
+	if (!my_name[0])
+		gethostname(my_name, sizeof(my_name));
+	if (!host[0])
+		strncpy(host, "127.0.0.1", sizeof(host));
+
+	if (!strcasecmp(argv[optind], "daemon")) {
+		daemon_simulator();
+	} else if (!strcasecmp(argv[optind], "crash")) {
+		err = nsm_client_crash(host);
+	} else if (!strcasecmp(argv[optind], "stat")) {
+		if (remaining_args < 2)
+			usage(argv[0]);
+		err = nsm_client_stat(host, argv[optind + 2]);
+	} else if (!strcasecmp(argv[optind], "unmon_all")) {
+		err = nsm_client_unmon_all(host, my_name, my_prog, my_vers);
+	} else if (!strcasecmp(argv[optind], "unmon")) {
+		if (remaining_args < 2)
+			usage(argv[0]);
+		err = nsm_client_unmon(host, argv[optind + 1], my_name, my_prog,
+					my_vers);
+	} else if (!strcasecmp(argv[optind], "notify")) {
+		if (remaining_args < 2)
+			usage(argv[0]);
+		err = nsm_client_notify(host, argv[optind + 1],
+					argv[optind + 2]);
+	} else if (!strcasecmp(argv[optind], "mon")) {
+		if (remaining_args < 2)
+			usage(argv[0]);
+
+		memset(cookie, '\0', SM_PRIV_SIZE);
+		if (!hex2bin(cookie, sizeof(cookie), argv[optind + 2])) {
+			fprintf(stderr, "SYS:%d\n", EINVAL);
+			printf("Unable to convert hex cookie %s to binary.\n",
+				argv[optind + 2]);
+			return 1;
+		}
+
+		err = nsm_client_mon(host, argv[optind + 1], cookie, my_name,
+					my_prog, my_vers);
+	} else {
+		err = usage(argv[0]);
+	}
+
+	return err;
+}
+
+static CLIENT *
+nsm_client_get_rpcclient(const char *node)
+{
+	unsigned short		port;
+	struct addrinfo		*ai;
+	struct addrinfo		hints = { .ai_flags	= AI_ADDRCONFIG };
+	int			err;
+	CLIENT			*client = NULL;
+
+#ifndef IPV6_ENABLED
+	hints.ai_family	= AF_INET;
+#endif /* IPV6_ENABLED */
+
+	/* FIXME: allow support for providing port? */
+	err = getaddrinfo(node, NULL, &hints, &ai);
+	if (err) {
+		fprintf(stderr, "EAI:%d\n", err);
+		if (err == EAI_SYSTEM)
+			fprintf(stderr, "SYS:%d\n", errno);
+		printf("Unable to translate host to address: %s\n",
+			err == EAI_SYSTEM ? strerror(errno) :
+			gai_strerror(err));
+		return client;
+	}
+
+	/* FIXME: allow for TCP too? */
+	port = nfs_getport(ai->ai_addr, ai->ai_addrlen, SM_PROG,
+			   SM_VERS, IPPROTO_UDP);
+	if (!port) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		printf("Unable to determine port for service\n");
+		goto out;
+	}
+
+	nfs_set_port(ai->ai_addr, port);
+
+	client = nfs_get_rpcclient(ai->ai_addr, ai->ai_addrlen, IPPROTO_UDP,
+				   SM_PROG, SM_VERS, &retrans_interval);
+	if (!client) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		printf("RPC client creation failed\n");
+	}
+out:
+	freeaddrinfo(ai);
+	return client;
+}
+
+static int
+nsm_client_mon(char *calling, char *monitoring, char *cookie, char *my_name,
+		int my_prog, int my_vers)
+{
+	CLIENT *client;
+	sm_stat_res *result;
+	mon mon;
+	int err = 0;
+
+	printf("Calling %s (as %s) to monitor %s\n", calling, my_name,
+		monitoring);
+
+	if ((client = nsm_client_get_rpcclient(calling)) == NULL)
+		return 1;
+
+	memcpy(mon.priv, cookie, SM_PRIV_SIZE);
+	mon.mon_id.my_id.my_name = my_name;
+	mon.mon_id.my_id.my_prog = my_prog;
+	mon.mon_id.my_id.my_vers = my_vers;
+	mon.mon_id.my_id.my_proc = NLM_SM_NOTIFY;
+	mon.mon_id.mon_name = monitoring;
+
+	if (!(result = sm_mon_1(&mon, client))) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		printf("%s\n", clnt_sperror(client, "sm_mon_1"));
+		err = 1;
+		goto mon_out;
+	}
+
+	printf("SM_MON request %s, state: %d\n",
+		result->res_stat == stat_succ ? "successful" : "failed",
+		result->state);
+
+	if (result->res_stat != stat_succ) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		err = 1;
+	}
+
+mon_out:
+	clnt_destroy(client);
+	return err;
+}
+
+static int
+nsm_client_unmon(char *calling, char *unmonitoring, char *my_name, int my_prog,
+		int my_vers)
+{
+	CLIENT *client;
+	sm_stat *result;
+	mon_id mon_id;
+	int err = 0;
+
+	printf("Calling %s (as %s) to unmonitor %s\n", calling, my_name,
+		unmonitoring);
+
+	if ((client = nsm_client_get_rpcclient(calling)) == NULL)
+		return 1;
+
+	mon_id.my_id.my_name = my_name;
+	mon_id.my_id.my_prog = my_prog;
+	mon_id.my_id.my_vers = my_vers;
+	mon_id.my_id.my_proc = NLM_SM_NOTIFY;
+	mon_id.mon_name = unmonitoring;
+
+	if (!(result = sm_unmon_1(&mon_id, client))) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		printf("%s\n", clnt_sperror(client, "sm_unmon_1"));
+		err = 1;
+		goto unmon_out;
+	}
+
+	printf("SM_UNMON state: %d\n", result->state);
+
+unmon_out:
+	clnt_destroy(client);
+	return err;
+}
+
+static int
+nsm_client_unmon_all(char *calling, char *my_name, int my_prog, int my_vers)
+{
+	CLIENT *client;
+	sm_stat *result;
+	my_id my_id;
+	int err = 0;
+
+	printf("Calling %s (as %s) to unmonitor all hosts\n", calling, my_name);
+
+	if ((client = nsm_client_get_rpcclient(calling)) == NULL) {
+		printf("RPC client creation failed\n");
+		return 1;
+	}
+
+	my_id.my_name = my_name;
+	my_id.my_prog = my_prog;
+	my_id.my_vers = my_vers;
+	my_id.my_proc = NLM_SM_NOTIFY;
+
+	if (!(result = sm_unmon_all_1(&my_id, client))) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		printf("%s\n", clnt_sperror(client, "sm_unmon_all_1"));
+		err = 1;
+		goto unmon_all_out;
+	}
+
+	printf("SM_UNMON_ALL state: %d\n", result->state);
+
+unmon_all_out:
+	return err;
+}
+
+static int
+nsm_client_crash(char *host)
+{
+	CLIENT *client;
+
+	if ((client = nsm_client_get_rpcclient(host)) == NULL)
+		return 1;
+
+	if (!sm_simu_crash_1(NULL, client)) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		printf("%s\n", clnt_sperror(client, "sm_simu_crash_1"));
+		return 1;
+	}
+
+	return 0;
+}
+
+static int
+nsm_client_stat(char *calling, char *monitoring)
+{
+	CLIENT *client;
+	sm_name checking;
+	sm_stat_res *result;
+
+	if ((client = nsm_client_get_rpcclient(calling)) == NULL)
+		return 1;
+
+	checking.mon_name = monitoring;
+
+	if (!(result = sm_stat_1(&checking, client))) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		printf("%s\n", clnt_sperror(client, "sm_stat_1"));
+		return 1;
+	}
+
+	if (result->res_stat != stat_succ) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		printf("stat_fail from %s for %s, state: %d\n", calling,
+			monitoring, result->state);
+		return 1;
+	}
+
+	printf("stat_succ from %s for %s, state: %d\n", calling,
+		monitoring, result->state);
+
+	return 0;
+}
+
+static int
+nsm_client_notify(char *calling, char *mon_name, char *statestr)
+{
+	CLIENT *client;
+
+	stat_chge stat_chge = { .mon_name	= mon_name };
+
+	stat_chge.state = atoi(statestr);
+
+	if ((client = nsm_client_get_rpcclient(calling)) == NULL)
+		return 1;
+
+	if (!sm_notify_1(&stat_chge, client)) {
+		fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat);
+		printf("%s\n", clnt_sperror(client, "sm_notify_1"));
+		return 1;
+	}
+
+	return 0;
+}
+
+static void sim_killer(int sig)
+{
+#ifdef HAVE_LIBTIRPC
+	(void) rpcb_unset(NLM_SM_PROG, NLM_SM_VERS4, NULL);
+#else
+	(void) pmap_unset(NLM_SM_PROG, NLM_SM_VERS4);
+#endif
+	exit(0);
+}
+
+static void daemon_simulator(void)
+{
+	signal(SIGHUP, sim_killer);
+	signal(SIGINT, sim_killer);
+	signal(SIGTERM, sim_killer);
+	/* FIXME: allow for different versions? */
+	nfs_svc_create("nlmsim", NLM_SM_PROG, NLM_SM_VERS4, nlm_sm_prog_4, 0);
+	svc_run();
+}
+
+void *nlm_sm_notify_4_svc(struct nlm_sm_notify *argp, struct svc_req *rqstp)
+{
+	static char *result;
+	char	    priv[SM_PRIV_SIZE * 2 + 1];
+
+	bin2hex(priv, argp->priv, SM_PRIV_SIZE);
+
+	printf("state=%d:mon_name=%s:private=%s\n", argp->state,
+		argp->mon_name, priv);
+	return (void *) &result;
+}
+
+void *nlm_sm_notify_3_svc(struct nlm_sm_notify *argp, struct svc_req *rqstp)
+{
+	return nlm_sm_notify_4_svc(argp, rqstp);
+}
diff --git a/tests/statdb_dump.c b/tests/statdb_dump.c
new file mode 100644
index 0000000..92d63f2
--- /dev/null
+++ b/tests/statdb_dump.c
@@ -0,0 +1,99 @@
+/*
+ * statdb_dump.c -- dump contents of statd's monitor DB
+ *
+ * Copyright (C) 2010  Red Hat, Jeff Layton <jlayton@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+#include "nsm.h"
+#include "xlog.h"
+
+static char cookiebuf[(SM_PRIV_SIZE * 2) + 1];
+static char addrbuf[INET6_ADDRSTRLEN + 1];
+
+static unsigned int
+dump_host(const char *hostname, const struct sockaddr *sa, const struct mon *m,
+	  const time_t timestamp)
+{
+	int ret;
+	const char *addr;
+	const struct sockaddr_in *sin;
+	const struct sockaddr_in6 *sin6;
+
+	ret = nsm_priv_to_hex(m->priv, cookiebuf, sizeof(cookiebuf));
+	if (!ret) {
+		xlog(L_ERROR, "Unable to convert cookie to hex string.\n");
+		return ret;
+	}
+
+	switch (sa->sa_family) {
+	case AF_INET:
+		sin = (struct sockaddr_in *)(char *)sa;
+		addr = inet_ntop(sa->sa_family, &sin->sin_addr.s_addr, addrbuf,
+				 (socklen_t)sizeof(addrbuf));
+		break;
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *)(char *)sa;
+		addr = inet_ntop(sa->sa_family, &sin6->sin6_addr, addrbuf,
+				 (socklen_t)sizeof(addrbuf));
+		break;
+	default:
+		xlog(L_ERROR, "Unrecognized address family: %hu\n",
+			sa->sa_family);
+		return 0;
+	}
+
+	if (addr == NULL) {
+		xlog(L_ERROR, "Unable to convert sockaddr to string: %s\n",
+				strerror(errno));
+		return 0;
+	}
+
+	/*
+	 * Callers of this program should assume that in the future, extra
+	 * fields may be added to the output. Anyone adding extra fields to
+	 * the output should add them to the end of the line.
+	 */
+	printf("%s %s %s %s %s %d %d %d\n",
+			hostname, addr, cookiebuf,
+			m->mon_id.mon_name,
+			m->mon_id.my_id.my_name,
+			m->mon_id.my_id.my_prog,
+			m->mon_id.my_id.my_vers,
+			m->mon_id.my_id.my_proc); 
+
+	return 1;
+}
+
+int
+main(int argc, char **argv)
+{
+	xlog_syslog(0);
+	xlog_stderr(1);
+	xlog_open(argv[0]);
+	
+	nsm_load_monitor_list(dump_host);
+	return 0;
+}
diff --git a/tests/t0001-statd-basic-mon-unmon.sh b/tests/t0001-statd-basic-mon-unmon.sh
new file mode 100644
index 0000000..00127fb
--- /dev/null
+++ b/tests/t0001-statd-basic-mon-unmon.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+#
+# statd_basic_mon_unmon -- test basic mon/unmon functionality with statd
+#
+# Copyright (C) 2010  Red Hat, Jeff Layton <jlayton@redhat.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+
+. ./test-lib.sh
+
+# This test needs root privileges
+check_root
+
+start_statd
+if [ $? -ne 0 ]; then
+	echo "FAIL: problem starting statd"
+	exit 1
+fi
+
+COOKIE=`echo $$ | md5sum | cut -d' ' -f1`
+MON_NAME=`hostname`
+
+nsm_client mon $MON_NAME $COOKIE
+if [ $? -ne 0 ]; then
+	echo "FAIL: mon failed"
+	kill_statd
+	exit 1
+fi
+
+statdb_dump | grep $MON_NAME | grep -q $COOKIE
+if [ $? -ne 0 ]; then
+	echo "FAIL: monitor DB doesn't seem to contain entry"
+	kill_statd
+	exit 1
+fi
+
+nsm_client unmon $MON_NAME
+if [ $? -ne 0 ]; then
+	echo "FAIL: unmon failed"
+	kill_statd
+	exit 1
+fi
+
+kill_statd
+
diff --git a/tests/test-lib.sh b/tests/test-lib.sh
new file mode 100644
index 0000000..3d47264
--- /dev/null
+++ b/tests/test-lib.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+#
+# test-lib.sh -- library of functions for nfs-utils tests
+#
+# Copyright (C) 2010  Red Hat, Jeff Layton <jlayton@redhat.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+
+# make sure $srcdir is set and sanity check it
+srcdir=${srcdir-.}
+if [ ! -d ${srcdir} ]; then
+	echo "***ERROR***: bad installation -- \$srcdir=${srcdir}"
+	exit 1
+fi
+
+export PATH=$PATH:${srcdir}:${srcdir}/nsm_client
+
+# Some tests require root privileges. Check for them and skip the test (exit 77)
+# if the caller doesn't have them.
+check_root() {
+	if [ $EUID -ne 0 ]; then
+		echo "*** Skipping this test as it requires root privs ***"
+		exit 77
+	fi
+}
+
+# is lockd registered as a service?
+lockd_registered() {
+	rpcinfo -p | grep -q nlockmgr
+	return $?
+}
+
+# start up statd
+start_statd() {
+	rpcinfo -u 127.0.0.1 status 1 &> /dev/null
+	if [ $? -eq 0 ]; then
+		echo "***ERROR***: statd is already running and should "
+		echo "             be down when starting this test"
+		return 1
+	fi
+	$srcdir/../utils/statd/statd --no-notify
+}
+
+# shut down statd
+kill_statd() {
+	kill `cat /var/run/rpc.statd.pid`
+}
diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
index 593a8eb..331e57e 100644
--- a/utils/exportfs/exportfs.c
+++ b/utils/exportfs/exportfs.c
@@ -13,6 +13,7 @@
 #endif
 
 #include <sys/vfs.h>
+#include <sys/stat.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <string.h>
diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c
index 40a2b4d..bd37a5f 100644
--- a/utils/gssd/gssd.c
+++ b/utils/gssd/gssd.c
@@ -56,7 +56,6 @@
 #include "krb5_util.h"
 
 char pipefs_dir[PATH_MAX] = GSSD_PIPEFS_DIR;
-char pipefs_nfsdir[PATH_MAX] = GSSD_PIPEFS_DIR;
 char keytabfile[PATH_MAX] = GSSD_DEFAULT_KEYTAB_FILE;
 char ccachedir[PATH_MAX] = GSSD_DEFAULT_CRED_DIR;
 char *ccachesearch[GSSD_MAX_CCACHE_SEARCH + 1];
@@ -159,11 +158,6 @@ main(int argc, char *argv[])
 	if (preferred_realm == NULL)
 		gssd_k5_get_default_realm(&preferred_realm);
 
-	snprintf(pipefs_nfsdir, sizeof(pipefs_nfsdir), "%s/%s",
-		 pipefs_dir, GSSD_SERVICE_NAME);
-	if (pipefs_nfsdir[sizeof(pipefs_nfsdir)-1] != '\0')
-		errx(1, "pipefs_nfsdir path name too long");
-
 	if ((progname = strrchr(argv[0], '/')))
 		progname++;
 	else
diff --git a/utils/gssd/gssd.h b/utils/gssd/gssd.h
index 3c52f46..465c305 100644
--- a/utils/gssd/gssd.h
+++ b/utils/gssd/gssd.h
@@ -60,7 +60,6 @@ enum {AUTHTYPE_KRB5, AUTHTYPE_SPKM3, AUTHTYPE_LIPKEY};
 
 
 extern char			pipefs_dir[PATH_MAX];
-extern char			pipefs_nfsdir[PATH_MAX];
 extern char			keytabfile[PATH_MAX];
 extern char			*ccachesearch[];
 extern int			use_memcache;
@@ -83,13 +82,24 @@ struct clnt_info {
 	int			krb5_poll_index;
 	int			spkm3_fd;
 	int			spkm3_poll_index;
+	int                     gssd_fd;
+	int                     gssd_poll_index;
 	struct sockaddr_storage addr;
 };
 
+TAILQ_HEAD(topdirs_list_head, topdirs_info) topdirs_list;
+
+struct topdirs_info {
+	TAILQ_ENTRY(topdirs_info)   list;
+	char			*dirname;
+	int			fd;
+};
+
 void init_client_list(void);
 int update_client_list(void);
 void handle_krb5_upcall(struct clnt_info *clp);
 void handle_spkm3_upcall(struct clnt_info *clp);
+void handle_gssd_upcall(struct clnt_info *clp);
 int gssd_acquire_cred(char *server_name);
 void gssd_run(void);
 
diff --git a/utils/gssd/gssd_main_loop.c b/utils/gssd/gssd_main_loop.c
index 917b662..f1a68d3 100644
--- a/utils/gssd/gssd_main_loop.c
+++ b/utils/gssd/gssd_main_loop.c
@@ -49,6 +49,7 @@
 #include <fcntl.h>
 #include <signal.h>
 #include <unistd.h>
+#include <dirent.h>
 
 #include "gssd.h"
 #include "err_util.h"
@@ -73,6 +74,17 @@ scan_poll_results(int ret)
 
 	for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next)
 	{
+		i = clp->gssd_poll_index;
+		if (i >= 0 && pollarray[i].revents) {
+			if (pollarray[i].revents & POLLHUP)
+				dir_changed = 1;
+			if (pollarray[i].revents & POLLIN)
+				handle_gssd_upcall(clp);
+			pollarray[clp->gssd_poll_index].revents = 0;
+			ret--;
+			if (!ret)
+				break;
+		}
 		i = clp->krb5_poll_index;
 		if (i >= 0 && pollarray[i].revents) {
 			if (pollarray[i].revents & POLLHUP)
@@ -98,12 +110,85 @@ scan_poll_results(int ret)
 	}
 };
 
+static int
+topdirs_add_entry(struct dirent *dent)
+{
+	struct topdirs_info *tdi;
+
+	tdi = calloc(sizeof(struct topdirs_info), 1);
+	if (tdi == NULL) {
+		printerr(0, "ERROR: Couldn't allocate struct topdirs_info\n");
+		return -1;
+	}
+	tdi->dirname = malloc(PATH_MAX);
+	if (tdi->dirname == NULL) {
+		printerr(0, "ERROR: Couldn't allocate directory name\n");
+		free(tdi);
+		return -1;
+	}
+	snprintf(tdi->dirname, PATH_MAX, "%s/%s", pipefs_dir, dent->d_name);
+	tdi->fd = open(tdi->dirname, O_RDONLY);
+	if (tdi->fd != -1) {
+		fcntl(tdi->fd, F_SETSIG, DNOTIFY_SIGNAL);
+		fcntl(tdi->fd, F_NOTIFY,
+		      DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT);
+	}
+
+	TAILQ_INSERT_HEAD(&topdirs_list, tdi, list);
+	return 0;
+}
+
+static void
+topdirs_free_list(void)
+{
+	struct topdirs_info *tdi;
+
+	TAILQ_FOREACH(tdi, &topdirs_list, list) {
+		free(tdi->dirname);
+		if (tdi->fd != -1)
+			close(tdi->fd);
+		TAILQ_REMOVE(&topdirs_list, tdi, list);
+		free(tdi);
+	}
+}
+
+static int
+topdirs_init_list(void)
+{
+	DIR		*pipedir;
+	struct dirent	*dent;
+	int		ret;
+
+	TAILQ_INIT(&topdirs_list);
+
+	pipedir = opendir(pipefs_dir);
+	if (pipedir == NULL) {
+		printerr(0, "ERROR: could not open rpc_pipefs directory '%s': "
+			 "%s\n", pipefs_dir, strerror(errno));
+		return -1;
+	}
+	for (dent = readdir(pipedir); dent != NULL; dent = readdir(pipedir)) {
+		if (dent->d_type != DT_DIR ||
+		    strcmp(dent->d_name, ".") == 0  ||
+		    strcmp(dent->d_name, "..") == 0) {
+			continue;
+		}
+		ret = topdirs_add_entry(dent);
+		if (ret)
+			goto out_err;
+	}
+	closedir(pipedir);
+	return 0;
+out_err:
+	topdirs_free_list();
+	return -1;
+}
+
 void
 gssd_run()
 {
 	int			ret;
 	struct sigaction	dn_act;
-	int			fd;
 	sigset_t		set;
 
 	/* Taken from linux/Documentation/dnotify.txt: */
@@ -117,13 +202,8 @@ gssd_run()
 	sigaddset(&set, DNOTIFY_SIGNAL);
 	sigprocmask(SIG_UNBLOCK, &set, NULL);
 
-	if ((fd = open(pipefs_nfsdir, O_RDONLY)) == -1) {
-		printerr(0, "ERROR: failed to open %s: %s\n",
-			 pipefs_nfsdir, strerror(errno));
-		exit(1);
-	}
-	fcntl(fd, F_SETSIG, DNOTIFY_SIGNAL);
-	fcntl(fd, F_NOTIFY, DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT);
+	if (topdirs_init_list() != 0)
+		return;
 
 	init_client_list();
 
@@ -132,8 +212,7 @@ gssd_run()
 		while (dir_changed) {
 			dir_changed = 0;
 			if (update_client_list()) {
-				printerr(0, "ERROR: couldn't update "
-					 "client list\n");
+				/* Error msg is already printed */
 				exit(1);
 			}
 		}
@@ -151,6 +230,7 @@ gssd_run()
 			scan_poll_results(ret);
 		}
 	}
-	close(fd);
+	topdirs_free_list();
+
 	return;
 }
diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c
index 37e2aa5..be4fb11 100644
--- a/utils/gssd/gssd_proc.c
+++ b/utils/gssd/gssd_proc.c
@@ -73,6 +73,7 @@
 #include "krb5_util.h"
 #include "context.h"
 #include "nfsrpc.h"
+#include "nfslib.h"
 
 /*
  * pollarray:
@@ -83,20 +84,22 @@
  *      linked list of struct clnt_info which associates a clntXXX directory
  *	with an index into pollarray[], and other basic data about that client.
  *
- * Directory structure: created by the kernel nfs client
- *      {pipefs_nfsdir}/clntXX             : one per rpc_clnt struct in the kernel
- *      {pipefs_nfsdir}/clntXX/krb5        : read uid for which kernel wants
+ * Directory structure: created by the kernel
+ *      {rpc_pipefs}/{dir}/clntXX         : one per rpc_clnt struct in the kernel
+ *      {rpc_pipefs}/{dir}/clntXX/krb5    : read uid for which kernel wants
  *					    a context, write the resulting context
- *      {pipefs_nfsdir}/clntXX/info        : stores info such as server name
+ *      {rpc_pipefs}/{dir}/clntXX/info    : stores info such as server name
+ *      {rpc_pipefs}/{dir}/clntXX/gssd    : pipe for all gss mechanisms using
+ *					    a text-based string of parameters
  *
  * Algorithm:
- *      Poll all {pipefs_nfsdir}/clntXX/krb5 files.  When ready, data read
- *      is a uid; performs rpcsec_gss context initialization protocol to
+ *      Poll all {rpc_pipefs}/{dir}/clntXX/YYYY files.  When data is ready,
+ *      read and process; performs rpcsec_gss context initialization protocol to
  *      get a cred for that user.  Writes result to corresponding krb5 file
  *      in a form the kernel code will understand.
  *      In addition, we make sure we are notified whenever anything is
- *      created or destroyed in {pipefs_nfsdir} or in an of the clntXX directories,
- *      and rescan the whole {pipefs_nfsdir} when this happens.
+ *      created or destroyed in {rpc_pipefs} or in any of the clntXX directories,
+ *      and rescan the whole {rpc_pipefs} when this happens.
  */
 
 struct pollfd * pollarray;
@@ -105,7 +108,7 @@ int pollsize;  /* the size of pollaray (in pollfd's) */
 
 /*
  * convert a presentation address string to a sockaddr_storage struct. Returns
- * true on success and false on failure.
+ * true on success or false on failure.
  *
  * Note that we do not populate the sin6_scope_id field here for IPv6 addrs.
  * gssd nececessarily relies on hostname resolution and DNS AAAA records
@@ -117,26 +120,43 @@ int pollsize;  /* the size of pollaray (in pollfd's) */
  * not really feasible at present.
  */
 static int
-addrstr_to_sockaddr(struct sockaddr *sa, const char *addr, const int port)
+addrstr_to_sockaddr(struct sockaddr *sa, const char *node, const char *port)
 {
-	struct sockaddr_in	*s4 = (struct sockaddr_in *) sa;
-#ifdef IPV6_SUPPORTED
-	struct sockaddr_in6	*s6 = (struct sockaddr_in6 *) sa;
-#endif /* IPV6_SUPPORTED */
+	int rc;
+	struct addrinfo *res;
+	struct addrinfo hints = { .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV };
 
-	if (inet_pton(AF_INET, addr, &s4->sin_addr)) {
-		s4->sin_family = AF_INET;
-		s4->sin_port = htons(port);
-#ifdef IPV6_SUPPORTED
-	} else if (inet_pton(AF_INET6, addr, &s6->sin6_addr)) {
-		s6->sin6_family = AF_INET6;
-		s6->sin6_port = htons(port);
+#ifndef IPV6_SUPPORTED
+	hints.ai_family = AF_INET;
 #endif /* IPV6_SUPPORTED */
-	} else {
-		printerr(0, "ERROR: unable to convert %s to address\n", addr);
+
+	rc = getaddrinfo(node, port, &hints, &res);
+	if (rc) {
+		printerr(0, "ERROR: unable to convert %s|%s to sockaddr: %s\n",
+			 node, port, rc == EAI_SYSTEM ? strerror(errno) :
+						gai_strerror(rc));
 		return 0;
 	}
 
+#ifdef IPV6_SUPPORTED
+	/*
+	 * getnameinfo ignores the scopeid. If the address turns out to have
+	 * a non-zero scopeid, we can't use it -- the resolved host might be
+	 * completely different from the one intended.
+	 */
+	if (res->ai_addr->sa_family == AF_INET6) {
+		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)res->ai_addr;
+		if (sin6->sin6_scope_id) {
+			printerr(0, "ERROR: address %s has non-zero "
+				    "sin6_scope_id!\n", node);
+			freeaddrinfo(res);
+			return 0;
+		}
+	}
+#endif /* IPV6_SUPPORTED */
+
+	memcpy(sa, res->ai_addr, res->ai_addrlen);
+	freeaddrinfo(res);
 	return 1;
 }
 
@@ -194,11 +214,10 @@ read_service_info(char *info_file_name, char **servicename, char **servername,
 	char		program[16];
 	char		version[16];
 	char		protoname[16];
-	char		cb_port[128];
+	char		port[128];
 	char		*p;
 	int		fd = -1;
 	int		numfields;
-	int		port = 0;
 
 	*servicename = *servername = *protocol = NULL;
 
@@ -227,20 +246,22 @@ read_service_info(char *info_file_name, char **servicename, char **servername,
 		goto fail;
 	}
 
-	cb_port[0] = '\0';
+	port[0] = '\0';
 	if ((p = strstr(buf, "port")) != NULL)
-		sscanf(p, "port: %127s\n", cb_port);
+		sscanf(p, "port: %127s\n", port);
 
 	/* check service, program, and version */
-	if(memcmp(service, "nfs", 3)) return -1;
+	if (memcmp(service, "nfs", 3) != 0)
+		return -1;
 	*prog = atoi(program + 1); /* skip open paren */
 	*vers = atoi(version);
-	if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4)))
-		goto fail;
 
-	if (cb_port[0] != '\0') {
-		port = atoi(cb_port);
-		if (port < 0 || port > 65535)
+	if (strlen(service) == 3 ) {
+		if ((*prog != 100003) || ((*vers != 2) && (*vers != 3) &&
+		    (*vers != 4)))
+			goto fail;
+	} else if (memcmp(service, "nfs4_cb", 7) == 0) {
+		if (*vers != 1)
 			goto fail;
 	}
 
@@ -281,9 +302,13 @@ destroy_client(struct clnt_info *clp)
 	if (clp->spkm3_poll_index != -1)
 		memset(&pollarray[clp->spkm3_poll_index], 0,
 					sizeof(struct pollfd));
+	if (clp->gssd_poll_index != -1)
+		memset(&pollarray[clp->gssd_poll_index], 0,
+					sizeof(struct pollfd));
 	if (clp->dir_fd != -1) close(clp->dir_fd);
 	if (clp->krb5_fd != -1) close(clp->krb5_fd);
 	if (clp->spkm3_fd != -1) close(clp->spkm3_fd);
+	if (clp->gssd_fd != -1) close(clp->gssd_fd);
 	free(clp->dirname);
 	free(clp->servicename);
 	free(clp->servername);
@@ -303,8 +328,10 @@ insert_new_clnt(void)
 	}
 	clp->krb5_poll_index = -1;
 	clp->spkm3_poll_index = -1;
+	clp->gssd_poll_index = -1;
 	clp->krb5_fd = -1;
 	clp->spkm3_fd = -1;
+	clp->gssd_fd = -1;
 	clp->dir_fd = -1;
 
 	TAILQ_INSERT_HEAD(&clnt_list, clp, list);
@@ -315,19 +342,43 @@ out:
 static int
 process_clnt_dir_files(struct clnt_info * clp)
 {
-	char	kname[32];
-	char	sname[32];
-	char	info_file_name[32];
+	char	name[PATH_MAX];
+	char	gname[PATH_MAX];
+	char	info_file_name[PATH_MAX];
 
-	if (clp->krb5_fd == -1) {
-		snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname);
-		clp->krb5_fd = open(kname, O_RDWR);
+	if (clp->gssd_fd == -1) {
+		snprintf(gname, sizeof(gname), "%s/gssd", clp->dirname);
+		clp->gssd_fd = open(gname, O_RDWR);
 	}
-	if (clp->spkm3_fd == -1) {
-		snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname);
-		clp->spkm3_fd = open(sname, O_RDWR);
+	if (clp->gssd_fd == -1) {
+		if (clp->krb5_fd == -1) {
+			snprintf(name, sizeof(name), "%s/krb5", clp->dirname);
+			clp->krb5_fd = open(name, O_RDWR);
+		}
+		if (clp->spkm3_fd == -1) {
+			snprintf(name, sizeof(name), "%s/spkm3", clp->dirname);
+			clp->spkm3_fd = open(name, O_RDWR);
+		}
+
+		/* If we opened a gss-specific pipe, let's try opening
+		 * the new upcall pipe again. If we succeed, close
+		 * gss-specific pipe(s).
+		 */
+		if (clp->krb5_fd != -1 || clp->spkm3_fd != -1) {
+			clp->gssd_fd = open(gname, O_RDWR);
+			if (clp->gssd_fd != -1) {
+				if (clp->krb5_fd != -1)
+					close(clp->krb5_fd);
+				clp->krb5_fd = -1;
+				if (clp->spkm3_fd != -1)
+					close(clp->spkm3_fd);
+				clp->spkm3_fd = -1;
+			}
+		}
 	}
-	if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1))
+
+	if ((clp->krb5_fd == -1) && (clp->spkm3_fd == -1) &&
+			(clp->gssd_fd == -1))
 		return -1;
 	snprintf(info_file_name, sizeof(info_file_name), "%s/info",
 			clp->dirname);
@@ -362,6 +413,15 @@ get_poll_index(int *ind)
 static int
 insert_clnt_poll(struct clnt_info *clp)
 {
+	if ((clp->gssd_fd != -1) && (clp->gssd_poll_index == -1)) {
+		if (get_poll_index(&clp->gssd_poll_index)) {
+			printerr(0, "ERROR: Too many gssd clients\n");
+			return -1;
+		}
+		pollarray[clp->gssd_poll_index].fd = clp->gssd_fd;
+		pollarray[clp->gssd_poll_index].events |= POLLIN;
+	}
+
 	if ((clp->krb5_fd != -1) && (clp->krb5_poll_index == -1)) {
 		if (get_poll_index(&clp->krb5_poll_index)) {
 			printerr(0, "ERROR: Too many krb5 clients\n");
@@ -384,17 +444,18 @@ insert_clnt_poll(struct clnt_info *clp)
 }
 
 static void
-process_clnt_dir(char *dir)
+process_clnt_dir(char *dir, char *pdir)
 {
 	struct clnt_info *	clp;
 
 	if (!(clp = insert_new_clnt()))
 		goto fail_destroy_client;
 
-	if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) {
+	/* An extra for the '/', and an extra for the null */
+	if (!(clp->dirname = calloc(strlen(dir) + strlen(pdir) + 2, 1))) {
 		goto fail_destroy_client;
 	}
-	memcpy(clp->dirname, dir, strlen(dir));
+	sprintf(clp->dirname, "%s/%s", pdir, dir);
 	if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) {
 		printerr(0, "ERROR: can't open %s: %s\n",
 			 clp->dirname, strerror(errno));
@@ -438,16 +499,24 @@ init_client_list(void)
  * directories, since the DNOTIFY could have been in there.
  */
 static void
-update_old_clients(struct dirent **namelist, int size)
+update_old_clients(struct dirent **namelist, int size, char *pdir)
 {
 	struct clnt_info *clp;
 	void *saveprev;
 	int i, stillhere;
+	char fname[PATH_MAX];
 
 	for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
+		/* only compare entries in the global list that are from the
+		 * same pipefs parent directory as "pdir"
+		 */
+		if (strncmp(clp->dirname, pdir, strlen(pdir)) != 0) continue;
+
 		stillhere = 0;
 		for (i=0; i < size; i++) {
-			if (!strcmp(clp->dirname, namelist[i]->d_name)) {
+			snprintf(fname, sizeof(fname), "%s/%s",
+				 pdir, namelist[i]->d_name);
+			if (strcmp(clp->dirname, fname) == 0) {
 				stillhere = 1;
 				break;
 			}
@@ -468,48 +537,69 @@ update_old_clients(struct dirent **namelist, int size)
 
 /* Search for a client by directory name, return 1 if found, 0 otherwise */
 static int
-find_client(char *dirname)
+find_client(char *dirname, char *pdir)
 {
 	struct clnt_info	*clp;
+	char fname[PATH_MAX];
 
-	for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next)
-		if (!strcmp(clp->dirname, dirname))
+	for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
+		snprintf(fname, sizeof(fname), "%s/%s", pdir, dirname);
+		if (strcmp(clp->dirname, fname) == 0)
 			return 1;
+	}
 	return 0;
 }
 
-/* Used to read (and re-read) list of clients, set up poll array. */
-int
-update_client_list(void)
+static int
+process_pipedir(char *pipe_name)
 {
 	struct dirent **namelist;
 	int i, j;
 
-	if (chdir(pipefs_nfsdir) < 0) {
+	if (chdir(pipe_name) < 0) {
 		printerr(0, "ERROR: can't chdir to %s: %s\n",
-			 pipefs_nfsdir, strerror(errno));
+			 pipe_name, strerror(errno));
 		return -1;
 	}
 
-	j = scandir(pipefs_nfsdir, &namelist, NULL, alphasort);
+	j = scandir(pipe_name, &namelist, NULL, alphasort);
 	if (j < 0) {
 		printerr(0, "ERROR: can't scandir %s: %s\n",
-			 pipefs_nfsdir, strerror(errno));
+			 pipe_name, strerror(errno));
 		return -1;
 	}
-	update_old_clients(namelist, j);
+
+	update_old_clients(namelist, j, pipe_name);
 	for (i=0; i < j; i++) {
 		if (i < FD_ALLOC_BLOCK
 				&& !strncmp(namelist[i]->d_name, "clnt", 4)
-				&& !find_client(namelist[i]->d_name))
-			process_clnt_dir(namelist[i]->d_name);
+				&& !find_client(namelist[i]->d_name, pipe_name))
+			process_clnt_dir(namelist[i]->d_name, pipe_name);
 		free(namelist[i]);
 	}
 
 	free(namelist);
+
 	return 0;
 }
 
+/* Used to read (and re-read) list of clients, set up poll array. */
+int
+update_client_list(void)
+{
+	int retval = -1;
+	struct topdirs_info *tdi;
+
+	TAILQ_FOREACH(tdi, &topdirs_list, list) {
+		retval = process_pipedir(tdi->dirname);
+		if (retval)
+			printerr(1, "WARNING: error processing %s\n",
+				 tdi->dirname);
+
+	}
+	return retval;
+}
+
 static int
 do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd,
 	    gss_buffer_desc *context_token)
@@ -798,15 +888,14 @@ int create_auth_rpc_client(struct clnt_info *clp,
 	goto out;
 }
 
-
 /*
  * this code uses the userland rpcsec gss library to create a krb5
  * context on behalf of the kernel
  */
-void
-handle_krb5_upcall(struct clnt_info *clp)
+static void
+process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname,
+		    char *service)
 {
-	uid_t			uid;
 	CLIENT			*rpc_clnt = NULL;
 	AUTH			*auth = NULL;
 	struct authgss_private_data pd;
@@ -815,23 +904,51 @@ handle_krb5_upcall(struct clnt_info *clp)
 	char			**ccname;
 	char			**dirname;
 	int			create_resp = -1;
+	int			err, downcall_err = -EACCES;
 
-	printerr(1, "handling krb5 upcall\n");
+	printerr(1, "handling krb5 upcall (%s)\n", clp->dirname);
 
+	if (tgtname) {
+		if (clp->servicename) {
+			free(clp->servicename);
+			clp->servicename = strdup(tgtname);
+		}
+	}
 	token.length = 0;
 	token.value = NULL;
 	memset(&pd, 0, sizeof(struct authgss_private_data));
 
-	if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) {
-		printerr(0, "WARNING: failed reading uid from krb5 "
-			    "upcall pipe: %s\n", strerror(errno));
-		goto out;
-	}
-
-	if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0)) {
+	/*
+	 * If "service" is specified, then the kernel is indicating that
+	 * we must use machine credentials for this request.  (Regardless
+	 * of the uid value or the setting of root_uses_machine_creds.)
+	 * If the service value is "*", then any service name can be used.
+	 * Otherwise, it specifies the service name that should be used.
+	 * (For now, the values of service will only be "*" or "nfs".)
+	 *
+	 * Restricting gssd to use "nfs" service name is needed for when
+	 * the NFS server is doing a callback to the NFS client.  In this
+	 * case, the NFS server has to authenticate itself as "nfs" --
+	 * even if there are other service keys such as "host" or "root"
+	 * in the keytab.
+	 *
+	 * Another case when the kernel may specify the service attribute
+	 * is when gssd is being asked to create the context for a
+	 * SETCLIENT_ID operation.  In this case, machine credentials
+	 * must be used for the authentication.  However, the service name
+	 * used for this case is not important.
+	 *
+	 */
+	printerr(2, "%s: service is '%s'\n", __func__,
+		 service ? service : "<null>");
+	if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0 &&
+				service == NULL)) {
 		/* Tell krb5 gss which credentials cache to use */
 		for (dirname = ccachesearch; *dirname != NULL; dirname++) {
-			if (gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname) == 0)
+			err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname);
+			if (err == -EKEYEXPIRED)
+				downcall_err = -EKEYEXPIRED;
+			else if (!err)
 				create_resp = create_auth_rpc_client(clp, &rpc_clnt, &auth, uid,
 							     AUTHTYPE_KRB5);
 			if (create_resp == 0)
@@ -839,12 +956,13 @@ handle_krb5_upcall(struct clnt_info *clp)
 		}
 	}
 	if (create_resp != 0) {
-		if (uid == 0 && root_uses_machine_creds == 1) {
+		if (uid == 0 && (root_uses_machine_creds == 1 ||
+				service != NULL)) {
 			int nocache = 0;
 			int success = 0;
 			do {
 				gssd_refresh_krb5_machine_credential(clp->servername,
-								     NULL, nocache);
+								     NULL, service);
 				/*
 				 * Get a list of credential cache names and try each
 				 * of them until one works or we've tried them all
@@ -904,7 +1022,7 @@ handle_krb5_upcall(struct clnt_info *clp)
 		goto out_return_error;
 	}
 
-	do_downcall(clp->krb5_fd, uid, &pd, &token);
+	do_downcall(fd, uid, &pd, &token);
 
 out:
 	if (token.value)
@@ -920,7 +1038,7 @@ out:
 	return;
 
 out_return_error:
-	do_error_downcall(clp->krb5_fd, uid, -1);
+	do_error_downcall(fd, uid, downcall_err);
 	goto out;
 }
 
@@ -928,26 +1046,19 @@ out_return_error:
  * this code uses the userland rpcsec gss library to create an spkm3
  * context on behalf of the kernel
  */
-void
-handle_spkm3_upcall(struct clnt_info *clp)
+static void
+process_spkm3_upcall(struct clnt_info *clp, uid_t uid, int fd)
 {
-	uid_t			uid;
 	CLIENT			*rpc_clnt = NULL;
 	AUTH			*auth = NULL;
 	struct authgss_private_data pd;
 	gss_buffer_desc		token;
 
-	printerr(2, "handling spkm3 upcall\n");
+	printerr(2, "handling spkm3 upcall (%s)\n", clp->dirname);
 
 	token.length = 0;
 	token.value = NULL;
 
-	if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) {
-		printerr(0, "WARNING: failed reading uid from spkm3 "
-			 "upcall pipe: %s\n", strerror(errno));
-		goto out;
-	}
-
 	if (create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, AUTHTYPE_SPKM3)) {
 		printerr(0, "WARNING: Failed to create spkm3 context for "
 			    "user with uid %d\n", uid);
@@ -968,7 +1079,7 @@ handle_spkm3_upcall(struct clnt_info *clp)
 		goto out_return_error;
 	}
 
-	do_downcall(clp->spkm3_fd, uid, &pd, &token);
+	do_downcall(fd, uid, &pd, &token);
 
 out:
 	if (token.value)
@@ -980,6 +1091,139 @@ out:
 	return;
 
 out_return_error:
-	do_error_downcall(clp->spkm3_fd, uid, -1);
+	do_error_downcall(fd, uid, -1);
 	goto out;
 }
+
+void
+handle_krb5_upcall(struct clnt_info *clp)
+{
+	uid_t			uid;
+
+	if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) {
+		printerr(0, "WARNING: failed reading uid from krb5 "
+			    "upcall pipe: %s\n", strerror(errno));
+		return;
+	}
+
+	return process_krb5_upcall(clp, uid, clp->krb5_fd, NULL, NULL);
+}
+
+void
+handle_spkm3_upcall(struct clnt_info *clp)
+{
+	uid_t			uid;
+
+	if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) {
+		printerr(0, "WARNING: failed reading uid from spkm3 "
+			 "upcall pipe: %s\n", strerror(errno));
+		return;
+	}
+
+	return process_spkm3_upcall(clp, uid, clp->spkm3_fd);
+}
+
+void
+handle_gssd_upcall(struct clnt_info *clp)
+{
+	uid_t			uid;
+	char			*lbuf = NULL;
+	int			lbuflen = 0;
+	char			*p;
+	char			*mech = NULL;
+	char			*target = NULL;
+	char			*service = NULL;
+
+	printerr(1, "handling gssd upcall (%s)\n", clp->dirname);
+
+	if (readline(clp->gssd_fd, &lbuf, &lbuflen) != 1) {
+		printerr(0, "WARNING: handle_gssd_upcall: "
+			    "failed reading request\n");
+		return;
+	}
+	printerr(2, "%s: '%s'\n", __func__, lbuf);
+
+	/* find the mechanism name */
+	if ((p = strstr(lbuf, "mech=")) != NULL) {
+		mech = malloc(lbuflen);
+		if (!mech)
+			goto out;
+		if (sscanf(p, "mech=%s", mech) != 1) {
+			printerr(0, "WARNING: handle_gssd_upcall: "
+				    "failed to parse gss mechanism name "
+				    "in upcall string '%s'\n", lbuf);
+			goto out;
+		}
+	} else {
+		printerr(0, "WARNING: handle_gssd_upcall: "
+			    "failed to find gss mechanism name "
+			    "in upcall string '%s'\n", lbuf);
+		goto out;
+	}
+
+	/* read uid */
+	if ((p = strstr(lbuf, "uid=")) != NULL) {
+		if (sscanf(p, "uid=%d", &uid) != 1) {
+			printerr(0, "WARNING: handle_gssd_upcall: "
+				    "failed to parse uid "
+				    "in upcall string '%s'\n", lbuf);
+			goto out;
+		}
+	} else {
+		printerr(0, "WARNING: handle_gssd_upcall: "
+			    "failed to find uid "
+			    "in upcall string '%s'\n", lbuf);
+		goto out;
+	}
+
+	/* read target name */
+	if ((p = strstr(lbuf, "target=")) != NULL) {
+		target = malloc(lbuflen);
+		if (!target)
+			goto out;
+		if (sscanf(p, "target=%s", target) != 1) {
+			printerr(0, "WARNING: handle_gssd_upcall: "
+				    "failed to parse target name "
+				    "in upcall string '%s'\n", lbuf);
+			goto out;
+		}
+	}
+
+	/*
+	 * read the service name
+	 *
+	 * The presence of attribute "service=" indicates that machine
+	 * credentials should be used for this request.  If the value
+	 * is "*", then any machine credentials available can be used.
+	 * If the value is anything else, then machine credentials for
+	 * the specified service name (always "nfs" for now) should be
+	 * used.
+	 */
+	if ((p = strstr(lbuf, "service=")) != NULL) {
+		service = malloc(lbuflen);
+		if (!service)
+			goto out;
+		if (sscanf(p, "service=%s", service) != 1) {
+			printerr(0, "WARNING: handle_gssd_upcall: "
+				    "failed to parse service type "
+				    "in upcall string '%s'\n", lbuf);
+			goto out;
+		}
+	}
+
+	if (strcmp(mech, "krb5") == 0)
+		process_krb5_upcall(clp, uid, clp->gssd_fd, target, service);
+	else if (strcmp(mech, "spkm3") == 0)
+		process_spkm3_upcall(clp, uid, clp->gssd_fd);
+	else
+		printerr(0, "WARNING: handle_gssd_upcall: "
+			    "received unknown gss mech '%s'\n", mech);
+
+out:
+	free(lbuf);
+	free(mech);
+	free(target);
+	free(service);
+	return;	
+}
+
diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
index 78e9775..1295f57 100644
--- a/utils/gssd/krb5_util.c
+++ b/utils/gssd/krb5_util.c
@@ -170,9 +170,8 @@ select_krb5_ccache(const struct dirent *d)
  * what we want. Otherwise, return zero and no dirent pointer.
  * The caller is responsible for freeing the dirent if one is returned.
  *
- * Returns:
- *	0 => could not find an existing entry
- *	1 => found an existing entry
+ * Returns 0 if a valid-looking entry was found and a non-zero error
+ * code otherwise.
  */
 static int
 gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d)
@@ -186,7 +185,7 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d)
 	char buf[1030];
 	char *princname = NULL;
 	char *realm = NULL;
-	int score, best_match_score = 0;
+	int score, best_match_score = 0, err = -EACCES;
 
 	memset(&best_match_stat, 0, sizeof(best_match_stat));
 	*d = NULL;
@@ -229,6 +228,7 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d)
 				printerr(3, "CC file '%s' is expired or corrupt\n",
 					 statname);
 				free(namelist[i]);
+				err = -EKEYEXPIRED;
 				continue;
 			}
 
@@ -284,11 +284,12 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d)
 		}
 		free(namelist);
 	}
-	if (found)
-	{
+	if (found) {
 		*d = best_match_dir;
+		return 0;
 	}
-	return found;
+
+	return err;
 }
 
 
@@ -797,10 +798,9 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
  */
 static int
 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
-		  krb5_keytab_entry *kte)
+		  krb5_keytab_entry *kte, const char **svcnames)
 {
 	krb5_error_code code;
-	const char *svcnames[] = { "root", "nfs", "host", NULL };
 	char **realmnames = NULL;
 	char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
 	int i, j, retval;
@@ -1025,29 +1025,29 @@ err_cache:
  * given only a UID.  We really need more information, but we
  * do the best we can.
  *
- * Returns:
- *	0 => a ccache was found
- *	1 => no ccache was found
+ * Returns 0 if a ccache was found, and a non-zero error code otherwise.
  */
 int
 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirname)
 {
 	char			buf[MAX_NETOBJ_SZ];
 	struct dirent		*d;
+	int			err;
 
 	printerr(2, "getting credentials for client with uid %u for "
 		    "server %s\n", uid, servername);
 	memset(buf, 0, sizeof(buf));
-	if (gssd_find_existing_krb5_ccache(uid, dirname, &d)) {
-		snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name);
-		free(d);
-	}
-	else
-		return 1;
+	err = gssd_find_existing_krb5_ccache(uid, dirname, &d);
+	if (err)
+		return err;
+
+	snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name);
+	free(d);
+
 	printerr(2, "using %s as credentials cache for client with "
 		    "uid %u for server %s\n", buf, uid, servername);
 	gssd_set_krb5_ccache_name(buf);
-	return 0;
+	return err;
 }
 
 /*
@@ -1096,7 +1096,8 @@ gssd_get_krb5_machine_cred_list(char ***list)
 	for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
 		if (ple->ccname) {
 			/* Make sure cred is up-to-date before returning it */
-			retval = gssd_refresh_krb5_machine_credential(NULL, ple, 0);
+			retval = gssd_refresh_krb5_machine_credential(NULL, ple,
+				NULL);
 			if (retval)
 				continue;
 			if (i + 1 > listsize) {
@@ -1186,14 +1187,24 @@ gssd_destroy_krb5_machine_creds(void)
  */
 int
 gssd_refresh_krb5_machine_credential(char *hostname,
-				     struct gssd_k5_kt_princ *ple, int nocache)
+				     struct gssd_k5_kt_princ *ple, 
+					 char *service)
 {
 	krb5_error_code code = 0;
 	krb5_context context;
 	krb5_keytab kt = NULL;;
 	int retval = 0;
 	char *k5err = NULL;
+	const char *svcnames[4] = { "root", "nfs", "host", NULL };
 
+	/*
+	 * If a specific service name was specified, use it.
+	 * Otherwise, use the default list.
+	 */
+	if (service != NULL && strcmp(service, "*") != 0) {
+		svcnames[0] = service;
+		svcnames[1] = NULL;
+	}
 	if (hostname == NULL && ple == NULL)
 		return EINVAL;
 
@@ -1216,7 +1227,7 @@ gssd_refresh_krb5_machine_credential(char *hostname,
 	if (ple == NULL) {
 		krb5_keytab_entry kte;
 
-		code = find_keytab_entry(context, kt, hostname, &kte);
+		code = find_keytab_entry(context, kt, hostname, &kte, svcnames);
 		if (code) {
 			printerr(0, "ERROR: %s: no usable keytab entry found "
 				 "in keytab %s for connection with host %s\n",
@@ -1241,7 +1252,7 @@ gssd_refresh_krb5_machine_credential(char *hostname,
 			goto out;
 		}
 	}
-	retval = gssd_get_single_krb5_cred(context, kt, ple, nocache);
+	retval = gssd_get_single_krb5_cred(context, kt, ple, 0);
 out:
 	if (kt)
 		krb5_kt_close(context, kt);
diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h
index 4b6b281..4602cc3 100644
--- a/utils/gssd/krb5_util.h
+++ b/utils/gssd/krb5_util.h
@@ -30,7 +30,8 @@ void gssd_free_krb5_machine_cred_list(char **list);
 void gssd_setup_krb5_machine_gss_ccache(char *servername);
 void gssd_destroy_krb5_machine_creds(void);
 int  gssd_refresh_krb5_machine_credential(char *hostname,
-					  struct gssd_k5_kt_princ *ple, int nocache);
+					  struct gssd_k5_kt_princ *ple, 
+					  char *service);
 char *gssd_k5_err_msg(krb5_context context, krb5_error_code code);
 void gssd_k5_get_default_realm(char **def_realm);
 
diff --git a/utils/gssd/svcgssd_proc.c b/utils/gssd/svcgssd_proc.c
index 6f2ba61..f1bfbef 100644
--- a/utils/gssd/svcgssd_proc.c
+++ b/utils/gssd/svcgssd_proc.c
@@ -56,6 +56,7 @@
 #include "gss_util.h"
 #include "err_util.h"
 #include "context.h"
+#include "gss_oids.h"
 
 extern char * mech2file(gss_OID mech);
 #define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.rpcsec.context/channel"
@@ -73,7 +74,7 @@ struct svc_cred {
 static int
 do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred,
 		gss_OID mech, gss_buffer_desc *context_token,
-		int32_t endtime)
+		int32_t endtime, char *client_name)
 {
 	FILE *f;
 	int i;
@@ -98,9 +99,10 @@ do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred,
 	qword_printint(f, cred->cr_gid);
 	qword_printint(f, cred->cr_ngroups);
 	printerr(2, "mech: %s, hndl len: %d, ctx len %d, timeout: %d (%d from now), "
-		 "uid: %d, gid: %d, num aux grps: %d:\n",
+		 "clnt: %s, uid: %d, gid: %d, num aux grps: %d:\n",
 		 fname, out_handle->length, context_token->length,
 		 endtime, endtime - time(0),
+		 client_name ? client_name : "<null>",
 		 cred->cr_uid, cred->cr_gid, cred->cr_ngroups);
 	for (i=0; i < cred->cr_ngroups; i++) {
 		qword_printint(f, cred->cr_groups[i]);
@@ -108,6 +110,8 @@ do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred,
 	}
 	qword_print(f, fname);
 	qword_printhex(f, context_token->value, context_token->length);
+	if (client_name)
+		qword_print(f, client_name);
 	err = qword_eol(f);
 	if (err) {
 		printerr(1, "WARNING: error writing to downcall channel "
@@ -307,6 +311,75 @@ print_hexl(const char *description, unsigned char *cp, int length)
 }
 #endif
 
+static int
+get_krb5_hostbased_name (gss_buffer_desc *name, char **hostbased_name)
+{
+	char *p, *sname = NULL;
+	if (strchr(name->value, '@') && strchr(name->value, '/')) {
+		if ((sname = calloc(name->length, 1)) == NULL) {
+			printerr(0, "ERROR: get_krb5_hostbased_name failed "
+				 "to allocate %d bytes\n", name->length);
+			return -1;
+		}
+		/* read in name and instance and replace '/' with '@' */
+		sscanf(name->value, "%[^@]", sname);
+		p = strrchr(sname, '/');
+		if (p == NULL) {    /* The '@' preceeded the '/' */
+			free(sname);
+			return -1;
+		}
+		*p = '@';
+	}
+	*hostbased_name = sname;
+	return 0;
+}
+
+static int
+get_hostbased_client_name(gss_name_t client_name, gss_OID mech,
+			  char **hostbased_name)
+{
+	u_int32_t	maj_stat, min_stat;
+	gss_buffer_desc	name;
+	gss_OID		name_type = GSS_C_NO_OID;
+	char		*cname;
+	int		res = -1;
+
+	*hostbased_name = NULL;	    /* preset in case we fail */
+
+	/* Get the client's gss authenticated name */
+	maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
+	if (maj_stat != GSS_S_COMPLETE) {
+		pgsserr("get_hostbased_client_name: gss_display_name",
+			maj_stat, min_stat, mech);
+		goto out_err;
+	}
+	if (name.length >= 0xffff) {	    /* don't overflow */
+		printerr(0, "ERROR: get_hostbased_client_name: "
+			 "received gss_name is too long (%d bytes)\n",
+			 name.length);
+		goto out_rel_buf;
+	}
+
+	/* For Kerberos, transform the NT_KRB5_PRINCIPAL name to
+	 * an NT_HOSTBASED_SERVICE name */
+	if (g_OID_equal(&krb5oid, mech)) {
+		if (get_krb5_hostbased_name(&name, &cname) == 0)
+			*hostbased_name = cname;
+	}
+
+	/* No support for SPKM3, just print a warning (for now) */
+	if (g_OID_equal(&spkm3oid, mech)) {
+		printerr(1, "WARNING: get_hostbased_client_name: "
+			 "no hostbased_name support for SPKM3\n");
+	}
+
+	res = 0;
+out_rel_buf:
+	gss_release_buffer(&min_stat, &name);
+out_err:
+	return res;
+}
+
 void
 handle_nullreq(FILE *f) {
 	/* XXX initialize to a random integer to reduce chances of unnecessary
@@ -325,7 +398,7 @@ handle_nullreq(FILE *f) {
 				null_token = {.value = NULL};
 	u_int32_t		ret_flags;
 	gss_ctx_id_t		ctx = GSS_C_NO_CONTEXT;
-	gss_name_t		client_name;
+	gss_name_t		client_name = NULL;
 	gss_OID			mech = GSS_C_NO_OID;
 	u_int32_t		maj_stat = GSS_S_FAILURE, min_stat = 0;
 	u_int32_t		ignore_min_stat;
@@ -334,6 +407,7 @@ handle_nullreq(FILE *f) {
 	static int		lbuflen = 0;
 	static char		*cp;
 	int32_t			ctx_endtime;
+	char			*hostbased_name = NULL;
 
 	printerr(1, "handling null request\n");
 
@@ -396,11 +470,13 @@ handle_nullreq(FILE *f) {
 	if (get_ids(client_name, mech, &cred)) {
 		/* get_ids() prints error msg */
 		maj_stat = GSS_S_BAD_NAME; /* XXX ? */
-		gss_release_name(&ignore_min_stat, &client_name);
 		goto out_err;
 	}
-	gss_release_name(&ignore_min_stat, &client_name);
-
+	if (get_hostbased_client_name(client_name, mech, &hostbased_name)) {
+		/* get_hostbased_client_name() prints error msg */
+		maj_stat = GSS_S_BAD_NAME; /* XXX ? */
+		goto out_err;
+	}
 
 	/* Context complete. Pass handle_seq in out_handle to use
 	 * for context lookup in the kernel. */
@@ -419,7 +495,8 @@ handle_nullreq(FILE *f) {
 	/* We no longer need the gss context */
 	gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
 
-	do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime);
+	do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime,
+			hostbased_name);
 continue_needed:
 	send_response(f, &in_handle, &in_tok, maj_stat, min_stat,
 			&out_handle, &out_tok);
@@ -428,6 +505,9 @@ out:
 		free(ctx_token.value);
 	if (out_tok.value != NULL)
 		gss_release_buffer(&ignore_min_stat, &out_tok);
+	if (client_name)
+		gss_release_name(&ignore_min_stat, &client_name);
+	free(hostbased_name);
 	printerr(1, "finished handling null request\n");
 	return;
 
diff --git a/utils/idmapd/Makefile.am b/utils/idmapd/Makefile.am
index 4dabb3d..4218048 100644
--- a/utils/idmapd/Makefile.am
+++ b/utils/idmapd/Makefile.am
@@ -1,6 +1,5 @@
 ## Process this file with automake to produce Makefile.in
 
-man5_MANS	= idmapd.conf.man
 man8_MANS	= idmapd.man
 
 RPCPREFIX	= rpc.
@@ -8,7 +7,6 @@ KPREFIX		= @kprefix@
 sbin_PROGRAMS	= idmapd
 
 EXTRA_DIST = \
-	$(man5_MANS) \
 	$(man8_MANS) \
 	idmapd.conf
 
@@ -48,8 +46,8 @@ uninstall-hook:
 
 # XXX This makes some assumptions about what automake does.
 # XXX But there is no install-man-hook or install-man-local.
-install-man: install-man5 install-man8 install-man-links
-uninstall-man: uninstall-man5 uninstall-man8 uninstall-man-links
+install-man: install-man8 install-man-links
+uninstall-man: uninstall-man8 uninstall-man-links
 
 install-man-links:
 	(cd $(DESTDIR)$(man8dir) && \
diff --git a/utils/idmapd/idmapd.conf.man b/utils/idmapd/idmapd.conf.man
deleted file mode 100644
index 02722b1..0000000
--- a/utils/idmapd/idmapd.conf.man
+++ /dev/null
@@ -1,74 +0,0 @@
-.\"	$OpenBSD: mdoc.template,v 1.6 2001/02/03 08:22:44 niklas Exp $
-.\"
-.\" The following requests are required for all man pages.
-.Dd July 16, 2003
-.Dt idmapd.conf 5
-.Os
-.Sh NAME
-.Nm idmapd.conf
-.Nd configuration file for idmapd, the NFSv4 ID <-> Name Mapper
-.Sh SYNOPSIS
-Configuration file for idmapd, the NFSv4 ID <-> Name Mapper
-.Sh DESCRIPTION
-The
-.Nm
-configuration file has two sections, initiated by the strings
-[General] and [Mapping].  Each section may contain lines of the form
-.Dl ""
-.Dl variable = value
-.Dl ""
-The variables allowed in the General section are
-.Va Verbosity,
-.Va Pipefs-Directory,
-and
-.Va Domain,
-whose values have the same effect as the arguments to the
-.Fl v,
-.Fl p,
-and
-.Fl d
-commandline options, respectively.  The variables allowed in the
-Mapping section are
-.Va Nobody-User
-and
-.Va Nobody-Group,
-which have the same effect as the
-.Fl U
-and
-.Fl G
-commandline options.
-'
-.Sh EXAMPLES
-'
-An example
-.Pa /etc/idmapd.conf
-file:
-'
-.Bd -literal
-[General]
-
-Verbosity = 0
-Pipefs-Directory = /var/lib/nfs/rpc_pipefs
-Domain = localdomain
-
-[Mapping]
-
-Nobody-User = nobody
-Nobody-Group = nobody
-.Ed
-'
-.Sh SEE ALSO
-.Xr idmapd 8
-.\".Sh SEE ALSO
-.\".Xr nylon.conf 4
-.\" .Sh COMPATIBILITY
-.\".Sh STANDARDS
-.\".Sh ACKNOWLEDGEMENTS
-.Sh AUTHORS
-The idmapd software has been developed by Marius Aamodt Eriksen
-.Aq marius@citi.umich.edu .
-.\" .Sh HISTORY
-.\".Sh BUGS
-.\"Please report any bugs to Marius Aamodt Eriksen
-.\".Aq marius@monkey.org .
-.\" .Sh CAVEATS
diff --git a/utils/mount/configfile.c b/utils/mount/configfile.c
index 28b722c..1dd4159 100644
--- a/utils/mount/configfile.c
+++ b/utils/mount/configfile.c
@@ -194,13 +194,29 @@ void free_all(void)
 static char *versions[] = {"v2", "v3", "v4", "vers", "nfsvers", NULL};
 int inline check_vers(char *mopt, char *field)
 {
-	int i;
+	int i, found=0;
 
-	if (strncmp("mountvers", field, strlen("mountvers")) != 0) {
-		for (i=0; versions[i]; i++) 
-			if (strcasestr(mopt, versions[i]) != NULL)
-				return 1;
+	/*
+	 * First check to see if the config setting is one 
+	 * of the many version settings
+	 */
+	for (i=0; versions[i]; i++) { 
+		if (strcasestr(field, versions[i]) != NULL) {
+			found++;
+			break;
+		}
 	}
+	if (!found)
+		return 0;
+	/*
+	 * It appears the version is being set, now see
+	 * if the version appears on the command 
+	 */
+	for (i=0; versions[i]; i++)  {
+		if (strcasestr(mopt, versions[i]) != NULL)
+			return 1;
+	}
+
 	return 0;
 }
 
diff --git a/utils/mount/mount.c b/utils/mount/mount.c
index 355df79..82b9169 100644
--- a/utils/mount/mount.c
+++ b/utils/mount/mount.c
@@ -24,6 +24,7 @@
 
 #include <unistd.h>
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <stdio.h>
 #include <string.h>
 #include <errno.h>
@@ -593,6 +594,9 @@ int main(int argc, char *argv[])
 	if (mnt_err == EX_BG) {
 		printf(_("%s: backgrounding \"%s\"\n"),
 			progname, spec);
+		printf(_("%s: mount options: \"%s\"\n"),
+			progname, extra_opts);
+
 		fflush(stdout);
 
 		/*
diff --git a/utils/mount/network.c b/utils/mount/network.c
index 7b1152a..06cab6a 100644
--- a/utils/mount/network.c
+++ b/utils/mount/network.c
@@ -37,11 +37,13 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
+#include <sys/stat.h>
 #include <netinet/in.h>
 #include <rpc/rpc.h>
 #include <rpc/pmap_prot.h>
 #include <rpc/pmap_clnt.h>
 
+#include "sockaddr.h"
 #include "xcommon.h"
 #include "mount.h"
 #include "nls.h"
@@ -56,10 +58,6 @@
 #define CONNECT_TIMEOUT	(20)
 #define MOUNT_TIMEOUT	(30)
 
-#if SIZEOF_SOCKLEN_T - 0 == 0
-#define socklen_t unsigned int
-#endif
-
 extern int nfs_mount_data_version;
 extern char *progname;
 extern int verbose;
@@ -193,8 +191,18 @@ static const unsigned int *nfs_default_proto()
 }
 #endif /* MOUNT_CONFIG */
 
-static int nfs_lookup(const char *hostname, const sa_family_t family,
-		      struct sockaddr *sap, socklen_t *salen)
+/**
+ * nfs_lookup - resolve hostname to an IPv4 or IPv6 socket address
+ * @hostname: pointer to C string containing DNS hostname to resolve
+ * @family: address family hint
+ * @sap: pointer to buffer to fill with socket address
+ * @len: IN: size of buffer to fill; OUT: size of socket address
+ *
+ * Returns 1 and places a socket address at @sap if successful;
+ * otherwise zero.
+ */
+int nfs_lookup(const char *hostname, const sa_family_t family,
+		struct sockaddr *sap, socklen_t *salen)
 {
 	struct addrinfo *gai_results;
 	struct addrinfo gai_hint = {
@@ -243,25 +251,6 @@ static int nfs_lookup(const char *hostname, const sa_family_t family,
 }
 
 /**
- * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address
- * @hostname: pointer to C string containing DNS hostname to resolve
- * @sap: pointer to buffer to fill with socket address
- * @len: IN: size of buffer to fill; OUT: size of socket address
- *
- * Returns 1 and places a socket address at @sap if successful;
- * otherwise zero.
- */
-int nfs_name_to_address(const char *hostname,
-			struct sockaddr *sap, socklen_t *salen)
-{
-#ifdef IPV6_SUPPORTED
-	return nfs_lookup(hostname, AF_UNSPEC, sap, salen);
-#else	/* !IPV6_SUPPORTED */
-	return nfs_lookup(hostname, AF_INET, sap, salen);
-#endif	/* !IPV6_SUPPORTED */
-}
-
-/**
  * nfs_gethostbyname - resolve a hostname to an IPv4 address
  * @hostname: pointer to a C string containing a DNS hostname
  * @sin: returns an IPv4 address 
@@ -283,8 +272,8 @@ int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin)
  *		OUT: length of converted socket address
  *
  * Convert a presentation format address string to a socket address.
- * Similar to nfs_name_to_address(), but the DNS query is squelched,
- * and won't make any noise if the getaddrinfo() call fails.
+ * Similar to nfs_lookup(), but the DNS query is squelched, and it
+ * won't make any noise if the getaddrinfo() call fails.
  *
  * Returns 1 and fills in @sap and @salen if successful; otherwise zero.
  *
@@ -549,8 +538,8 @@ static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen,
 			  struct pmap *pmap, const unsigned long *versions,
 			  const unsigned int *protos)
 {
-	struct sockaddr_storage address;
-	struct sockaddr *saddr = (struct sockaddr *)&address;
+	union nfs_sockaddr address;
+	struct sockaddr *saddr = &address.sa;
 	const unsigned long prog = pmap->pm_prog, *p_vers;
 	const unsigned int prot = (u_int)pmap->pm_prot, *p_prot;
 	const u_short port = (u_short) pmap->pm_port;
@@ -840,8 +829,8 @@ int start_statd(void)
 int nfs_advise_umount(const struct sockaddr *sap, const socklen_t salen,
 		      const struct pmap *pmap, const dirpath *argp)
 {
-	struct sockaddr_storage address;
-	struct sockaddr *saddr = (struct sockaddr *)&address;
+	union nfs_sockaddr address;
+	struct sockaddr *saddr = &address.sa;
 	struct pmap mnt_pmap = *pmap;
 	struct timeval timeout = {
 		.tv_sec		= MOUNT_TIMEOUT >> 3,
@@ -1289,6 +1278,7 @@ nfs_nfs_version(struct mount_options *options, unsigned long *version)
 int
 nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol)
 {
+	sa_family_t family;
 	char *option;
 
 	switch (po_rightmost(options, nfs_transport_opttbl)) {
@@ -1300,17 +1290,8 @@ nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol)
 		return 1;
 	case 2: /* proto */
 		option = po_get(options, "proto");
-		if (option) {
-			if (strcmp(option, "tcp") == 0) {
-				*protocol = IPPROTO_TCP;
-				return 1;
-			}
-			if (strcmp(option, "udp") == 0) {
-				*protocol = IPPROTO_UDP;
-				return 1;
-			}
-			return 0;
-		}
+		if (option != NULL)
+			return nfs_get_proto(option, &family, protocol);
 	}
 
 	/*
@@ -1352,6 +1333,40 @@ nfs_nfs_port(struct mount_options *options, unsigned long *port)
 }
 
 /*
+ * Returns TRUE and fills in @family if a valid NFS protocol option
+ * is found, or FALSE if the option was specified with an invalid value.
+ */
+int nfs_nfs_proto_family(struct mount_options *options,
+				sa_family_t *family)
+{
+	unsigned long protocol;
+	char *option;
+
+#ifdef IPV6_SUPPORTED
+	*family = AF_UNSPEC;
+#else
+	*family = AF_INET;
+#endif
+
+	switch (po_rightmost(options, nfs_transport_opttbl)) {
+	case 0:	/* udp */
+		return 1;
+	case 1: /* tcp */
+		return 1;
+	case 2: /* proto */
+		option = po_get(options, "proto");
+		if (option != NULL)
+			return nfs_get_proto(option, family, &protocol);
+	}
+
+	/*
+	 * NFS transport protocol wasn't specified.  Return the
+	 * default address family.
+	 */
+	return 1;
+}
+
+/*
  * "mountprog" is supported only by the legacy mount command.  The
  * kernel mount client does not support this option.
  *
@@ -1419,20 +1434,12 @@ nfs_mount_version(struct mount_options *options, unsigned long *version)
 static int
 nfs_mount_protocol(struct mount_options *options, unsigned long *protocol)
 {
+	sa_family_t family;
 	char *option;
 
 	option = po_get(options, "mountproto");
-	if (option) {
-		if (strcmp(option, "tcp") == 0) {
-			*protocol = IPPROTO_TCP;
-			return 1;
-		}
-		if (strcmp(option, "udp") == 0) {
-			*protocol = IPPROTO_UDP;
-			return 1;
-		}
-		return 0;
-	}
+	if (option != NULL)
+		return nfs_get_proto(option, &family, protocol);
 
 	/*
 	 * MNT transport protocol wasn't specified.  If the NFS
@@ -1472,6 +1479,35 @@ nfs_mount_port(struct mount_options *options, unsigned long *port)
 	return 1;
 }
 
+/*
+ * Returns TRUE and fills in @family if a valid MNT protocol option
+ * is found, or FALSE if the option was specified with an invalid value.
+ */
+int nfs_mount_proto_family(struct mount_options *options,
+				sa_family_t *family)
+{
+	unsigned long protocol;
+	char *option;
+
+#ifdef HAVE_LIBTIRPC
+	*family = AF_UNSPEC;
+#else
+	*family = AF_INET;
+#endif
+
+	option = po_get(options, "mountproto");
+	if (option != NULL)
+		return nfs_get_proto(option, family, &protocol);
+
+	/*
+	 * MNT transport protocol wasn't specified.  If the NFS
+	 * transport protocol was specified, derive the family
+	 * from that; otherwise, return the default family for
+	 * NFS.
+	 */
+	return nfs_nfs_proto_family(options, family);
+}
+
 /**
  * nfs_options2pmap - set up pmap structs based on mount options
  * @options: pointer to mount options
diff --git a/utils/mount/network.h b/utils/mount/network.h
index 7eb89b0..2a3a110 100644
--- a/utils/mount/network.h
+++ b/utils/mount/network.h
@@ -44,7 +44,8 @@ int nfs_probe_bothports(const struct sockaddr *, const socklen_t,
 			struct pmap *, const struct sockaddr *,
 			const socklen_t, struct pmap *);
 int nfs_gethostbyname(const char *, struct sockaddr_in *);
-int nfs_name_to_address(const char *, struct sockaddr *, socklen_t *);
+int nfs_lookup(const char *hostname, const sa_family_t family,
+		struct sockaddr *sap, socklen_t *salen);
 int nfs_string_to_sockaddr(const char *, struct sockaddr *, socklen_t *);
 int nfs_present_sockaddr(const struct sockaddr *,
 			 const socklen_t, char *, const size_t);
@@ -56,6 +57,8 @@ int clnt_ping(struct sockaddr_in *, const unsigned long,
 
 struct mount_options;
 
+int nfs_nfs_proto_family(struct mount_options *options, sa_family_t *family);
+int nfs_mount_proto_family(struct mount_options *options, sa_family_t *family);
 int nfs_nfs_version(struct mount_options *options, unsigned long *version);
 int  nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol);
 
diff --git a/utils/mount/nfs.man b/utils/mount/nfs.man
index 2299637..93bd642 100644
--- a/utils/mount/nfs.man
+++ b/utils/mount/nfs.man
@@ -58,9 +58,17 @@ The server's hostname and export pathname
 are separated by a colon, while
 the mount options are separated by commas. The remaining fields
 are separated by blanks or tabs.
+.P
 The server's hostname can be an unqualified hostname,
 a fully qualified domain name,
-or a dotted quad IPv4 address.
+a dotted quad IPv4 address, or
+an IPv6 address enclosed in square brackets.
+Link-local and site-local IPv6 addresses must be accompanied by an
+interface identifier.
+See
+.BR ipv6 (7)
+for details on specifying raw IPv6 addresses.
+.P
 The
 .I fstype
 field contains either "nfs" (for version 2 or version 3 NFS mounts)
@@ -470,32 +478,38 @@ for mounting the
 .B nfs
 file system type.
 .TP 1.5i
-.BI proto= transport
-The transport the NFS client uses
+.BI proto= netid
+The transport protocol name and protocol family the NFS client uses
 to transmit requests to the NFS server for this mount point.
-.I transport
-can be either
-.B udp
-or
-.BR tcp .
-Each transport uses different default
+If an NFS server has both an IPv4 and an IPv6 address, using a specific
+netid will force the use of IPv4 or IPv6 networking to communicate
+with that server.
+.IP
+If support for TI-RPC is built into the
+.B mount.nfs
+command,
+.I netid
+is a valid netid listed in
+.IR /etc/netconfig .
+Otherwise,
+.I netid
+is one of "tcp," "udp," or "rdma," and only IPv4 may be used.
+.IP
+Each transport protocol uses different default
 .B retrans
 and
 .B timeo
-settings; refer to the description of these two mount options for details.
+settings.
+Refer to the description of these two mount options for details.
 .IP
 In addition to controlling how the NFS client transmits requests to
 the server, this mount option also controls how the
 .BR mount (8)
 command communicates with the server's rpcbind and mountd services.
-Specifying
-.B proto=tcp
-forces all traffic from the
+Specifying a netid that uses TCP forces all traffic from the
 .BR mount (8)
 command and the NFS client to use TCP.
-Specifying
-.B proto=udp
-forces all traffic types to use UDP.
+Specifying a netid that uses UDP forces all traffic types to use UDP.
 .IP
 If the
 .B proto
@@ -548,15 +562,20 @@ or the server's mountd service is not available on the advertised port.
 This option can be used when mounting an NFS server
 through a firewall that blocks the rpcbind protocol.
 .TP 1.5i
-.BI mountproto= transport
-The transport the NFS client uses
+.BI mountproto= netid
+The transport protocol name and protocol family the NFS client uses
 to transmit requests to the NFS server's mountd service when performing
 this mount request, and when later unmounting this mount point.
-.I transport
-can be either
-.B udp
-or
-.BR tcp .
+.IP
+If support for TI-RPC is built into the
+.B mount.nfs
+command,
+.I netid
+is a valid netid listed in
+.IR /etc/netconfig .
+Otherwise,
+.I netid
+is one of "tcp" or "udp," and only IPv4 may be used.
 .IP
 This option can be used when mounting an NFS server
 through a firewall that blocks a particular transport.
@@ -566,6 +585,7 @@ option, different transports for mountd requests and NFS requests
 can be specified.
 If the server's mountd service is not available via the specified
 transport, the mount request fails.
+.IP
 Refer to the TRANSPORT METHODS section for more on how the
 .B mountproto
 mount option interacts with the
@@ -709,17 +729,26 @@ for mounting the
 .B nfs4
 file system type.
 .TP 1.5i
-.BI proto= transport
-The transport the NFS client uses
+.BI proto= netid
+The transport protocol name and protocol family the NFS client uses
 to transmit requests to the NFS server for this mount point.
-.I transport
-can be either
-.B udp
-or
-.BR tcp .
+If an NFS server has both an IPv4 and an IPv6 address, using a specific
+netid will force the use of IPv4 or IPv6 networking to communicate
+with that server.
+.IP
+If support for TI-RPC is built into the
+.B mount.nfs
+command,
+.I netid
+is a valid netid listed in
+.IR /etc/netconfig .
+Otherwise,
+.I netid
+is one of "tcp" or "udp," and only IPv4 may be used.
+.IP
 All NFS version 4 servers are required to support TCP,
 so if this mount option is not specified, the NFS version 4 client
-uses the TCP transport protocol.
+uses the TCP protocol.
 Refer to the TRANSPORT METHODS section for more details.
 .TP 1.5i
 .BI port= n
@@ -779,7 +808,8 @@ The DATA AND METADATA COHERENCE section discusses
 the behavior of this option in more detail.
 .TP 1.5i
 .BI clientaddr= n.n.n.n
-Specifies  a  single  IPv4  address  (in dotted-quad form)
+Specifies a single IPv4 address (in dotted-quad form),
+or a non-link-local IPv6 address,
 that the NFS client advertises to allow servers
 to perform NFS version 4 callback requests against
 files on this mount point. If  the  server is unable to
@@ -855,6 +885,14 @@ This example can be used to mount /usr over NFS.
 .TA 2.5i +0.7i +0.7i +.7i
 	server:/export	/usr	nfs	ro,nolock,nocto,actimeo=3600	0 0
 .FI
+.P
+This example shows how to mount an NFS server
+using a raw IPv6 link-local address.
+.P
+.NF
+.TA 2.5i +0.7i +0.7i +.7i
+	[fe80::215:c5ff:fb3e:e2b1%eth0]:/export	/mnt	nfs	defaults	0 0
+.FI
 .SH "TRANSPORT METHODS"
 NFS clients send requests to NFS servers via
 Remote Procedure Calls, or
@@ -1498,6 +1536,8 @@ such as security negotiation, server referrals, and named attributes.
 .BR mount.nfs (5),
 .BR umount.nfs (5),
 .BR exports (5),
+.BR netconfig (5),
+.BR ipv6 (7),
 .BR nfsd (8),
 .BR sm-notify (8),
 .BR rpc.statd (8),
diff --git a/utils/mount/nfs4mount.c b/utils/mount/nfs4mount.c
index a2f318f..4a2fab7 100644
--- a/utils/mount/nfs4mount.c
+++ b/utils/mount/nfs4mount.c
@@ -217,8 +217,11 @@ int nfs4mount(const char *spec, const char *node, int flags,
 				progname);
 		goto fail;
 	}
-	snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s",
-		 old_opts, *old_opts ? "," : "", s);
+	if (running_bg)
+		strncpy(new_opts, old_opts, sizeof(new_opts));
+	else
+		snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s",
+			 old_opts, *old_opts ? "," : "", s);
 	*extra_opts = xstrdup(new_opts);
 
 	/* Set default options.
@@ -434,15 +437,17 @@ int nfs4mount(const char *spec, const char *node, int flags,
 			break;
 		}
 
-		switch(rpc_createerr.cf_stat){
-		case RPC_TIMEDOUT:
-			break;
-		case RPC_SYSTEMERROR:
-			if (errno == ETIMEDOUT)
+		if (!bg) {
+			switch(rpc_createerr.cf_stat) {
+			case RPC_TIMEDOUT:
 				break;
-		default:
-			rpc_mount_errors(hostname, 0, bg);
-			goto fail;
+			case RPC_SYSTEMERROR:
+				if (errno == ETIMEDOUT)
+					break;
+			default:
+				rpc_mount_errors(hostname, 0, bg);
+				goto fail;
+			}
 		}
 
 		if (bg && !running_bg) {
diff --git a/utils/mount/nfsmount.c b/utils/mount/nfsmount.c
index 6355681..6b3356c 100644
--- a/utils/mount/nfsmount.c
+++ b/utils/mount/nfsmount.c
@@ -170,7 +170,7 @@ parse_options(char *old_opts, struct nfs_mount_data *data,
 	struct pmap *mnt_pmap = &mnt_server->pmap;
 	struct pmap *nfs_pmap = &nfs_server->pmap;
 	int len;
-	char *opt, *opteq, *p, *opt_b;
+	char *opt, *opteq, *p, *opt_b, *tmp_opts;
 	char *mounthost = NULL;
 	char cbuf[128];
 	int open_quote = 0;
@@ -179,7 +179,8 @@ parse_options(char *old_opts, struct nfs_mount_data *data,
 	*bg = 0;
 
 	len = strlen(new_opts);
-	for (p=old_opts, opt_b=NULL; p && *p; p++) {
+	tmp_opts = xstrdup(old_opts);
+	for (p=tmp_opts, opt_b=NULL; p && *p; p++) {
 		if (!opt_b)
 			opt_b = p;		/* begin of the option item */
 		if (*p == '"')
@@ -457,10 +458,12 @@ parse_options(char *old_opts, struct nfs_mount_data *data,
 			goto out_bad;
 		*mnt_server->hostname = mounthost;
 	}
+	free(tmp_opts);
 	return 1;
  bad_parameter:
 	nfs_error(_("%s: Bad nfs mount parameter: %s\n"), progname, opt);
  out_bad:
+	free(tmp_opts);
 	return 0;
 }
 
diff --git a/utils/mount/nfsumount.c b/utils/mount/nfsumount.c
index c5505b1..9d798a2 100644
--- a/utils/mount/nfsumount.c
+++ b/utils/mount/nfsumount.c
@@ -169,10 +169,15 @@ out:
 static int nfs_umount_do_umnt(struct mount_options *options,
 			      char **hostname, char **dirname)
 {
-	struct sockaddr_storage address;
-	struct sockaddr *sap = (struct sockaddr *)&address;
+	union {
+		struct sockaddr		sa;
+		struct sockaddr_in	s4;
+		struct sockaddr_in6	s6;
+	} address;
+	struct sockaddr *sap = &address.sa;
 	socklen_t salen = sizeof(address);
 	struct pmap nfs_pmap, mnt_pmap;
+	sa_family_t family;
 
 	if (!nfs_options2pmap(options, &nfs_pmap, &mnt_pmap)) {
 		nfs_error(_("%s: bad mount options"), progname);
@@ -189,8 +194,10 @@ static int nfs_umount_do_umnt(struct mount_options *options,
 		return EX_FAIL;
 	}
 
-	if (nfs_name_to_address(*hostname, sap, &salen) == 0)
-		/* nfs_name_to_address reports any errors */
+	if (!nfs_mount_proto_family(options, &family))
+		return 0;
+	if (!nfs_lookup(*hostname, family, sap, &salen))
+		/* nfs_lookup reports any errors */
 		return EX_FAIL;
 
 	if (nfs_advise_umount(sap, salen, &mnt_pmap, dirname) == 0)
diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c
index b595649..57a4b32 100644
--- a/utils/mount/stropts.c
+++ b/utils/mount/stropts.c
@@ -35,9 +35,11 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
+#include "sockaddr.h"
 #include "xcommon.h"
 #include "mount.h"
 #include "nls.h"
+#include "nfsrpc.h"
 #include "mount_constants.h"
 #include "stropts.h"
 #include "error.h"
@@ -81,7 +83,7 @@ struct nfsmount_info {
 				*node,		/* mounted-on dir */
 				*type;		/* "nfs" or "nfs4" */
 	char			*hostname;	/* server's hostname */
-	struct sockaddr_storage	address;	/* server's address */
+	union nfs_sockaddr	address;
 	socklen_t		salen;		/* size of server's address */
 
 	struct mount_options	*options;	/* parsed mount options */
@@ -204,9 +206,9 @@ static int nfs_append_clientaddr_option(const struct sockaddr *sap,
 					socklen_t salen,
 					struct mount_options *options)
 {
-	struct sockaddr_storage dummy;
-	struct sockaddr *my_addr = (struct sockaddr *)&dummy;
-	socklen_t my_len = sizeof(dummy);
+	union nfs_sockaddr address;
+	struct sockaddr *my_addr = &address.sa;
+	socklen_t my_len = sizeof(address);
 
 	if (po_contains(options, "clientaddr") == PO_FOUND)
 		return 1;
@@ -218,21 +220,33 @@ static int nfs_append_clientaddr_option(const struct sockaddr *sap,
 }
 
 /*
- * Resolve the 'mounthost=' hostname and append a new option using
- * the resulting address.
+ * Determine whether to append a 'mountaddr=' option.  The option is needed if:
+ *
+ *   1. "mounthost=" was specified, or
+ *   2. The address families for proto= and mountproto= are different.
  */
-static int nfs_fix_mounthost_option(struct mount_options *options)
+static int nfs_fix_mounthost_option(struct mount_options *options,
+		const char *nfs_hostname)
 {
-	struct sockaddr_storage dummy;
-	struct sockaddr *sap = (struct sockaddr *)&dummy;
-	socklen_t salen = sizeof(dummy);
+	union nfs_sockaddr address;
+	struct sockaddr *sap = &address.sa;
+	socklen_t salen = sizeof(address);
+	sa_family_t nfs_family, mnt_family;
 	char *mounthost;
 
+	if (!nfs_nfs_proto_family(options, &nfs_family))
+		return 0;
+	if (!nfs_mount_proto_family(options, &mnt_family))
+		return 0;
+
 	mounthost = po_get(options, "mounthost");
-	if (!mounthost)
-		return 1;
+	if (mounthost == NULL) {
+		if (nfs_family == mnt_family)
+			return 1;
+		mounthost = (char *)nfs_hostname;
+	}
 
-	if (!nfs_name_to_address(mounthost, sap, &salen)) {
+	if (!nfs_lookup(mounthost, mnt_family, sap, &salen)) {
 		nfs_error(_("%s: unable to determine mount server's address"),
 				progname);
 		return 0;
@@ -319,13 +333,16 @@ static int nfs_set_version(struct nfsmount_info *mi)
  */
 static int nfs_validate_options(struct nfsmount_info *mi)
 {
-	struct sockaddr *sap = (struct sockaddr *)&mi->address;
+	struct sockaddr *sap = &mi->address.sa;
+	sa_family_t family;
 
 	if (!nfs_parse_devname(mi->spec, &mi->hostname, NULL))
 		return 0;
 
+	if (!nfs_nfs_proto_family(mi->options, &family))
+		return 0;
 	mi->salen = sizeof(mi->address);
-	if (!nfs_name_to_address(mi->hostname, sap, &mi->salen))
+	if (!nfs_lookup(mi->hostname, family, sap, &mi->salen))
 		return 0;
 
 	if (!nfs_set_version(mi))
@@ -371,10 +388,13 @@ static int nfs_extract_server_addresses(struct mount_options *options,
 }
 
 static int nfs_construct_new_options(struct mount_options *options,
+				     struct sockaddr *nfs_saddr,
 				     struct pmap *nfs_pmap,
+				     struct sockaddr *mnt_saddr,
 				     struct pmap *mnt_pmap)
 {
 	char new_option[64];
+	char *netid;
 
 	po_remove_all(options, "nfsprog");
 	po_remove_all(options, "mountprog");
@@ -391,20 +411,14 @@ static int nfs_construct_new_options(struct mount_options *options,
 	po_remove_all(options, "proto");
 	po_remove_all(options, "udp");
 	po_remove_all(options, "tcp");
-	switch (nfs_pmap->pm_prot) {
-	case IPPROTO_TCP:
-		snprintf(new_option, sizeof(new_option) - 1,
-			 "proto=tcp");
-		if (po_append(options, new_option) == PO_FAILED)
-			return 0;
-		break;
-	case IPPROTO_UDP:
-		snprintf(new_option, sizeof(new_option) - 1,
-			 "proto=udp");
-		if (po_append(options, new_option) == PO_FAILED)
-			return 0;
-		break;
-	}
+	netid = nfs_get_netid(nfs_saddr->sa_family, nfs_pmap->pm_prot);
+	if (netid == NULL)
+		return 0;
+	snprintf(new_option, sizeof(new_option) - 1,
+			 "proto=%s", netid);
+	free(netid);
+	if (po_append(options, new_option) == PO_FAILED)
+		return 0;
 
 	po_remove_all(options, "port");
 	if (nfs_pmap->pm_port != NFS_PORT) {
@@ -421,20 +435,14 @@ static int nfs_construct_new_options(struct mount_options *options,
 		return 0;
 
 	po_remove_all(options, "mountproto");
-	switch (mnt_pmap->pm_prot) {
-	case IPPROTO_TCP:
-		snprintf(new_option, sizeof(new_option) - 1,
-			 "mountproto=tcp");
-		if (po_append(options, new_option) == PO_FAILED)
-			return 0;
-		break;
-	case IPPROTO_UDP:
-		snprintf(new_option, sizeof(new_option) - 1,
-			 "mountproto=udp");
-		if (po_append(options, new_option) == PO_FAILED)
-			return 0;
-		break;
-	}
+	netid = nfs_get_netid(mnt_saddr->sa_family, mnt_pmap->pm_prot);
+	if (netid == NULL)
+		return 0;
+	snprintf(new_option, sizeof(new_option) - 1,
+			 "mountproto=%s", netid);
+	free(netid);
+	if (po_append(options, new_option) == PO_FAILED)
+		return 0;
 
 	po_remove_all(options, "mountport");
 	snprintf(new_option, sizeof(new_option) - 1,
@@ -461,12 +469,12 @@ static int nfs_construct_new_options(struct mount_options *options,
 static int
 nfs_rewrite_pmap_mount_options(struct mount_options *options)
 {
-	struct sockaddr_storage nfs_address;
-	struct sockaddr *nfs_saddr = (struct sockaddr *)&nfs_address;
+	union nfs_sockaddr nfs_address;
+	struct sockaddr *nfs_saddr = &nfs_address.sa;
 	socklen_t nfs_salen = sizeof(nfs_address);
 	struct pmap nfs_pmap;
-	struct sockaddr_storage mnt_address;
-	struct sockaddr *mnt_saddr = (struct sockaddr *)&mnt_address;
+	union nfs_sockaddr mnt_address;
+	struct sockaddr *mnt_saddr = &mnt_address.sa;
 	socklen_t mnt_salen = sizeof(mnt_address);
 	struct pmap mnt_pmap;
 	char *option;
@@ -510,7 +518,8 @@ nfs_rewrite_pmap_mount_options(struct mount_options *options)
 		return 0;
 	}
 
-	if (!nfs_construct_new_options(options, &nfs_pmap, &mnt_pmap)) {
+	if (!nfs_construct_new_options(options, nfs_saddr, &nfs_pmap,
+					mnt_saddr, &mnt_pmap)) {
 		errno = EINVAL;
 		return 0;
 	}
@@ -566,7 +575,7 @@ static int nfs_try_mount_v3v2(struct nfsmount_info *mi)
 		return result;
 	}
 
-	if (!nfs_fix_mounthost_option(options)) {
+	if (!nfs_fix_mounthost_option(options, mi->hostname)) {
 		errno = EINVAL;
 		goto out_fail;
 	}
@@ -601,7 +610,7 @@ out_fail:
  */
 static int nfs_try_mount_v4(struct nfsmount_info *mi)
 {
-	struct sockaddr *sap = (struct sockaddr *)&mi->address;
+	struct sockaddr *sap = &mi->address.sa;
 	struct mount_options *options = po_dup(mi->options);
 	int result = 0;
 
@@ -611,6 +620,18 @@ static int nfs_try_mount_v4(struct nfsmount_info *mi)
 	}
 
 	if (mi->version == 0) {
+		if (po_contains(options, "mounthost") ||
+			po_contains(options, "mountaddr") ||
+			po_contains(options, "mountvers") ||
+			po_contains(options, "mountproto")) {
+		/*
+		 * Since these mountd options are set assume version 3
+		 * is wanted so error out with EPROTONOSUPPORT so the
+		 * protocol negation starts with v3.
+		 */
+			errno = EPROTONOSUPPORT;
+			goto out_fail;
+		}
 		if (po_append(options, "vers=4") == PO_FAILED) {
 			errno = EINVAL;
 			goto out_fail;
@@ -656,9 +677,10 @@ static int nfs_try_mount(struct nfsmount_info *mi)
 				/* 
 				 * To deal with legacy Linux servers that don't
 				 * automatically export a pseudo root, retry
-				 * ENOENT errors using version 3
+				 * ENOENT errors using version 3. And for
+				 * Linux servers prior to 2.6.25, retry EPERM
 				 */
-				if (errno != ENOENT)
+				if (errno != ENOENT && errno != EPERM)
 					break;
 			}
 		}
diff --git a/utils/mountd/Makefile.am b/utils/mountd/Makefile.am
index 1e76cf8..eba81fc 100644
--- a/utils/mountd/Makefile.am
+++ b/utils/mountd/Makefile.am
@@ -8,7 +8,7 @@ KPREFIX		= @kprefix@
 sbin_PROGRAMS	= mountd
 
 mountd_SOURCES = mountd.c mount_dispatch.c auth.c rmtab.c cache.c \
-		 svc_run.c fsloc.c mountd.h
+		 svc_run.c fsloc.c v4root.c mountd.h
 mountd_LDADD = ../../support/export/libexport.a \
 	       ../../support/nfs/libnfs.a \
 	       ../../support/misc/libmisc.a \
diff --git a/utils/mountd/auth.c b/utils/mountd/auth.c
index 575f207..13eba70 100644
--- a/utils/mountd/auth.c
+++ b/utils/mountd/auth.c
@@ -20,6 +20,7 @@
 #include "exportfs.h"
 #include "mountd.h"
 #include "xmalloc.h"
+#include "v4root.h"
 
 enum auth_error
 {
@@ -102,75 +103,91 @@ auth_reload()
 	memset(&my_client, 0, sizeof(my_client));
 	xtab_export_read();
 	check_useipaddr();
+	v4root_set();
+
 	++counter;
 
 	return counter;
 }
 
+static char *get_client_hostname(struct sockaddr_in *caller, struct hostent *hp, enum auth_error *error)
+{
+	char *n;
+
+	if (use_ipaddr)
+		return strdup(inet_ntoa(caller->sin_addr));
+	n = client_compose(hp);
+	*error = unknown_host;
+	if (!n)
+		return NULL;
+	if (*n)
+		return n;
+	free(n);
+	return strdup("DEFAULT");
+}
+
+/* return static nfs_export with details filled in */
 static nfs_export *
-auth_authenticate_internal(char *what, struct sockaddr_in *caller,
+auth_authenticate_newcache(char *what, struct sockaddr_in *caller,
 			   char *path, struct hostent *hp,
 			   enum auth_error *error)
 {
-	nfs_export		*exp;
+	nfs_export *exp;
+	int i;
 
-	if (new_cache) {
-		int i;
-		/* return static nfs_export with details filled in */
-		char *n;
-		free(my_client.m_hostname);
-		if (use_ipaddr) {
-			my_client.m_hostname =
-				strdup(inet_ntoa(caller->sin_addr));
-		} else {
-			n = client_compose(hp);
-			*error = unknown_host;
-			if (!n)
-				my_client.m_hostname = NULL;
-			else if (*n)
-				my_client.m_hostname = n;
-			else {
-				free(n);
-				my_client.m_hostname = strdup("DEFAULT");
-			}
+	free(my_client.m_hostname);
+
+	my_client.m_hostname = get_client_hostname(caller, hp, error);
+	if (my_client.m_hostname == NULL)
+		return NULL;
+
+	my_client.m_naddr = 1;
+	my_client.m_addrlist[0] = caller->sin_addr;
+	my_exp.m_client = &my_client;
+
+	exp = NULL;
+	for (i = 0; !exp && i < MCL_MAXTYPES; i++)
+		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
+			if (strcmp(path, exp->m_export.e_path))
+				continue;
+			if (!use_ipaddr && !client_member(my_client.m_hostname, exp->m_client->m_hostname))
+				continue;
+			if (use_ipaddr && !client_check(exp->m_client, hp))
+				continue;
+			break;
 		}
-		if (my_client.m_hostname == NULL)
-			return NULL;
-		my_client.m_naddr = 1;
-		my_client.m_addrlist[0] = caller->sin_addr;
-		my_exp.m_client = &my_client;
-
-		exp = NULL;
-		for (i = 0; !exp && i < MCL_MAXTYPES; i++) 
-			for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
-				if (strcmp(path, exp->m_export.e_path))
-					continue;
-				if (!use_ipaddr && !client_member(my_client.m_hostname, exp->m_client->m_hostname))
-					continue;
-				if (use_ipaddr && !client_check(exp->m_client, hp))
-					continue;
-				break;
-			}
-		*error = not_exported;
-		if (!exp)
-			return exp;
+	*error = not_exported;
+	if (!exp)
+		return NULL;
 
-		my_exp.m_export = exp->m_export;
-		exp = &my_exp;
+	my_exp.m_export = exp->m_export;
+	exp = &my_exp;
+	return exp;
+}
+
+static nfs_export *
+auth_authenticate_internal(char *what, struct sockaddr_in *caller,
+			   char *path, struct hostent *hp,
+			   enum auth_error *error)
+{
+	nfs_export *exp;
 
+	if (new_cache) {
+		exp = auth_authenticate_newcache(what, caller, path, hp, error);
+		if (!exp)
+			return NULL;
 	} else {
 		if (!(exp = export_find(hp, path))) {
 			*error = no_entry;
 			return NULL;
 		}
-		if (!exp->m_mayexport) {
-			*error = not_exported;
-			return NULL;
-		}
+	}
+	if (exp->m_export.e_flags & NFSEXP_V4ROOT) {
+		*error = no_entry;
+		return NULL;
 	}
 	if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) &&
-		    (ntohs(caller->sin_port) <  IPPORT_RESERVED/2 ||
-		     ntohs(caller->sin_port) >= IPPORT_RESERVED)) {
+		     ntohs(caller->sin_port) >= IPPORT_RESERVED) {
 		*error = illegal_port;
 		return NULL;
 	}
diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c
index e4e2f22..d63e10a 100644
--- a/utils/mountd/cache.c
+++ b/utils/mountd/cache.c
@@ -614,73 +614,54 @@ static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *ex
 	return qword_eol(f);
 }
 
-void nfsd_export(FILE *f)
+static int is_subdirectory(char *subpath, char *path)
 {
-	/* requests are:
-	 *  domain path
-	 * determine export options and return:
-	 *  domain path expiry flags anonuid anongid fsid
-	 */
-
-	char *cp;
-	int i;
-	char *dom, *path;
-	nfs_export *exp, *found = NULL;
-	int found_type = 0;
-	struct in_addr addr;
-	struct hostent *he = NULL;
-
-
-	if (readline(fileno(f), &lbuf, &lbuflen) != 1)
-		return;
+	int l = strlen(path);
 
-	xlog(D_CALL, "nfsd_export: inbuf '%s'", lbuf);
+	return strcmp(subpath, path) == 0
+		|| (strncmp(subpath, path, l) == 0 && path[l] == '/');
+}
 
-	cp = lbuf;
-	dom = malloc(strlen(cp));
-	path = malloc(strlen(cp));
+static int path_matches(nfs_export *exp, char *path)
+{
+	if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT)
+		return is_subdirectory(path, exp->m_export.e_path);
+	return strcmp(path, exp->m_export.e_path) == 0;
+}
 
-	if (!dom || !path)
-		goto out;
+static int client_matches(nfs_export *exp, char *dom, struct hostent *he)
+{
+	if (use_ipaddr)
+		return client_check(exp->m_client, he);
+	return client_member(dom, exp->m_client->m_hostname);
+}
 
-	if (qword_get(&cp, dom, strlen(lbuf)) <= 0)
-		goto out;
-	if (qword_get(&cp, path, strlen(lbuf)) <= 0)
-		goto out;
+static int export_matches(nfs_export *exp, char *dom, char *path, struct hostent *he)
+{
+	return path_matches(exp, path) && client_matches(exp, dom, he);
+}
 
-	auth_reload();
+static nfs_export *lookup_export(char *dom, char *path, struct hostent *he)
+{
+	nfs_export *exp;
+	nfs_export *found = NULL;
+	int found_type = 0;
+	int i;
 
-	/* now find flags for this export point in this domain */
 	for (i=0 ; i < MCL_MAXTYPES; i++) {
 		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
-			if (!use_ipaddr && !client_member(dom, exp->m_client->m_hostname))
-				continue;
-			if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) {
-				/* if path is a mountpoint below e_path, then OK */
-				int l = strlen(exp->m_export.e_path);
-				if (strcmp(path, exp->m_export.e_path) == 0 ||
-				    (strncmp(path, exp->m_export.e_path, l) == 0 &&
-				     path[l] == '/' &&
-				     is_mountpoint(path)))
-					/* ok */;
-				else
-					continue;
-			} else if (strcmp(path, exp->m_export.e_path) != 0)
+			if (!export_matches(exp, dom, path, he))
 				continue;
-			if (use_ipaddr) {
-				if (he == NULL) {
-					if (!inet_aton(dom, &addr))
-						goto out;
-					he = client_resolve(addr);
-				}
-				if (!client_check(exp->m_client, he))
-					continue;
-			}
 			if (!found) {
 				found = exp;
 				found_type = i;
 				continue;
 			}
+
+			/* Always prefer non-V4ROOT mounts */
+			if (found->m_export.e_flags & NFSEXP_V4ROOT)
+				continue;
+
 			/* If one is a CROSSMOUNT, then prefer the longest path */
 			if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) ||
 			     (exp->m_export.e_flags & NFSEXP_CROSSMOUNT)) &&
@@ -703,6 +684,50 @@ void nfsd_export(FILE *f)
 			}
 		}
 	}
+	return found;
+}
+
+void nfsd_export(FILE *f)
+{
+	/* requests are:
+	 *  domain path
+	 * determine export options and return:
+	 *  domain path expiry flags anonuid anongid fsid
+	 */
+
+	char *cp;
+	char *dom, *path;
+	nfs_export *found = NULL;
+	struct in_addr addr;
+	struct hostent *he = NULL;
+
+
+	if (readline(fileno(f), &lbuf, &lbuflen) != 1)
+		return;
+
+	xlog(D_CALL, "nfsd_export: inbuf '%s'", lbuf);
+
+	cp = lbuf;
+	dom = malloc(strlen(cp));
+	path = malloc(strlen(cp));
+
+	if (!dom || !path)
+		goto out;
+
+	if (qword_get(&cp, dom, strlen(lbuf)) <= 0)
+		goto out;
+	if (qword_get(&cp, path, strlen(lbuf)) <= 0)
+		goto out;
+
+	auth_reload();
+
+	if (use_ipaddr) {
+		if (!inet_aton(dom, &addr))
+			goto out;
+		he = client_resolve(addr);
+	}
+
+	found = lookup_export(dom, path, he);
 
 	if (found) {
 		if (dump_to_cache(f, dom, path, &found->m_export) < 0) {
diff --git a/utils/mountd/mount_dispatch.c b/utils/mountd/mount_dispatch.c
index 199fcec..ba6981d 100644
--- a/utils/mountd/mount_dispatch.c
+++ b/utils/mountd/mount_dispatch.c
@@ -70,12 +70,10 @@ mount_dispatch(struct svc_req *rqstp, SVCXPRT *transp)
 {
 	union mountd_arguments 	argument;
 	union mountd_results	result;
-#ifdef HAVE_TCP_WRAPPER
-	struct sockaddr_in *sin = nfs_getrpccaller_in(transp);
 
+#ifdef HAVE_TCP_WRAPPER
 	/* remote host authorization check */
-	if (sin->sin_family == AF_INET &&
-	    !check_default("mountd", sin, rqstp->rq_proc, MOUNTPROG)) {
+	if (!check_default("mountd", nfs_getrpccaller(transp), MOUNTPROG)) {
 		svcerr_auth (transp, AUTH_FAILED);
 		return;
 	}
diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
index 888fd8c..a0a1f2d 100644
--- a/utils/mountd/mountd.c
+++ b/utils/mountd/mountd.c
@@ -509,12 +509,89 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
 	return fh;
 }
 
+static void remove_all_clients(exportnode *e)
+{
+	struct groupnode *g, *ng;
+
+	for (g = e->ex_groups; g; g = ng) {
+		ng = g->gr_next;
+		xfree(g->gr_name);
+		xfree(g);
+	}
+	e->ex_groups = NULL;
+}
+
+static void free_exportlist(exports *elist)
+{
+	struct exportnode *e, *ne;
+
+	for (e = *elist; e != NULL; e = ne) {
+		ne = e->ex_next;
+		remove_all_clients(e);
+		xfree(e->ex_dir);
+		xfree(e);
+	}
+	*elist = NULL;
+}
+
+static void prune_clients(nfs_export *exp, struct exportnode *e)
+{
+	struct hostent 	*hp;
+	struct groupnode *c, **cp;
+
+	cp = &e->ex_groups;
+	while ((c = *cp) != NULL) {
+		if (client_gettype(c->gr_name) == MCL_FQDN
+				&& (hp = gethostbyname(c->gr_name))) {
+			hp = hostent_dup(hp);
+			if (client_check(exp->m_client, hp)) {
+				*cp = c->gr_next;
+				xfree(c->gr_name);
+				xfree(c);
+				xfree (hp);
+				continue;
+			}
+			xfree (hp);
+		}
+		cp = &(c->gr_next);
+	}
+}
+
+static exportnode *lookup_or_create_elist_entry(exports *elist, nfs_export *exp)
+{
+	exportnode *e;
+
+	for (e = *elist; e != NULL; e = e->ex_next) {
+		if (!strcmp(exp->m_export.e_path, e->ex_dir))
+			return e;
+	}
+	e = xmalloc(sizeof(*e));
+	e->ex_next = *elist;
+	e->ex_groups = NULL;
+	e->ex_dir = xstrdup(exp->m_export.e_path);
+	*elist = e;
+	return e;
+}
+
+static void insert_group(struct exportnode *e, char *newname)
+{
+	struct groupnode *g;
+
+	for (g = e->ex_groups; g; g = g->gr_next)
+		if (strcmp(g->gr_name, newname))
+			return;
+
+	g = xmalloc(sizeof(*g));
+	g->gr_name = xstrdup(newname);
+	g->gr_next = e->ex_groups;
+	e->ex_groups = g;
+}
+
 static exports
 get_exportlist(void)
 {
 	static exports		elist = NULL;
-	struct exportnode	*e, *ne;
-	struct groupnode	*g, *ng, *c, **cp;
+	struct exportnode	*e;
 	nfs_export		*exp;
 	int			i;
 	static unsigned int	ecounter;
@@ -526,77 +603,26 @@ get_exportlist(void)
 
 	ecounter = acounter;
 
-	for (e = elist; e != NULL; e = ne) {
-		ne = e->ex_next;
-		for (g = e->ex_groups; g != NULL; g = ng) {
-			ng = g->gr_next;
-			xfree(g->gr_name);
-			xfree(g);
-		}
-		xfree(e->ex_dir);
-		xfree(e);
-	}
-	elist = NULL;
+	free_exportlist(&elist);
 
 	for (i = 0; i < MCL_MAXTYPES; i++) {
 		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
-			for (e = elist; e != NULL; e = e->ex_next) {
-				if (!strcmp(exp->m_export.e_path, e->ex_dir))
-					break;
-			}
-			if (!e) {
-				e = (struct exportnode *) xmalloc(sizeof(*e));
-				e->ex_next = elist;
-				e->ex_groups = NULL;
-				e->ex_dir = xstrdup(exp->m_export.e_path);
-				elist = e;
-			}
-
-			/* We need to check if we should remove
-			   previous ones. */
-			if (i == MCL_ANONYMOUS && e->ex_groups) {
-				for (g = e->ex_groups; g; g = ng) {
-					ng = g->gr_next;
-					xfree(g->gr_name);
-					xfree(g);
-				}
-				e->ex_groups = NULL;
+			 /* Don't show pseudo exports */
+			if (exp->m_export.e_flags & NFSEXP_V4ROOT)
 				continue;
-			}
-
-			if (i != MCL_FQDN && e->ex_groups) {
-			  struct hostent 	*hp;
+			e = lookup_or_create_elist_entry(&elist, exp);
 
-			  cp = &e->ex_groups;
-			  while ((c = *cp) != NULL) {
-			    if (client_gettype (c->gr_name) == MCL_FQDN
-			        && (hp = gethostbyname(c->gr_name))) {
-			      hp = hostent_dup (hp);
-			      if (client_check(exp->m_client, hp)) {
-				*cp = c->gr_next;
-				xfree(c->gr_name);
-				xfree(c);
-				xfree (hp);
+			/* exports to "*" absorb any others */
+			if (i == MCL_ANONYMOUS && e->ex_groups) {
+				remove_all_clients(e);
 				continue;
-			      }
-			      xfree (hp);
-			    }
-			    cp = &(c->gr_next);
-			  }
 			}
+			/* non-FQDN's absorb FQDN's they contain: */
+			if (i != MCL_FQDN && e->ex_groups)
+				prune_clients(exp, e);
 
-			if (exp->m_export.e_hostname [0] != '\0') {
-				for (g = e->ex_groups; g; g = g->gr_next)
-					if (strcmp (exp->m_export.e_hostname,
-						    g->gr_name) == 0)
-						break;
-				if (g)
-					continue;
-				g = (struct groupnode *) xmalloc(sizeof(*g));
-				g->gr_name = xstrdup(exp->m_export.e_hostname);
-				g->gr_next = e->ex_groups;
-				e->ex_groups = g;
-			}
+			if (exp->m_export.e_hostname[0] != '\0')
+				insert_group(e, exp->m_export.e_hostname);
 		}
 	}
 
diff --git a/utils/mountd/rmtab.c b/utils/mountd/rmtab.c
index c371f8d..b028529 100644
--- a/utils/mountd/rmtab.c
+++ b/utils/mountd/rmtab.c
@@ -143,23 +143,16 @@ mountlist_del_all(struct sockaddr_in *sin)
 		return;
 	if (!(hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET))) {
 		xlog(L_ERROR, "can't get hostname of %s", inet_ntoa(addr));
-		xfunlock(lockid);
-		return;
+		goto out_unlock;
 	}
-	else
-		hp = hostent_dup (hp);
+	hp = hostent_dup (hp);
+
+	if (!setrmtabent("r"))
+		goto out_free;
+
+	if (!(fp = fsetrmtabent(_PATH_RMTABTMP, "w")))
+		goto out_close;
 
-	if (!setrmtabent("r")) {
-		xfunlock(lockid);
-		free (hp);
-		return;
-	}
-	if (!(fp = fsetrmtabent(_PATH_RMTABTMP, "w"))) {
-		endrmtabent();
-		xfunlock(lockid);
-		free (hp);
-		return;
-	}
 	while ((rep = getrmtabent(1, NULL)) != NULL) {
 		if (strcmp(rep->r_client, hp->h_name) == 0 &&
 		    (exp = auth_authenticate("umountall", sin, rep->r_path)))
@@ -170,10 +163,13 @@ mountlist_del_all(struct sockaddr_in *sin)
 		xlog(L_ERROR, "couldn't rename %s to %s",
 				_PATH_RMTABTMP, _PATH_RMTAB);
 	}
-	endrmtabent();	/* close & unlink */
 	fendrmtabent(fp);
-	xfunlock(lockid);
+out_close:
+	endrmtabent();	/* close & unlink */
+out_free:
 	free (hp);
+out_unlock:
+	xfunlock(lockid);
 }
 
 mountlist
diff --git a/utils/mountd/v4root.c b/utils/mountd/v4root.c
new file mode 100644
index 0000000..7fd6af3
--- /dev/null
+++ b/utils/mountd/v4root.c
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2009 Red Hat <nfs@redhat.com>
+ *
+ * support/export/v4root.c
+ *
+ * Routines used to support NFSv4 pseudo roots
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <unistd.h>
+#include <errno.h>
+
+#include "xlog.h"
+#include "exportfs.h"
+#include "nfslib.h"
+#include "misc.h"
+#include "v4root.h"
+
+int v4root_needed;
+
+static nfs_export pseudo_root = {
+	.m_next = NULL,
+	.m_client = NULL,
+	.m_export = {
+		.e_hostname = "*",
+		.e_path = "/",
+		.e_flags = NFSEXP_READONLY | NFSEXP_ROOTSQUASH
+				| NFSEXP_NOSUBTREECHECK | NFSEXP_FSID
+				| NFSEXP_V4ROOT,
+		.e_anonuid = 65534,
+		.e_anongid = 65534,
+		.e_squids = NULL,
+		.e_nsquids = 0,
+		.e_sqgids = NULL,
+		.e_nsqgids = 0,
+		.e_fsid = 0,
+		.e_mountpoint = NULL,
+	},
+	.m_exported = 0,
+	.m_xtabent = 1,
+	.m_mayexport = 1,
+	.m_changed = 0,
+	.m_warned = 0,
+};
+
+void set_pseudofs_security(struct exportent *pseudo, struct exportent *source)
+{
+	struct sec_entry *se;
+	int i;
+
+	if (source->e_flags & NFSEXP_INSECURE_PORT)
+		pseudo->e_flags |= NFSEXP_INSECURE_PORT;
+	for (se = source->e_secinfo; se->flav; se++) {
+		struct sec_entry *new;
+
+		i = secinfo_addflavor(se->flav, pseudo);
+		new = &pseudo->e_secinfo[i];
+
+		if (se->flags & NFSEXP_INSECURE_PORT)
+			new->flags |= NFSEXP_INSECURE_PORT;
+	}
+}
+
+/*
+ * Create a pseudo export
+ */
+static struct exportent *
+v4root_create(char *path, nfs_export *export)
+{
+	nfs_export *exp;
+	struct exportent eep;
+	struct exportent *curexp = &export->m_export;
+
+	dupexportent(&eep, &pseudo_root.m_export);
+	eep.e_hostname = strdup(curexp->e_hostname);
+	strncpy(eep.e_path, path, sizeof(eep.e_path));
+	if (strcmp(path, "/") != 0)
+		eep.e_flags &= ~NFSEXP_FSID;
+	set_pseudofs_security(&eep, curexp);
+	exp = export_create(&eep, 0);
+	if (exp == NULL)
+		return NULL;
+	xlog(D_CALL, "v4root_create: path '%s'", exp->m_export.e_path);
+	return &exp->m_export;
+}
+
+/*
+ * Make sure the kernel has pseudo root support.
+ */
+static int
+v4root_support(void)
+{
+	struct export_features *ef;
+	static int warned = 0;
+
+	ef = get_export_features();
+
+	if (ef->flags & NFSEXP_V4ROOT)
+		return 1;
+	if (!warned) {
+		xlog(L_WARNING, "Kernel does not have pseudo root support.");
+		xlog(L_WARNING, "NFS v4 mounts will be disabled unless fsid=0");
+		xlog(L_WARNING, "is specfied in /etc/exports file.");
+		warned++;
+	}
+	return 0;
+}
+
+int pseudofs_update(char *hostname, char *path, nfs_export *source)
+{
+	nfs_export *exp;
+
+	exp = export_lookup(hostname, path, 0);
+	if (exp && !(exp->m_export.e_flags & NFSEXP_V4ROOT))
+		return 0;
+	if (!exp) {
+		if (v4root_create(path, source) == NULL) {
+			xlog(L_WARNING, "v4root_set: Unable to create "
+					"pseudo export for '%s'", path);
+			return -ENOMEM;
+		}
+		return 0;
+	}
+	/* Update an existing V4ROOT export: */
+	set_pseudofs_security(&exp->m_export, &source->m_export);
+	return 0;
+}
+
+static int v4root_add_parents(nfs_export *exp)
+{
+	char *hostname = exp->m_export.e_hostname;
+	char *path;
+	char *ptr;
+
+	path = strdup(exp->m_export.e_path);
+	if (!path)
+		return -ENOMEM;
+	for (ptr = path + 1; ptr; ptr = strchr(ptr, '/')) {
+		int ret;
+		char saved;
+
+		saved = *ptr;
+		*ptr = '\0';
+		ret = pseudofs_update(hostname, path, exp);
+		if (ret)
+			return ret;
+		*ptr = saved;
+		ptr++;
+	}
+	free(path);
+	return 0;
+}
+
+/*
+ * Create pseudo exports by running through the real export
+ * looking at the components of the path that make up the export.
+ * Those path components, if not exported, will become pseudo
+ * exports allowing them to be found when the kernel does an upcall
+ * looking for components of the v4 mount.
+ */
+void
+v4root_set()
+{
+	nfs_export	*exp;
+	int	i, ret;
+
+	if (!v4root_needed)
+		return;
+	if (!v4root_support())
+		return;
+
+	for (i = 0; i < MCL_MAXTYPES; i++) {
+		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
+			if (exp->m_export.e_flags & NFSEXP_V4ROOT)
+				/*
+				 * We just added this one, so its
+				 * parents are already dealt with!
+				 */
+				continue;
+
+			ret = v4root_add_parents(exp);
+			/* XXX: error handling! */
+		}
+	}
+}
diff --git a/utils/nfsd/nfssvc.c b/utils/nfsd/nfssvc.c
index 12d3253..b8028bb 100644
--- a/utils/nfsd/nfssvc.c
+++ b/utils/nfsd/nfssvc.c
@@ -212,7 +212,7 @@ int
 nfssvc_set_sockets(const int family, const unsigned int protobits,
 		   const char *host, const char *port)
 {
-	struct addrinfo hints = { .ai_flags = AI_PASSIVE | AI_ADDRCONFIG };
+	struct addrinfo hints = { .ai_flags = AI_PASSIVE };
 
 	hints.ai_family = family;
 
diff --git a/utils/showmount/showmount.c b/utils/showmount/showmount.c
index 418e8b9..f567093 100644
--- a/utils/showmount/showmount.c
+++ b/utils/showmount/showmount.c
@@ -78,29 +78,36 @@ static void usage(FILE *fp, int n)
 	exit(n);
 }
 
-static const char *nfs_sm_pgmtbl[] = {
+static const char *mount_pgm_tbl[] = {
 	"showmount",
 	"mount",
 	"mountd",
 	NULL,
 };
 
+static const rpcvers_t mount_vers_tbl[] = {
+	MOUNTVERS_NFSV3,
+	MOUNTVERS_POSIX,
+	MOUNTVERS,
+};
+static const unsigned int max_vers_tblsz = 
+	(sizeof(mount_vers_tbl)/sizeof(mount_vers_tbl[0]));
+
 /*
  * Generate an RPC client handle connected to the mountd service
  * at @hostname, or die trying.
  *
  * Supports both AF_INET and AF_INET6 server addresses.
  */
-static CLIENT *nfs_get_mount_client(const char *hostname)
+static CLIENT *nfs_get_mount_client(const char *hostname, rpcvers_t vers)
 {
-	rpcprog_t program = nfs_getrpcbyname(MOUNTPROG, nfs_sm_pgmtbl);
+	rpcprog_t program = nfs_getrpcbyname(MOUNTPROG, mount_pgm_tbl);
 	CLIENT *client;
 
-	client = clnt_create(hostname, program, MOUNTVERS, "tcp");
+	client = clnt_create(hostname, program, vers, "tcp");
 	if (client)
 		return client;
-
-	client = clnt_create(hostname, program, MOUNTVERS, "udp");
+	client = clnt_create(hostname, program, vers, "udp");
 	if (client)
 		return client;
 
@@ -123,6 +130,7 @@ int main(int argc, char **argv)
 	int i;
 	int n;
 	int maxlen;
+	int unsigned vers=0;
 	char **dumpv;
 
 	program_name = argv[0];
@@ -185,11 +193,12 @@ int main(int argc, char **argv)
 		break;
 	}
 
-	mclient = nfs_get_mount_client(hostname);
+	mclient = nfs_get_mount_client(hostname, mount_vers_tbl[vers]);
 	mclient->cl_auth = authunix_create_default();
 	total_timeout.tv_sec = TOTAL_TIMEOUT;
 	total_timeout.tv_usec = 0;
 
+again:
 	if (eflag) {
 		memset(&exportlist, '\0', sizeof(exportlist));
 
@@ -197,6 +206,13 @@ int main(int argc, char **argv)
 			(xdrproc_t) xdr_void, NULL,
 			(xdrproc_t) xdr_exports, (caddr_t) &exportlist,
 			total_timeout);
+		if (clnt_stat == RPC_PROGVERSMISMATCH) {
+			if (++vers <  max_vers_tblsz) {
+				(void)CLNT_CONTROL(mclient, CLSET_VERS, 
+					(void *)&mount_vers_tbl[vers]);
+				goto again;
+				}
+		}
 		if (clnt_stat != RPC_SUCCESS) {
 			clnt_perror(mclient, "rpc mount export");
 			clnt_destroy(mclient);
@@ -232,6 +248,13 @@ int main(int argc, char **argv)
 		(xdrproc_t) xdr_void, NULL,
 		(xdrproc_t) xdr_mountlist, (caddr_t) &dumplist,
 		total_timeout);
+	if (clnt_stat == RPC_PROGVERSMISMATCH) {
+		if (++vers <  max_vers_tblsz) {
+			(void)CLNT_CONTROL(mclient, CLSET_VERS, 
+				(void *)&mount_vers_tbl[vers]);
+			goto again;
+		}
+	}
 	if (clnt_stat != RPC_SUCCESS) {
 		clnt_perror(mclient, "rpc mount dump");
 		clnt_destroy(mclient);
diff --git a/utils/statd/Makefile.am b/utils/statd/Makefile.am
index 8a3ba4e..1744791 100644
--- a/utils/statd/Makefile.am
+++ b/utils/statd/Makefile.am
@@ -2,31 +2,25 @@
 
 man8_MANS = statd.man sm-notify.man
 
-GENFILES_CLNT	= sm_inter_clnt.c
-GENFILES_SVC	= sm_inter_svc.c
-GENFILES_XDR	= sm_inter_xdr.c
-GENFILES_H	= sm_inter.h
-
-GENFILES	= $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H)
-
 RPCPREFIX	= rpc.
 KPREFIX		= @kprefix@
 sbin_PROGRAMS	= statd sm-notify
 dist_sbin_SCRIPTS	= start-statd
-statd_SOURCES = callback.c notlist.c log.c misc.c monitor.c \
+statd_SOURCES = callback.c notlist.c misc.c monitor.c hostname.c \
 	        simu.c stat.c statd.c svc_run.c rmtcall.c \
-	        sm_inter_clnt.c sm_inter_svc.c sm_inter_xdr.c log.h \
-	        notlist.h statd.h system.h version.h sm_inter.h
+	        notlist.h statd.h system.h version.h
 sm_notify_SOURCES = sm-notify.c
 
 BUILT_SOURCES = $(GENFILES)
-statd_LDADD = ../../support/export/libexport.a \
+statd_LDADD = ../../support/nsm/libnsm.a \
 	      ../../support/nfs/libnfs.a \
 	      ../../support/misc/libmisc.a \
-	      $(LIBWRAP) $(LIBNSL)
-sm_notify_LDADD = $(LIBNSL)
+	      $(LIBWRAP) $(LIBNSL) $(LIBCAP)
+sm_notify_LDADD = ../../support/nsm/libnsm.a \
+		  ../../support/nfs/libnfs.a \
+		  $(LIBNSL) $(LIBCAP)
 
-EXTRA_DIST = sim_sm_inter.x sm_inter.x $(man8_MANS) COPYRIGHT simulate.c
+EXTRA_DIST = sim_sm_inter.x $(man8_MANS) COPYRIGHT simulate.c
 
 if CONFIG_RPCGEN
 RPCGEN	= $(top_builddir)/tools/rpcgen/rpcgen
diff --git a/utils/statd/callback.c b/utils/statd/callback.c
index 8885238..d1cc139 100644
--- a/utils/statd/callback.c
+++ b/utils/statd/callback.c
@@ -10,10 +10,9 @@
 #include <config.h>
 #endif
 
-#include <arpa/inet.h>
+#include <netdb.h>
 
 #include "rpcmisc.h"
-#include "misc.h"
 #include "statd.h"
 #include "notlist.h"
 
@@ -21,30 +20,85 @@
 /* notify_list *cbnl = NULL; ... never used */
 
 
-/* 
+/*
  * Services SM_NOTIFY requests.
- * Any clients that have asked us to monitor that host are put on
- * the global callback list, which is processed as soon as statd
- * returns to svc_run.
+ *
+ * When NLM uses an SM_MON request to tell statd to monitor a remote,
+ * the request contains a "mon_name" argument.  This is usually the
+ * "caller_name" argument of an NLMPROC_LOCK request.  On Linux, the
+ * NLM can send statd the remote's IP address instead of its
+ * caller_name.  The NSM protocol does not allow both the remote's
+ * caller_name and it's IP address to be sent in the same SM_MON
+ * request.
+ *
+ * The remote's caller_name is useful because it makes it simple
+ * to identify rebooting remotes by matching the "mon_name" argument
+ * they sent via an SM_NOTIFY request.
+ *
+ * The caller_name string may not be a fully qualified domain name,
+ * or even registered in the DNS database, however.  Having the
+ * remote's IP address is useful because then there is no ambiguity
+ * about where to send an SM_NOTIFY after the local system reboots.
+ *
+ * Without the actual caller_name, however, statd must use an
+ * heuristic to match an incoming SM_NOTIFY request to one of the
+ * hosts it is currently monitoring.  The incoming mon_name in an
+ * SM_NOTIFY address is converted to a list of IP addresses using
+ * DNS.  Each mon_name on statd's monitor list is also converted to
+ * an address list, and the two lists are checked to see if there is
+ * a matching address.
+ *
+ * There are some risks to this strategy:
+ *
+ *   1.  The external DNS database is not reliable.  It can change
+ *       over time, or the forward and reverse mappings could be
+ *       inconsistent.
+ *
+ *   2.  If statd's monitor list becomes substantial, finding a match
+ *       can generate a not inconsequential amount of DNS traffic.
+ *
+ *   3.  statd is a single-threaded service.  When DNS becomes slow or
+ *       unresponsive, statd also becomes slow or unresponsive.
+ *
+ *   4.  If the remote does not have a DNS entry at all (or if the
+ *       remote can resolve itself, but the local host can't resolve
+ *       the remote's hostname), the remote cannot be monitored, and
+ *       therefore NLM locking cannot be provided for that host.
+ *
+ *   5.  Local DNS resolution can produce different results for the
+ *       mon_name than the results the remote might see for the same
+ *       query, especially if the remote did not send a caller_name
+ *       or mon_name that is a fully qualified domain name.
+ *
+ *       Note that a caller_name is passed from NFS client to server,
+ *       but the client never knows what mon_name the server might use
+ *       to notify it of a reboot.  On Linux, the client extracts the
+ *       server's name from the devname it was passed by the mount
+ *       command.  This is often not a fully-qualified domain name.
  */
 void *
 sm_notify_1_svc(struct stat_chge *argp, struct svc_req *rqstp)
 {
 	notify_list    *lp, *call;
 	static char    *result = NULL;
-	struct sockaddr_in *sin = nfs_getrpccaller_in(rqstp->rq_xprt);
-	char *ip_addr = xstrdup(inet_ntoa(sin->sin_addr));
+	struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt);
+	char		ip_addr[INET6_ADDRSTRLEN];
 
-	dprintf(N_DEBUG, "Received SM_NOTIFY from %s, state: %d",
+	xlog(D_CALL, "Received SM_NOTIFY from %s, state: %d",
 				argp->mon_name, argp->state);
 
 	/* quick check - don't bother if we're not monitoring anyone */
 	if (rtnl == NULL) {
-		note(N_WARNING, "SM_NOTIFY from %s while not monitoring any hosts.",
+		xlog_warn("SM_NOTIFY from %s while not monitoring any hosts",
 				argp->mon_name);
 		return ((void *) &result);
 	}
 
+	if (!statd_present_address(sap, ip_addr, sizeof(ip_addr))) {
+		xlog_warn("Unrecognized sender address");
+		return ((void *) &result);
+	}
+
 	/* okir change: statd doesn't remove the remote host from its
 	 * internal monitor list when receiving an SM_NOTIFY call from
 	 * it. Lockd will want to continue monitoring the remote host
@@ -52,8 +106,8 @@ sm_notify_1_svc(struct stat_chge *argp, struct svc_req *rqstp)
 	 */
 	for (lp = rtnl ; lp ; lp = lp->next)
 		if (NL_STATE(lp) != argp->state &&
-		    (matchhostname(argp->mon_name, lp->dns_name) ||
-		     matchhostname(ip_addr, lp->dns_name))) {
+		    (statd_matchhostname(argp->mon_name, lp->dns_name) ||
+		     statd_matchhostname(ip_addr, lp->dns_name))) {
 			NL_STATE(lp) = argp->state;
 			call = nlist_clone(lp);
 			nlist_insert(&notify, call);
diff --git a/utils/statd/hostname.c b/utils/statd/hostname.c
new file mode 100644
index 0000000..7d704cc
--- /dev/null
+++ b/utils/statd/hostname.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2009 Oracle.  All rights reserved.
+ *
+ * This file is part of nfs-utils.
+ *
+ * nfs-utils is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * nfs-utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * NSM for Linux.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <strings.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include "sockaddr.h"
+#include "statd.h"
+#include "xlog.h"
+
+#ifndef HAVE_DECL_AI_ADDRCONFIG
+#define AI_ADDRCONFIG	0
+#endif
+
+/**
+ * statd_present_address - convert sockaddr to presentation address
+ * @sap: pointer to socket address to convert
+ * @buf: pointer to buffer to fill in
+ * @buflen: length of buffer
+ *
+ * Convert the passed-in sockaddr-style address to presentation format.
+ * The presentation format address is placed in @buf and is
+ * '\0'-terminated.
+ *
+ * Returns true if successful; otherwise false.
+ *
+ * getnameinfo(3) is preferred, since it can parse IPv6 scope IDs.
+ * An alternate version of statd_present_address() is available to
+ * handle older glibcs that do not have getnameinfo(3).
+ */
+#ifdef HAVE_GETNAMEINFO
+_Bool
+statd_present_address(const struct sockaddr *sap, char *buf, const size_t buflen)
+{
+	socklen_t salen;
+	int error;
+
+	salen = nfs_sockaddr_length(sap);
+	if (salen == 0) {
+		xlog(D_GENERAL, "%s: unsupported address family",
+				__func__);
+		return false;
+	}
+
+	error = getnameinfo(sap, salen, buf, (socklen_t)buflen,
+						NULL, 0, NI_NUMERICHOST);
+	if (error != 0) {
+		xlog(D_GENERAL, "%s: getnameinfo(3): %s",
+				__func__, gai_strerror(error));
+		return false;
+	}
+	return true;
+}
+#else	/* !HAVE_GETNAMEINFO */
+_Bool
+statd_present_address(const struct sockaddr *sap, char *buf, const size_t buflen)
+{
+	const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
+
+	if (sin->sin_family != AF_INET) {
+		xlog(D_GENERAL, "%s: unsupported address family", __func__);
+		return false;
+	}
+
+	/* ensure '\0' termination */
+	memset(buf, 0, buflen);
+
+	if (inet_ntop(AF_INET, (char *)&sin->sin_addr,
+					buf, (socklen_t)buflen) == NULL) {
+		xlog(D_GENERAL, "%s: inet_ntop(3): %m", __func__);
+		return false;
+	}
+	return true;
+}
+#endif	/* !HAVE_GETNAMEINFO */
+
+/*
+ * Look up the hostname; report exceptional errors.  Caller must
+ * call freeaddrinfo(3) if a valid addrinfo is returned.
+ */
+__attribute_malloc__
+static struct addrinfo *
+get_addrinfo(const char *hostname, const struct addrinfo *hint)
+{
+	struct addrinfo *ai = NULL;
+	int error;
+
+	error = getaddrinfo(hostname, NULL, hint, &ai);
+	switch (error) {
+	case 0:
+		return ai;
+	case EAI_NONAME:
+		break;
+	default:
+		xlog(D_GENERAL, "%s: failed to resolve host %s: %s",
+				__func__, hostname, gai_strerror(error));
+	}
+
+	return NULL;
+}
+
+#ifdef HAVE_GETNAMEINFO
+static _Bool
+get_nameinfo(const struct sockaddr *sap, const socklen_t salen,
+		/*@out@*/ char *buf, const socklen_t buflen)
+{
+	int error;
+
+	error = getnameinfo(sap, salen, buf, buflen, NULL, 0, NI_NAMEREQD);
+	if (error != 0) {
+		xlog(D_GENERAL, "%s: failed to resolve address: %s",
+				__func__, gai_strerror(error));
+		return false;
+	}
+
+	return true;
+}
+#else	/* !HAVE_GETNAMEINFO */
+static _Bool
+get_nameinfo(const struct sockaddr *sap,
+		__attribute__ ((unused)) const socklen_t salen,
+		/*@out@*/ char *buf, socklen_t buflen)
+{
+	struct sockaddr_in *sin = (struct sockaddr_in *)(char *)sap;
+	struct hostent *hp;
+
+	if (sin->sin_family != AF_INET) {
+		xlog(D_GENERAL, "%s: unknown address family: %d",
+				sin->sin_family);
+		return false;
+	}
+
+	hp = gethostbyaddr((const char *)&(sin->sin_addr.s_addr),
+				sizeof(struct in_addr), AF_INET);
+	if (hp == NULL) {
+		xlog(D_GENERAL, "%s: failed to resolve address: %m", __func__);
+		return false;
+	}
+
+	strncpy(buf, hp->h_name, (size_t)buflen);
+	return true;
+}
+#endif	/* !HAVE_GETNAMEINFO */
+
+/**
+ * statd_canonical_name - choose file name for monitor record files
+ * @hostname: C string containing hostname or presentation address
+ *
+ * Returns a '\0'-terminated ASCII string containing a fully qualified
+ * canonical hostname, or NULL if @hostname does not have a reverse
+ * mapping.  Caller must free the result with free(3).
+ *
+ * Incoming hostnames are looked up to determine the canonical hostname,
+ * and incoming presentation addresses are converted to canonical
+ * hostnames.
+ *
+ * We won't monitor peers that don't have a reverse map.  The canonical
+ * name gives us a key for our monitor list.
+ */
+__attribute_malloc__
+char *
+statd_canonical_name(const char *hostname)
+{
+	struct addrinfo hint = {
+#ifdef IPV6_SUPPORTED
+		.ai_family	= AF_UNSPEC,
+#else	/* !IPV6_SUPPORTED */
+		.ai_family	= AF_INET,
+#endif	/* !IPV6_SUPPORTED */
+		.ai_flags	= AI_NUMERICHOST,
+		.ai_protocol	= (int)IPPROTO_UDP,
+	};
+	char buf[NI_MAXHOST];
+	struct addrinfo *ai;
+
+	ai = get_addrinfo(hostname, &hint);
+	if (ai != NULL) {
+		/* @hostname was a presentation address */
+		_Bool result;
+		result = get_nameinfo(ai->ai_addr, ai->ai_addrlen,
+					buf, (socklen_t)sizeof(buf));
+		freeaddrinfo(ai);
+		if (!result)
+			return NULL;
+		return strdup(buf);
+	}
+
+	/* @hostname was a hostname */
+	hint.ai_flags = AI_CANONNAME;
+	ai = get_addrinfo(hostname, &hint);
+	if (ai == NULL)
+		return NULL;
+	strcpy(buf, ai->ai_canonname);
+	freeaddrinfo(ai);
+
+	return strdup(buf);
+}
+
+/**
+ * statd_matchhostname - check if two hostnames are equivalent
+ * @hostname1: C string containing hostname
+ * @hostname2: C string containing hostname
+ *
+ * Returns true if the hostnames are the same, the hostnames resolve
+ * to the same canonical name, or the hostnames resolve to at least
+ * one address that is the same.  False is returned if the hostnames
+ * do not match in any of these ways, if either hostname contains
+ * wildcard characters, if either hostname is a netgroup name, or
+ * if an error occurs.
+ */
+_Bool
+statd_matchhostname(const char *hostname1, const char *hostname2)
+{
+	struct addrinfo *ai1, *ai2, *results1 = NULL, *results2 = NULL;
+	struct addrinfo hint = {
+		.ai_family	= AF_UNSPEC,
+		.ai_flags	= AI_CANONNAME,
+		.ai_protocol	= (int)IPPROTO_UDP,
+	};
+	_Bool result = false;
+
+	if (strcasecmp(hostname1, hostname2) == 0) {
+		result = true;
+		goto out;
+	}
+
+	results1 = get_addrinfo(hostname1, &hint);
+	if (results1 == NULL)
+		goto out;
+	results2 = get_addrinfo(hostname2, &hint);
+	if (results2 == NULL)
+		goto out;
+
+	if (strcasecmp(results1->ai_canonname, results2->ai_canonname) == 0) {
+		result = true;
+		goto out;
+	}
+
+	for (ai1 = results1; ai1 != NULL; ai1 = ai1->ai_next)
+		for (ai2 = results2; ai2 != NULL; ai2 = ai2->ai_next)
+			if (nfs_compare_sockaddr(ai1->ai_addr, ai2->ai_addr)) {
+				result = true;
+				break;
+			}
+
+out:
+	freeaddrinfo(results2);
+	freeaddrinfo(results1);
+
+	xlog(D_CALL, "%s: hostnames %s", __func__,
+			(result ? "matched" : "did not match"));
+	return result;
+}
diff --git a/utils/statd/log.c b/utils/statd/log.c
deleted file mode 100644
index a6ca996..0000000
--- a/utils/statd/log.c
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 1995 Olaf Kirch
- * Modified by Jeffrey A. Uphoff, 1995, 1997, 1999.
- * Modified by H.J. Lu, 1998.
- * Modified by Lon Hohberger, Oct. 2000
- *
- * NSM for Linux.
- */
-
-/* 
- * 	log.c - logging functions for lockd/statd
- *	260295	 okir	started with simply syslog logging.
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <syslog.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <time.h>
-#include <sys/types.h>
-#include "log.h"
-#include "statd.h"
-
-static pid_t	mypid;
-								/* Turns on logging to console/stderr. */
-#if 0
-static int	opt_debug = 0;	/* Will be command-line option, eventually */
-#endif
-
-void log_init(void)
-{
-	if (!(run_mode & MODE_LOG_STDERR)) 
-		openlog(name_p, LOG_PID | LOG_NDELAY, LOG_DAEMON);
-
-	mypid = getpid();
-
-	note(N_WARNING,"Version %s Starting",version_p);
-}
-
-void log_background(void)
-{
-	/* NOP */
-}
-
-void die(char *fmt, ...)
-{
-	char	buffer[1024];
-	va_list	ap;
-
-	va_start(ap, fmt);
-	vsnprintf (buffer, 1024, fmt, ap);
-	va_end(ap);
-	buffer[1023]=0;
-
-	note(N_FATAL, "%s", buffer);
-
-#ifndef DEBUG
-	exit (2);
-#else
-	abort();	/* make a core */
-#endif
-}
-
-void note(int level, char *fmt, ...)
-{
-	char	buffer[1024];
-	va_list	ap;
-
-	va_start(ap, fmt);
-	vsnprintf (buffer, 1024, fmt, ap);
-	va_end(ap);
-	buffer[1023]=0;
-
-	if ((!(run_mode & MODE_LOG_STDERR)) && (level < N_DEBUG)) {
-		syslog(level, "%s", buffer);
-	} else if (run_mode & MODE_LOG_STDERR) {
-		/* Log everything, including dprintf() stuff to stderr */
-		time_t		now;
-		struct tm *	tm;
-
-		time(&now);
-		tm = localtime(&now);
-		fprintf (stderr, "%02d/%02d/%04d %02d:%02d:%02d %s[%d]: %s\n",
-			tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900,
-			tm->tm_hour, tm->tm_min, tm->tm_sec,
-			name_p, mypid,
-			buffer);
-	}
-}
diff --git a/utils/statd/log.h b/utils/statd/log.h
deleted file mode 100644
index fc55d3c..0000000
--- a/utils/statd/log.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 1995 Olaf Kirch
- * Modified by Jeffrey A. Uphoff, 1996, 1997, 1999.
- * Modified by Lon Hohberger, Oct. 2000
- *
- * NSM for Linux.
- */
-
-/*
- * 	logging functionality
- *	260295	okir
- */
-
-#ifndef _LOCKD_LOG_H_
-#define _LOCKD_LOG_H_
-
-#include <syslog.h>
-
-void	log_init(void);
-void	log_background(void);
-void	log_enable(int facility);
-int	log_enabled(int facility);
-void	note(int level, char *fmt, ...);
-void	die(char *fmt, ...);
-
-/*
- * Map per-application severity to system severity. What's fatal for
- * lockd is merely an itching spot from the universe's point of view.
- */
-#define N_CRIT		LOG_CRIT
-#define N_FATAL		LOG_ERR
-#define N_ERROR		LOG_WARNING
-#define N_WARNING	LOG_NOTICE
-#define N_DEBUG		LOG_DEBUG
-
-#ifdef DEBUG
-#define dprintf		note
-#else
-#define dprintf		if (run_mode & MODE_LOG_STDERR) note
-#endif
-
-#endif /* _LOCKD_LOG_H_ */
diff --git a/utils/statd/misc.c b/utils/statd/misc.c
index 7256291..f2a086f 100644
--- a/utils/statd/misc.c
+++ b/utils/statd/misc.c
@@ -29,8 +29,7 @@ xmalloc (size_t size)
     return ((void *)NULL);
 
   if (!(ptr = malloc (size)))
-    /* SHIT!  SHIT!  SHIT! */
-    die ("malloc failed");
+    xlog_err ("malloc failed");
 
   return (ptr);
 }
@@ -46,32 +45,7 @@ xstrdup (const char *string)
 
   /* Will only fail if underlying malloc() fails (ENOMEM). */
   if (!(result = strdup (string)))
-    die ("strdup failed");
+    xlog_err ("strdup failed");
 
   return (result);
 }
-
-
-/*
- * Unlinking a file.
- */
-void
-xunlink (char *path, char *host)
-{
-	char *tozap;
-
-	tozap = malloc(strlen(path)+strlen(host)+2);
-	if (tozap == NULL) {
-		note(N_ERROR, "xunlink: malloc failed: errno %d (%s)", 
-			errno, strerror(errno));
-		return;
-	}
-	sprintf (tozap, "%s/%s", path, host);
-
-	if (unlink (tozap) == -1)
-		note(N_ERROR, "unlink (%s): %s", tozap, strerror (errno));
-	else
-		dprintf (N_DEBUG, "Unlinked %s", tozap);
-
-	free(tozap);
-}
diff --git a/utils/statd/monitor.c b/utils/statd/monitor.c
index a2c9e2b..325dfd3 100644
--- a/utils/statd/monitor.c
+++ b/utils/statd/monitor.c
@@ -21,34 +21,38 @@
 #include <arpa/inet.h>
 #include <dirent.h>
 
+#include "sockaddr.h"
 #include "rpcmisc.h"
-#include "misc.h"
+#include "nsm.h"
 #include "statd.h"
 #include "notlist.h"
 #include "ha-callout.h"
 
 notify_list *		rtnl = NULL;	/* Run-time notify list. */
 
-#define LINELEN (4*(8+1)+SM_PRIV_SIZE*2+1)
-
 /*
  * Reject requests from non-loopback addresses in order
  * to prevent attack described in CERT CA-99.05.
+ *
+ * Although the kernel contacts the statd service via only IPv4
+ * transports, the statd service can receive other requests, such
+ * as SM_NOTIFY, from remote peers via IPv6.
  */
-static int
+static _Bool
 caller_is_localhost(struct svc_req *rqstp)
 {
-	struct sockaddr_in *sin = nfs_getrpccaller_in(rqstp->rq_xprt);
-	struct in_addr	caller;
-
-	caller = sin->sin_addr;
-	if (caller.s_addr != htonl(INADDR_LOOPBACK)) {
-		note(N_WARNING,
-			"Call to statd from non-local host %s",
-			inet_ntoa(caller));
-		return 0;
-	}
-	return 1;
+	struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt);
+	char buf[INET6_ADDRSTRLEN];
+
+	if (!nfs_is_v4_loopback(sap))
+		goto out_nonlocal;
+	return true;
+
+out_nonlocal:
+	if (!statd_present_address(sap, buf, sizeof(buf)))
+		buf[0] = '\0';
+	xlog_warn("SM_MON/SM_UNMON call from non-local host %s", buf);
+	return false;
 }
 
 /*
@@ -61,13 +65,15 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
 	char		*mon_name = argp->mon_id.mon_name,
 			*my_name  = argp->mon_id.my_id.my_name;
 	struct my_id	*id = &argp->mon_id.my_id;
-	char            *path;
 	char		*cp;
-	int             fd;
 	notify_list	*clnt;
-	struct in_addr	my_addr;
-	char		*dnsname;
-	struct hostent	*hostinfo = NULL;
+	struct sockaddr_in my_addr = {
+		.sin_family		= AF_INET,
+		.sin_addr.s_addr	= htonl(INADDR_LOOPBACK),
+	};
+	char *dnsname = NULL;
+
+	xlog(D_CALL, "Received SM_MON for %s from %s", mon_name, my_name);
 
 	/* Assume that we'll fail. */
 	result.res_stat = STAT_FAIL;
@@ -79,7 +85,6 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
 	 */
 	if (!caller_is_localhost(rqstp))
 		goto failure;
-	my_addr.s_addr = htonl(INADDR_LOOPBACK);
 
 	/* 2.	Reject any registrations for non-lockd services.
 	 *
@@ -92,8 +97,7 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
 	if (id->my_prog != 100021 ||
 	    (id->my_proc != 16 && id->my_proc != 24))
 	{
-		note(N_WARNING,
-			"Attempt to register callback to %d/%d",
+		xlog_warn("Attempt to register callback to %d/%d",
 			id->my_prog, id->my_proc);
 		goto failure;
 	}
@@ -105,12 +109,9 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
 
 	/* must check for /'s in hostname!  See CERT's CA-96.09 for details. */
 	if (strchr(mon_name, '/') || mon_name[0] == '.') {
-		note(N_CRIT, "SM_MON request for hostname containing '/' "
+		xlog(L_ERROR, "SM_MON request for hostname containing '/' "
 		     "or starting '.': %s", mon_name);
-		note(N_CRIT, "POSSIBLE SPOOF/ATTACK ATTEMPT!");
-		goto failure;
-	} else if ((hostinfo = gethostbyname(mon_name)) == NULL) {
-		note(N_WARNING, "gethostbyname error for %s", mon_name);
+		xlog(L_ERROR, "POSSIBLE SPOOF/ATTACK ATTEMPT!");
 		goto failure;
 	}
 
@@ -124,15 +125,13 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
 	 * Now choose a hostname to use for matching.  We cannot
 	 * really trust much in the incoming NOTIFY, so to make
 	 * sure that multi-homed hosts work nicely, we get an
-	 * FQDN now, and use that for matching
+	 * FQDN now, and use that for matching.
 	 */
-	hostinfo = gethostbyaddr(hostinfo->h_addr,
-				 hostinfo->h_length,
-				 hostinfo->h_addrtype);
-	if (hostinfo)
-		dnsname = xstrdup(hostinfo->h_name);
-	else
-		dnsname = xstrdup(my_name);
+	dnsname = statd_canonical_name(mon_name);
+	if (dnsname == NULL) {
+		xlog(L_WARNING, "No canonical hostname found for %s", mon_name);
+		goto failure;
+	}
 
 	/* Now check to see if this is a duplicate, and warn if so.
 	 * I will also return STAT_FAIL. (I *think* this is how I should
@@ -146,18 +145,19 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
 	clnt = rtnl;
 
 	while ((clnt = nlist_gethost(clnt, mon_name, 0))) {
-		if (matchhostname(NL_MY_NAME(clnt), my_name) &&
+		if (statd_matchhostname(NL_MY_NAME(clnt), my_name) &&
 		    NL_MY_PROC(clnt) == id->my_proc &&
 		    NL_MY_PROG(clnt) == id->my_prog &&
 		    NL_MY_VERS(clnt) == id->my_vers &&
 		    memcmp(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE) == 0) {
 			/* Hey!  We already know you guys! */
-			dprintf(N_DEBUG,
+			xlog(D_GENERAL,
 				"Duplicate SM_MON request for %s "
 				"from procedure on %s",
 				mon_name, my_name);
 
 			/* But we'll let you pass anyway. */
+			free(dnsname);
 			goto success;
 		}
 		clnt = NL_NEXT(clnt);
@@ -168,11 +168,11 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
 	 * doesn't fail.  (I should probably fix this assumption.)
 	 */
 	if (!(clnt = nlist_new(my_name, mon_name, 0))) {
-		note(N_WARNING, "out of memory");
+		free(dnsname);
+		xlog_warn("out of memory");
 		goto failure;
 	}
 
-	NL_ADDR(clnt) = my_addr;
 	NL_MY_PROG(clnt) = id->my_prog;
 	NL_MY_VERS(clnt) = id->my_vers;
 	NL_MY_PROC(clnt) = id->my_proc;
@@ -182,40 +182,16 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
 	/*
 	 * Now, Create file on stable storage for host.
 	 */
-
-	path=xmalloc(strlen(SM_DIR)+strlen(dnsname)+2);
-	sprintf(path, "%s/%s", SM_DIR, dnsname);
-	if ((fd = open(path, O_WRONLY|O_SYNC|O_CREAT|O_APPEND,
-		       S_IRUSR|S_IWUSR)) < 0) {
-		/* Didn't fly.  We won't monitor. */
-		note(N_ERROR, "creat(%s) failed: %s", path, strerror (errno));
+	if (!nsm_insert_monitored_host(dnsname,
+				(struct sockaddr *)(char *)&my_addr, argp)) {
 		nlist_free(NULL, clnt);
-		free(path);
 		goto failure;
 	}
-	{
-		char buf[LINELEN + 1 + SM_MAXSTRLEN*2 + 4];
-		char *e;
-		int i;
-		e = buf + sprintf(buf, "%08x %08x %08x %08x ",
-				  my_addr.s_addr, id->my_prog,
-				  id->my_vers, id->my_proc);
-		for (i=0; i<SM_PRIV_SIZE; i++)
-			e += sprintf(e, "%02x", 0xff & (argp->priv[i]));
-		if (e+1-buf != LINELEN) abort();
-		e += sprintf(e, " %s %s\n", mon_name, my_name);
-		if (write(fd, buf, e-buf) != (e-buf)) {
-			note(N_WARNING, "writing to %s failed: errno %d (%s)",
-				path, errno, strerror(errno));
-		}
-	}
 
-	free(path);
 	/* PRC: do the HA callout: */
 	ha_callout("add-client", mon_name, my_name, -1);
 	nlist_insert(&rtnl, clnt);
-	close(fd);
-	dprintf(N_DEBUG, "MONITORING %s for %s", mon_name, my_name);
+	xlog(D_GENERAL, "MONITORING %s for %s", mon_name, my_name);
  success:
 	result.res_stat = STAT_SUCC;
 	/* SUN's sm_inter.x says this should be "state number of local site".
@@ -232,75 +208,49 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
 	return (&result);
 
 failure:
-	note(N_WARNING, "STAT_FAIL to %s for SM_MON of %s", my_name, mon_name);
+	xlog_warn("STAT_FAIL to %s for SM_MON of %s", my_name, mon_name);
 	return (&result);
 }
 
-void load_state(void)
+static unsigned int
+load_one_host(const char *hostname,
+		__attribute__ ((unused)) const struct sockaddr *sap,
+		const struct mon *m,
+		__attribute__ ((unused)) const time_t timestamp)
 {
-	DIR *d;
-	struct dirent *de;
-	char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
-
-	d = opendir(SM_DIR);
-	if (!d)
-		return;
-	while ((de = readdir(d))) {
-		char *path;
-		FILE *f;
-		int p;
-
-		if (de->d_name[0] == '.')
-			continue;
-		path = xmalloc(strlen(SM_DIR)+strlen(de->d_name)+2);
-		sprintf(path, "%s/%s", SM_DIR, de->d_name);
-		f = fopen(path, "r");
-		free(path);
-		if (f == NULL)
-			continue;
-		while (fgets(buf, sizeof(buf), f) != NULL) {
-			int addr, proc, prog, vers;
-			char priv[SM_PRIV_SIZE];
-			char *monname, *myname;
-			char *b;
-			int i;
-			notify_list	*clnt;
-
-			buf[sizeof(buf)-1] = 0;
-			b = strchr(buf, '\n');
-			if (b) *b = 0;
-			sscanf(buf, "%x %x %x %x ",
-			       &addr, &prog, &vers, &proc);
-			b = buf+36;
-			for (i=0; i<SM_PRIV_SIZE; i++) {
-				sscanf(b, "%2x", &p);
-				priv[i] = p;
-				b += 2;
-			}
-			b++;
-			monname = b;
-			while (*b && *b != ' ') b++;
-			if (*b) *b++ = '\0';
-			while (*b == ' ') b++;
-			myname = b;
-			clnt = nlist_new(myname, monname, 0);
-			if (!clnt)
-				break;
-			NL_ADDR(clnt).s_addr = addr;
-			NL_MY_PROG(clnt) = prog;
-			NL_MY_VERS(clnt) = vers;
-			NL_MY_PROC(clnt) = proc;
-			clnt->dns_name = xstrdup(de->d_name);
-			memcpy(NL_PRIV(clnt), priv, SM_PRIV_SIZE);
-			nlist_insert(&rtnl, clnt);
-		}
-		fclose(f);
+	notify_list *clnt;
+
+	clnt = nlist_new(m->mon_id.my_id.my_name,
+				m->mon_id.mon_name, 0);
+	if (clnt == NULL)
+		return 0;
+
+	clnt->dns_name = strdup(hostname);
+	if (clnt->dns_name == NULL) {
+		nlist_free(NULL, clnt);
+		return 0;
 	}
-	closedir(d);
-}
 
+	xlog(D_GENERAL, "Adding record for %s to the monitor list...",
+			hostname);
 
+	NL_MY_PROG(clnt) = m->mon_id.my_id.my_prog;
+	NL_MY_VERS(clnt) = m->mon_id.my_id.my_vers;
+	NL_MY_PROC(clnt) = m->mon_id.my_id.my_proc;
+	memcpy(NL_PRIV(clnt), m->priv, SM_PRIV_SIZE);
 
+	nlist_insert(&rtnl, clnt);
+	return 1;
+}
+
+void load_state(void)
+{
+	unsigned int count;
+
+	count = nsm_load_monitor_list(load_one_host);
+	if (count)
+		xlog(D_GENERAL, "Loaded %u previously monitored hosts");
+}
 
 /*
  * Services SM_UNMON requests.
@@ -320,6 +270,8 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp)
 	struct my_id	*id = &argp->my_id;
 	char		*cp;
 
+	xlog(D_CALL, "Received SM_UNMON for %s from %s", mon_name, my_name);
+
 	result.state = MY_STATE;
 
 	if (!caller_is_localhost(rqstp))
@@ -333,9 +285,8 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp)
 
 	/* Check if we're monitoring anyone. */
 	if (rtnl == NULL) {
-		note(N_WARNING,
-			"Received SM_UNMON request from %s for %s while not "
-			"monitoring any hosts.", my_name, argp->mon_name);
+		xlog_warn("Received SM_UNMON request from %s for %s while not "
+			"monitoring any hosts", my_name, argp->mon_name);
 		return (&result);
 	}
 	clnt = rtnl;
@@ -347,18 +298,19 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp)
 	 * entry winds up in the list the way I'm currently handling them.)
 	 */
 	while ((clnt = nlist_gethost(clnt, mon_name, 0))) {
-		if (matchhostname(NL_MY_NAME(clnt), my_name) &&
+		if (statd_matchhostname(NL_MY_NAME(clnt), my_name) &&
 			NL_MY_PROC(clnt) == id->my_proc &&
 			NL_MY_PROG(clnt) == id->my_prog &&
 			NL_MY_VERS(clnt) == id->my_vers) {
 			/* Match! */
-			dprintf(N_DEBUG, "UNMONITORING %s for %s",
+			xlog(D_GENERAL, "UNMONITORING %s for %s",
 					mon_name, my_name);
 
 			/* PRC: do the HA callout: */
 			ha_callout("del-client", mon_name, my_name, -1);
 
-			xunlink(SM_DIR, clnt->dns_name);
+			nsm_delete_monitored_host(clnt->dns_name,
+							mon_name, my_name);
 			nlist_free(&rtnl, clnt);
 
 			return (&result);
@@ -367,7 +319,7 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp)
 	}
 
  failure:
-	note(N_WARNING, "Received erroneous SM_UNMON request from %s for %s",
+	xlog_warn("Received erroneous SM_UNMON request from %s for %s",
 		my_name, mon_name);
 	return (&result);
 }
@@ -381,13 +333,15 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp)
 	notify_list	*clnt;
 	char		*my_name = argp->my_name;
 
+	xlog(D_CALL, "Received SM_UNMON_ALL for %s", my_name);
+
 	if (!caller_is_localhost(rqstp))
 		goto failure;
 
 	result.state = MY_STATE;
 
 	if (rtnl == NULL) {
-		note(N_WARNING, "Received SM_UNMON_ALL request from %s "
+		xlog_warn("Received SM_UNMON_ALL request from %s "
 			"while not monitoring any hosts", my_name);
 		return (&result);
 	}
@@ -401,7 +355,7 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp)
 			char            mon_name[SM_MAXSTRLEN + 1];
 			notify_list	*temp;
 
-			dprintf(N_DEBUG,
+			xlog(D_GENERAL,
 				"UNMONITORING (SM_UNMON_ALL) %s for %s",
 				NL_MON_NAME(clnt), NL_MY_NAME(clnt));
 			strncpy(mon_name, NL_MON_NAME(clnt),
@@ -410,7 +364,8 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp)
 			temp = NL_NEXT(clnt);
 			/* PRC: do the HA callout: */
 			ha_callout("del-client", mon_name, my_name, -1);
-			xunlink(SM_DIR, clnt->dns_name);
+			nsm_delete_monitored_host(clnt->dns_name,
+							mon_name, my_name);
 			nlist_free(&rtnl, clnt);
 			++count;
 			clnt = temp;
@@ -419,8 +374,8 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp)
 	}
 
 	if (!count) {
-		dprintf(N_DEBUG, "SM_UNMON_ALL request from %s with no "
-			"SM_MON requests from it.", my_name);
+		xlog(D_GENERAL, "SM_UNMON_ALL request from %s with no "
+			"SM_MON requests from it", my_name);
 	}
 
  failure:
diff --git a/utils/statd/notlist.c b/utils/statd/notlist.c
index 1698c26..0341c15 100644
--- a/utils/statd/notlist.c
+++ b/utils/statd/notlist.c
@@ -17,7 +17,6 @@
 #endif
 
 #include <string.h>
-#include "misc.h"
 #include "statd.h"
 #include "notlist.h"
 
@@ -190,7 +189,6 @@ nlist_clone(notify_list *entry)
 	NL_MY_PROG(new) = NL_MY_PROG(entry);
 	NL_MY_VERS(new) = NL_MY_VERS(entry);
 	NL_MY_PROC(new) = NL_MY_PROC(entry);
-	NL_ADDR(new)    = NL_ADDR(entry);
 	memcpy(NL_PRIV(new), NL_PRIV(entry), SM_PRIV_SIZE);
 
 	return new;
@@ -234,7 +232,8 @@ nlist_gethost(notify_list *list, char *host, int myname)
 	notify_list	*lp;
 
 	for (lp = list; lp; lp = lp->next) {
-		if (matchhostname(host, myname? NL_MY_NAME(lp) : NL_MON_NAME(lp)))
+		if (statd_matchhostname(host,
+				myname? NL_MY_NAME(lp) : NL_MON_NAME(lp)))
 			return lp;
 	}
 
diff --git a/utils/statd/notlist.h b/utils/statd/notlist.h
index 664c9d8..6ed0da8 100644
--- a/utils/statd/notlist.h
+++ b/utils/statd/notlist.h
@@ -12,15 +12,14 @@
  */
 struct notify_list {
   mon			mon;	/* Big honkin' NSM structure. */
-  struct in_addr	addr;	/* IP address for callback. */
-  unsigned short	port;	/* port number for callback */
+  in_port_t		port;	/* port number for callback */
   short int		times;	/* Counter used for various things. */
   int			state;	/* For storing notified state for callbacks. */
   char			*dns_name; /* used for matching incoming
 				    * NOTIFY requests */
   struct notify_list	*next;	/* Linked list forward pointer. */
   struct notify_list	*prev;	/* Linked list backward pointer. */
-  u_int32_t		xid;	/* XID of MS_NOTIFY RPC call */
+  uint32_t		xid;	/* XID of MS_NOTIFY RPC call */
   time_t		when;	/* notify: timeout for re-xmit */
 };
 
@@ -53,7 +52,6 @@ extern notify_list *	nlist_gethost(notify_list *, char *, int);
 #define NL_FIRST	NL_NEXT
 #define NL_PREV(L)	((L)->prev)
 #define NL_DATA(L)	((L)->mon)
-#define NL_ADDR(L)	((L)->addr)
 #define NL_STATE(L)	((L)->state)
 #define NL_TIMES(L)	((L)->times)
 #define NL_MON_ID(L)	(NL_DATA((L)).mon_id)
diff --git a/utils/statd/rmtcall.c b/utils/statd/rmtcall.c
index cc1a4a4..0e52fe2 100644
--- a/utils/statd/rmtcall.c
+++ b/utils/statd/rmtcall.c
@@ -37,22 +37,19 @@
 #include <netdb.h>
 #include <string.h>
 #include <unistd.h>
-#ifdef HAVE_IFADDRS_H
-#include <ifaddrs.h>
-#endif /* HAVE_IFADDRS_H */
+
 #include "sm_inter.h"
 #include "statd.h"
 #include "notlist.h"
-#include "log.h"
 #include "ha-callout.h"
 
+#include "nsm.h"
+#include "nfsrpc.h"
+
 #if SIZEOF_SOCKLEN_T - 0 == 0
 #define socklen_t int
 #endif
 
-#define MAXMSGSIZE	(2048 / sizeof(unsigned int))
-
-static unsigned long	xid = 0;	/* RPC XID counter */
 static int		sockfd = -1;	/* notify socket */
 
 /*
@@ -81,7 +78,7 @@ statd_get_socket(void)
 		if (sockfd >= 0) close(sockfd);
 
 		if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
-			note(N_CRIT, "%s: Can't create socket: %m", __func__);
+			xlog(L_ERROR, "%s: Can't create socket: %m", __func__);
 			return -1;
 		}
 
@@ -91,7 +88,7 @@ statd_get_socket(void)
 		sin.sin_addr.s_addr = INADDR_ANY;
 
 		if (bindresvport(sockfd, &sin) < 0) {
-			dprintf(N_WARNING, "%s: can't bind to reserved port",
+			xlog(D_GENERAL, "%s: can't bind to reserved port",
 					__func__);
 			break;
 		}
@@ -104,112 +101,37 @@ statd_get_socket(void)
 	return sockfd;
 }
 
-static unsigned long
-xmit_call(struct sockaddr_in *sin,
-	  u_int32_t prog, u_int32_t vers, u_int32_t proc,
-	  xdrproc_t func, void *obj)
-/* 		__u32 prog, __u32 vers, __u32 proc, xdrproc_t func, void *obj) */
-{
-	unsigned int		msgbuf[MAXMSGSIZE], msglen;
-	struct rpc_msg		mesg;
-	struct pmap		pmap;
-	XDR			xdr, *xdrs = &xdr;
-	int			err;
-
-	if (!xid)
-		xid = getpid() + time(NULL);
-
-	mesg.rm_xid = ++xid;
-	mesg.rm_direction = CALL;
-	mesg.rm_call.cb_rpcvers = 2;
-	if (sin->sin_port == 0) {
-		sin->sin_port = htons(PMAPPORT);
-		mesg.rm_call.cb_prog = PMAPPROG;
-		mesg.rm_call.cb_vers = PMAPVERS;
-		mesg.rm_call.cb_proc = PMAPPROC_GETPORT;
-		pmap.pm_prog = prog;
-		pmap.pm_vers = vers;
-		pmap.pm_prot = IPPROTO_UDP;
-		pmap.pm_port = 0;
-		func = (xdrproc_t) xdr_pmap;
-		obj  = &pmap;
-	} else {
-		mesg.rm_call.cb_prog = prog;
-		mesg.rm_call.cb_vers = vers;
-		mesg.rm_call.cb_proc = proc;
-	}
-	mesg.rm_call.cb_cred.oa_flavor = AUTH_NULL;
-	mesg.rm_call.cb_cred.oa_base = (caddr_t) NULL;
-	mesg.rm_call.cb_cred.oa_length = 0;
-	mesg.rm_call.cb_verf.oa_flavor = AUTH_NULL;
-	mesg.rm_call.cb_verf.oa_base = (caddr_t) NULL;
-	mesg.rm_call.cb_verf.oa_length = 0;
-
-	/* Create XDR memory object for encoding */
-	xdrmem_create(xdrs, (caddr_t) msgbuf, sizeof(msgbuf), XDR_ENCODE);
-
-	/* Encode the RPC header part and payload */
-	if (!xdr_callmsg(xdrs, &mesg) || !func(xdrs, obj)) {
-		dprintf(N_WARNING, "%s: can't encode RPC message!", __func__);
-		xdr_destroy(xdrs);
-		return 0;
-	}
-
-	/* Get overall length of datagram */
-	msglen = xdr_getpos(xdrs);
-
-	if ((err = sendto(sockfd, msgbuf, msglen, 0,
-			(struct sockaddr *) sin, sizeof(*sin))) < 0) {
-		dprintf(N_WARNING, "%s: sendto failed: %m", __func__);
-	} else if (err != msglen) {
-		dprintf(N_WARNING, "%s: short write: %m", __func__);
-	}
-
-	xdr_destroy(xdrs);
-
-	return err == msglen? xid : 0;
-}
-
 static notify_list *
-recv_rply(struct sockaddr_in *sin, u_long *portp)
+recv_rply(u_long *portp)
 {
-	unsigned int		msgbuf[MAXMSGSIZE], msglen;
-	struct rpc_msg		mesg;
+	char			msgbuf[NSM_MAXMSGSIZE];
+	ssize_t			msglen;
 	notify_list		*lp = NULL;
-	XDR			xdr, *xdrs = &xdr;
-	socklen_t		alen = sizeof(*sin);
-
-	/* Receive message */
-	if ((msglen = recvfrom(sockfd, msgbuf, sizeof(msgbuf), 0,
-			(struct sockaddr *) sin, &alen)) < 0) {
-		dprintf(N_WARNING, "%s: recvfrom failed: %m", __func__);
+	XDR			xdr;
+	struct sockaddr_in	sin;
+	socklen_t		alen = (socklen_t)sizeof(sin);
+	uint32_t		xid;
+
+	memset(msgbuf, 0, sizeof(msgbuf));
+	msglen = recvfrom(sockfd, msgbuf, sizeof(msgbuf), 0,
+				(struct sockaddr *)(char *)&sin, &alen);
+	if (msglen == (ssize_t)-1) {
+		xlog_warn("%s: recvfrom failed: %m", __func__);
 		return NULL;
 	}
 
-	/* Create XDR object for decoding buffer */
-	xdrmem_create(xdrs, (caddr_t) msgbuf, msglen, XDR_DECODE);
-
-	memset(&mesg, 0, sizeof(mesg));
-	mesg.rm_reply.rp_acpt.ar_results.where = NULL;
-	mesg.rm_reply.rp_acpt.ar_results.proc = (xdrproc_t) xdr_void;
-
-	if (!xdr_replymsg(xdrs, &mesg)) {
-		note(N_WARNING, "%s: can't decode RPC message!", __func__);
+	memset(&xdr, 0, sizeof(xdr));
+	xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE);
+	xid = nsm_parse_reply(&xdr);
+	if (xid == 0)
 		goto done;
-	}
+	if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
+		struct in_addr addr = sin.sin_addr;
+		char buf[INET_ADDRSTRLEN];
 
-	if (mesg.rm_reply.rp_stat != 0) {
-		note(N_WARNING, "%s: [%s] RPC status %d", 
-				__func__,
-				inet_ntoa(sin->sin_addr),
-				mesg.rm_reply.rp_stat);
-		goto done;
-	}
-	if (mesg.rm_reply.rp_acpt.ar_stat != 0) {
-		note(N_WARNING, "%s: [%s] RPC status %d",
-				__func__,
-				inet_ntoa(sin->sin_addr),
-				mesg.rm_reply.rp_acpt.ar_stat);
+		xlog_warn("%s: Unrecognized reply from %s", __func__,
+				inet_ntop(AF_INET, &addr, buf,
+						(socklen_t)sizeof(buf)));
 		goto done;
 	}
 
@@ -217,32 +139,15 @@ recv_rply(struct sockaddr_in *sin, u_long *portp)
 		/* LH - this was a bug... it should have been checking
 		 * the xid from the response message from the client,
 		 * not the static, internal xid */
-		if (lp->xid != mesg.rm_xid)
+		if (lp->xid != xid)
 			continue;
-		if (lp->addr.s_addr != sin->sin_addr.s_addr) {
-			char addr [18];
-			strncpy (addr, inet_ntoa(lp->addr),
-				 sizeof (addr) - 1);
-			addr [sizeof (addr) - 1] = '\0';
-			dprintf(N_WARNING, "%s: address mismatch: "
-				"expected %s, got %s", __func__,
-				addr, inet_ntoa(sin->sin_addr));
-		}
-		if (lp->port == 0) {
-			if (!xdr_u_long(xdrs, portp)) {
-				note(N_WARNING,
-					"%s: [%s] can't decode reply body!",
-					__func__,
-					inet_ntoa(sin->sin_addr));
-				lp = NULL;
-				goto done;
-			}
-		}
+		if (lp->port == 0)
+			*portp = nsm_recv_getport(&xdr);
 		break;
 	}
 
 done:
-	xdr_destroy(xdrs);
+	xdr_destroy(&xdr);
 	return lp;
 }
 
@@ -253,15 +158,10 @@ static int
 process_entry(notify_list *lp)
 {
 	struct sockaddr_in	sin;
-	struct status		new_status;
-	xdrproc_t		func;
-	void			*objp;
-	u_int32_t		proc, vers, prog;
-/* 	__u32			proc, vers, prog; */
 
 	if (NL_TIMES(lp) == 0) {
-		note(N_DEBUG, "%s: Cannot notify %s, giving up.",
-				__func__, inet_ntoa(NL_ADDR(lp)));
+		xlog(D_GENERAL, "%s: Cannot notify localhost, giving up",
+				__func__);
 		return 0;
 	}
 
@@ -270,23 +170,31 @@ process_entry(notify_list *lp)
 	sin.sin_port   = lp->port;
 	/* LH - moved address into switch */
 
-	prog = NL_MY_PROG(lp);
-	vers = NL_MY_VERS(lp);
-	proc = NL_MY_PROC(lp);
-
 	/* __FORCE__ loopback for callbacks to lockd ... */
 	/* Just in case we somehow ignored it thus far */
 	sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
 
-	func = (xdrproc_t) xdr_status;
-	objp = &new_status;
-	new_status.mon_name = NL_MON_NAME(lp);
-	new_status.state    = NL_STATE(lp);
-	memcpy(new_status.priv, NL_PRIV(lp), SM_PRIV_SIZE);
+	if (sin.sin_port == 0)
+		lp->xid = nsm_xmit_getport(sockfd, &sin,
+					(rpcprog_t)NL_MY_PROG(lp),
+					(rpcvers_t)NL_MY_VERS(lp));
+	else {
+		struct mon m;
+
+		memcpy(m.priv, NL_PRIV(lp), SM_PRIV_SIZE);
 
-	lp->xid = xmit_call(&sin, prog, vers, proc, func, objp);
-	if (!lp->xid) {
-		note(N_WARNING, "%s: failed to notify port %d",
+		m.mon_id.mon_name = NL_MON_NAME(lp);
+		m.mon_id.my_id.my_name = NULL;
+		m.mon_id.my_id.my_prog = NL_MY_PROG(lp);
+		m.mon_id.my_id.my_vers = NL_MY_VERS(lp);
+		m.mon_id.my_id.my_proc = NL_MY_PROC(lp);
+
+		lp->xid = nsm_xmit_nlmcall(sockfd,
+				(struct sockaddr *)(char *)&sin,
+				(socklen_t)sizeof(sin), &m, NL_STATE(lp));
+	}
+	if (lp->xid == 0) {
+		xlog_warn("%s: failed to notify port %d",
 				__func__, ntohs(lp->port));
 	}
 	NL_TIMES(lp) -= 1;
@@ -300,14 +208,13 @@ process_entry(notify_list *lp)
 int
 process_reply(FD_SET_TYPE *rfds)
 {
-	struct sockaddr_in	sin;
 	notify_list		*lp;
 	u_long			port;
 
 	if (sockfd == -1 || !FD_ISSET(sockfd, rfds))
 		return 0;
 
-	if (!(lp = recv_rply(&sin, &port)))
+	if (!(lp = recv_rply(&port)))
 		return 1;
 
 	if (lp->port == 0) {
@@ -319,10 +226,10 @@ process_reply(FD_SET_TYPE *rfds)
 			nlist_insert_timer(&notify, lp);
 			return 1;
 		}
-		note(N_WARNING, "%s: [%s] service %d not registered",
-			__func__, inet_ntoa(lp->addr), NL_MY_PROG(lp));
+		xlog_warn("%s: service %d not registered on localhost",
+			__func__, NL_MY_PROG(lp));
 	} else {
-		dprintf(N_DEBUG, "%s: Callback to %s (for %d) succeeded.",
+		xlog(D_GENERAL, "%s: Callback to %s (for %d) succeeded",
 			__func__, NL_MY_NAME(lp), NL_MON_NAME(lp));
 	}
 	nlist_free(&notify, lp);
@@ -346,8 +253,8 @@ process_notify_list(void)
 			nlist_remove(&notify, entry);
 			nlist_insert_timer(&notify, entry);
 		} else {
-			note(N_ERROR,
-				"%s: Can't callback %s (%d,%d), giving up.",
+			xlog(L_ERROR,
+				"%s: Can't callback %s (%d,%d), giving up",
 					__func__,
 					NL_MY_NAME(entry),
 					NL_MY_PROG(entry),
diff --git a/utils/statd/simu.c b/utils/statd/simu.c
index a7ecb85..825e428 100644
--- a/utils/statd/simu.c
+++ b/utils/statd/simu.c
@@ -8,8 +8,10 @@
 #include <config.h>
 #endif
 
+#include <netdb.h>
 #include <arpa/inet.h>
 
+#include "sockaddr.h"
 #include "rpcmisc.h"
 #include "statd.h"
 #include "notlist.h"
@@ -19,32 +21,29 @@ extern void my_svc_exit (void);
 
 /*
  * Services SM_SIMU_CRASH requests.
+ *
+ * Although the kernel contacts the statd service via only IPv4
+ * transports, the statd service can receive other requests, such
+ * as SM_NOTIFY, from remote peers via IPv6.
  */
 void *
-sm_simu_crash_1_svc (void *argp, struct svc_req *rqstp)
+sm_simu_crash_1_svc (__attribute__ ((unused)) void *argp, struct svc_req *rqstp)
 {
-  struct sockaddr_in *sin = nfs_getrpccaller_in(rqstp->rq_xprt);
+  struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt);
+  char buf[INET6_ADDRSTRLEN];
   static char *result = NULL;
-  struct in_addr caller;
 
-  if (sin->sin_family != AF_INET) {
-    note(N_WARNING, "Call to statd from non-AF_INET address");
-    goto failure;
-  }
+  xlog(D_CALL, "Received SM_SIMU_CRASH");
 
-  caller = sin->sin_addr;
-  if (caller.s_addr != htonl(INADDR_LOOPBACK)) {
-    note(N_WARNING, "Call to statd from non-local host %s",
-      inet_ntoa(caller));
-    goto failure;
-  }
+  if (!nfs_is_v4_loopback(sap))
+    goto out_nonlocal;
 
-  if (ntohs(sin->sin_port) >= 1024) {
-    note(N_WARNING, "Call to statd-simu-crash from unprivileged port");
+  if ((int)nfs_get_port(sap) >= IPPORT_RESERVED) {
+    xlog_warn("SM_SIMU_CRASH call from unprivileged port");
     goto failure;
   }
 
-  note (N_WARNING, "*** SIMULATING CRASH! ***");
+  xlog_warn("*** SIMULATING CRASH! ***");
   my_svc_exit ();
 
   if (rtnl)
@@ -52,4 +51,10 @@ sm_simu_crash_1_svc (void *argp, struct svc_req *rqstp)
 
  failure:
   return ((void *)&result);
+
+ out_nonlocal:
+  if (!statd_present_address(sap, buf, sizeof(buf)))
+    buf[0] = '\0';
+  xlog_warn("SM_SIMU_CRASH call from non-local host %s", buf);
+  goto failure;
 }
diff --git a/utils/statd/simulate.c b/utils/statd/simulate.c
index de8f1c9..4ed1468 100644
--- a/utils/statd/simulate.c
+++ b/utils/statd/simulate.c
@@ -38,7 +38,9 @@ extern void svc_exit (void);
 void
 simulator (int argc, char **argv)
 {
-  log_enable (1);
+  xlog_stderr (1);
+  xlog_syslog (0);
+  xlog_open ("statd simulator");
 
   if (argc == 2)
     if (!strcasecmp (*argv, "crash"))
@@ -61,7 +63,7 @@ simulator (int argc, char **argv)
       simulate_mon (*(&argv[1]), *(&argv[2]), *(&argv[3]), *(&argv[4]),
 		    *(&argv[5]));
   }
-  die ("WTF?  Give me something I can use!");
+  xlog_err ("WTF?  Give me something I can use!");
 }
 
 static void
@@ -72,11 +74,11 @@ simulate_mon (char *calling, char *monitoring, char *as, char *proggy,
   sm_stat_res *result;
   mon mon;
 
-  dprintf (N_DEBUG, "Calling %s (as %s) to monitor %s", calling, as,
+  xlog (D_GENERAL, "Calling %s (as %s) to monitor %s", calling, as,
 	   monitoring);
 
   if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL)
-    die ("%s", clnt_spcreateerror ("clnt_create"));
+    xlog_err ("%s", clnt_spcreateerror ("clnt_create"));
 
   memcpy (mon.priv, fool, SM_PRIV_SIZE);
   mon.mon_id.my_id.my_name = xstrdup (as);
@@ -87,16 +89,15 @@ simulate_mon (char *calling, char *monitoring, char *as, char *proggy,
   mon.mon_id.mon_name = monitoring;
 
   if (!(result = sm_mon_1 (&mon, client)))
-    die ("%s", clnt_sperror (client, "sm_mon_1"));
+    xlog_err ("%s", clnt_sperror (client, "sm_mon_1"));
 
   free (mon.mon_id.my_id.my_name);
 
   if (result->res_stat != STAT_SUCC) {
-    note (N_FATAL, "SM_MON request failed, state: %d", result->state);
-    exit (0);
+    xlog_err ("SM_MON request failed, state: %d", result->state);
   } else {
-    dprintf (N_DEBUG, "SM_MON result successful, state: %d\n", result->state);
-    dprintf (N_DEBUG, "Waiting for callback.");
+    xlog (D_GENERAL, "SM_MON result successful, state: %d\n", result->state);
+    xlog (D_GENERAL, "Waiting for callback");
     daemon_simulator ();
     exit (0);
   }
@@ -109,11 +110,11 @@ simulate_unmon (char *calling, char *unmonitoring, char *as, char *proggy)
   sm_stat *result;
   mon_id mon_id;
 
-  dprintf (N_DEBUG, "Calling %s (as %s) to unmonitor %s", calling, as,
+  xlog (D_GENERAL, "Calling %s (as %s) to unmonitor %s", calling, as,
 	   unmonitoring);
 
   if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL)
-    die ("%s", clnt_spcreateerror ("clnt_create"));
+    xlog_err ("%s", clnt_spcreateerror ("clnt_create"));
 
   mon_id.my_id.my_name = xstrdup (as);
   mon_id.my_id.my_prog = atoi (proggy) * SIM_SM_PROG;
@@ -122,10 +123,10 @@ simulate_unmon (char *calling, char *unmonitoring, char *as, char *proggy)
   mon_id.mon_name = unmonitoring;
 
   if (!(result = sm_unmon_1 (&mon_id, client)))
-    die ("%s", clnt_sperror (client, "sm_unmon_1"));
+    xlog_err ("%s", clnt_sperror (client, "sm_unmon_1"));
 
   free (mon_id.my_id.my_name);
-  dprintf (N_DEBUG, "SM_UNMON request returned state: %d\n", result->state);
+  xlog (D_GENERAL, "SM_UNMON request returned state: %d\n", result->state);
   exit (0);
 }
 
@@ -136,10 +137,10 @@ simulate_unmon_all (char *calling, char *as, char *proggy)
   sm_stat *result;
   my_id my_id;
 
-  dprintf (N_DEBUG, "Calling %s (as %s) to unmonitor all hosts", calling, as);
+  xlog (D_GENERAL, "Calling %s (as %s) to unmonitor all hosts", calling, as);
 
   if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL)
-    die ("%s", clnt_spcreateerror ("clnt_create"));
+    xlog_err ("%s", clnt_spcreateerror ("clnt_create"));
 
   my_id.my_name = xstrdup (as);
   my_id.my_prog = atoi (proggy) * SIM_SM_PROG;
@@ -147,10 +148,10 @@ simulate_unmon_all (char *calling, char *as, char *proggy)
   my_id.my_proc = SIM_SM_MON;
 
   if (!(result = sm_unmon_all_1 (&my_id, client)))
-    die ("%s", clnt_sperror (client, "sm_unmon_all_1"));
+    xlog_err ("%s", clnt_sperror (client, "sm_unmon_all_1"));
 
   free (my_id.my_name);
-  dprintf (N_DEBUG, "SM_UNMON_ALL request returned state: %d\n", result->state);
+  xlog (D_GENERAL, "SM_UNMON_ALL request returned state: %d\n", result->state);
   exit (0);
 }
 
@@ -160,10 +161,10 @@ simulate_crash (char *host)
   CLIENT *client;
 
   if ((client = clnt_create (host, SM_PROG, SM_VERS, "udp")) == NULL)
-    die ("%s", clnt_spcreateerror ("clnt_create"));
+    xlog_err ("%s", clnt_spcreateerror ("clnt_create"));
 
   if (!sm_simu_crash_1 (NULL, client))
-    die ("%s", clnt_sperror (client, "sm_simu_crash_1"));
+    xlog_err ("%s", clnt_sperror (client, "sm_simu_crash_1"));
 
   exit (0);
 }
@@ -176,18 +177,18 @@ simulate_stat (char *calling, char *monitoring)
   sm_stat_res *result;
   
   if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL)
-    die ("%s", clnt_spcreateerror ("clnt_create"));
+    xlog_err ("%s", clnt_spcreateerror ("clnt_create"));
 
   checking.mon_name = monitoring;
 
   if (!(result = sm_stat_1 (&checking, client)))
-    die ("%s", clnt_sperror (client, "sm_stat_1"));
+    xlog_err ("%s", clnt_sperror (client, "sm_stat_1"));
 
   if (result->res_stat == STAT_SUCC)
-    dprintf (N_DEBUG, "STAT_SUCC from %s for %s, state: %d", calling,
+    xlog (D_GENERAL, "STAT_SUCC from %s for %s, state: %d", calling,
 	     monitoring, result->state);
   else
-    dprintf (N_DEBUG, "STAT_FAIL from %s for %s, state: %d", calling,
+    xlog (D_GENERAL, "STAT_FAIL from %s for %s, state: %d", calling,
 	     monitoring, result->state);
 
   exit (0);
@@ -196,9 +197,8 @@ simulate_stat (char *calling, char *monitoring)
 static void
 sim_killer (int sig)
 {
-  note (N_FATAL, "Simulator caught signal %d, un-registering and exiting.", sig);
   pmap_unset (sim_port, SIM_SM_VERS);
-  exit (0);
+  xlog_err ("Simulator caught signal %d, un-registering and exiting", sig);
 }
 
 static void
@@ -219,7 +219,7 @@ sim_sm_mon_1_svc (struct status *argp, struct svc_req *rqstp)
 {
   static char *result;
 
-  dprintf (N_DEBUG, "Recieved state %d for mon_name %s (opaque \"%s\")",
+  xlog (D_GENERAL, "Recieved state %d for mon_name %s (opaque \"%s\")",
 	   argp->state, argp->mon_name, argp->priv);
   svc_exit ();
   return ((void *)&result);
diff --git a/utils/statd/sm-notify.c b/utils/statd/sm-notify.c
index 72dcff4..3259a3e 100644
--- a/utils/statd/sm-notify.c
+++ b/utils/statd/sm-notify.c
@@ -8,6 +8,7 @@
 #include <config.h>
 #endif
 
+#include <err.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
@@ -28,129 +29,114 @@
 #include <errno.h>
 #include <grp.h>
 
-#ifndef BASEDIR
-# ifdef NFS_STATEDIR
-#  define BASEDIR		NFS_STATEDIR
-# else
-#  define BASEDIR		"/var/lib/nfs"
-# endif
-#endif
-
-#define DEFAULT_SM_STATE_PATH	BASEDIR "/state"
-#define	DEFAULT_SM_DIR_PATH	BASEDIR "/sm"
-#define	DEFAULT_SM_BAK_PATH	DEFAULT_SM_DIR_PATH ".bak"
+#include "sockaddr.h"
+#include "xlog.h"
+#include "nsm.h"
+#include "nfsrpc.h"
 
-char *_SM_BASE_PATH = BASEDIR;
-char *_SM_STATE_PATH = DEFAULT_SM_STATE_PATH;
-char *_SM_DIR_PATH = DEFAULT_SM_DIR_PATH;
-char *_SM_BAK_PATH = DEFAULT_SM_BAK_PATH;
+#ifndef HAVE_DECL_AI_ADDRCONFIG
+#define AI_ADDRCONFIG	0
+#endif
 
-#define NSM_PROG	100024
-#define NSM_PROGRAM	100024
-#define NSM_VERSION	1
 #define NSM_TIMEOUT	2
-#define NSM_NOTIFY	6
 #define NSM_MAX_TIMEOUT	120	/* don't make this too big */
-#define MAXMSGSIZE	256
 
 struct nsm_host {
 	struct nsm_host *	next;
 	char *			name;
-	char *			path;
-	struct sockaddr_storage	addr;
+	char *			mon_name;
+	char *			my_name;
 	struct addrinfo		*ai;
 	time_t			last_used;
 	time_t			send_next;
 	unsigned int		timeout;
 	unsigned int		retries;
-	unsigned int		xid;
+	uint32_t		xid;
 };
 
 static char		nsm_hostname[256];
-static uint32_t		nsm_state;
+static int		nsm_state;
+static int		nsm_family = AF_INET;
 static int		opt_debug = 0;
-static int		opt_quiet = 0;
-static int		opt_update_state = 1;
+static _Bool		opt_update_state = true;
 static unsigned int	opt_max_retry = 15 * 60;
-static char *		opt_srcaddr = 0;
-static uint16_t		opt_srcport = 0;
-static int		log_syslog = 0;
+static char *		opt_srcaddr = NULL;
+static char *		opt_srcport = NULL;
 
-static unsigned int	nsm_get_state(int);
-static void		notify(void);
+static void		notify(const int sock);
 static int		notify_host(int, struct nsm_host *);
 static void		recv_reply(int);
-static void		backup_hosts(const char *, const char *);
-static void		get_hosts(const char *);
 static void		insert_host(struct nsm_host *);
 static struct nsm_host *find_host(uint32_t);
-static void		nsm_log(int fac, const char *fmt, ...);
 static int		record_pid(void);
-static void		drop_privs(void);
-static void		set_kernel_nsm_state(int state);
 
 static struct nsm_host *	hosts = NULL;
 
-/*
- * Address handling utilities
- */
-
-static unsigned short smn_get_port(const struct sockaddr *sap)
+__attribute_malloc__
+static struct addrinfo *
+smn_lookup(const char *name)
 {
-	switch (sap->sa_family) {
-	case AF_INET:
-		return ntohs(((struct sockaddr_in *)sap)->sin_port);
-	case AF_INET6:
-		return ntohs(((struct sockaddr_in6 *)sap)->sin6_port);
-	}
-	return 0;
-}
+	struct addrinfo	*ai = NULL;
+	struct addrinfo hint = {
+		.ai_flags	= AI_ADDRCONFIG,
+		.ai_family	= (nsm_family == AF_INET ? AF_INET: AF_UNSPEC),
+		.ai_protocol	= (int)IPPROTO_UDP,
+	};
+	int error;
 
-static void smn_set_port(struct sockaddr *sap, const unsigned short port)
-{
-	switch (sap->sa_family) {
-	case AF_INET:
-		((struct sockaddr_in *)sap)->sin_port = htons(port);
-		break;
-	case AF_INET6:
-		((struct sockaddr_in6 *)sap)->sin6_port = htons(port);
-		break;
+	error = getaddrinfo(name, NULL, &hint, &ai);
+	if (error != 0) {
+		xlog(D_GENERAL, "getaddrinfo(3): %s", gai_strerror(error));
+		return NULL;
 	}
+
+	return ai;
 }
 
-static struct addrinfo *smn_lookup(const char *name)
+__attribute_malloc__
+static struct nsm_host *
+smn_alloc_host(const char *hostname, const char *mon_name,
+		const char *my_name, const time_t timestamp)
 {
-	struct addrinfo	*ai, hint = {
-#if HAVE_DECL_AI_ADDRCONFIG
-		.ai_flags	= AI_ADDRCONFIG,
-#endif	/* HAVE_DECL_AI_ADDRCONFIG */
-		.ai_family	= AF_INET,
-		.ai_protocol	= IPPROTO_UDP,
-	};
-	int error;
+	struct nsm_host	*host;
 
-	error = getaddrinfo(name, NULL, &hint, &ai);
-	switch (error) {
-	case 0:
-		return ai;
-	case EAI_SYSTEM: 
-		if (opt_debug)
-			nsm_log(LOG_ERR, "getaddrinfo(3): %s",
-					strerror(errno));
-		break;
-	default:
-		if (opt_debug)
-			nsm_log(LOG_ERR, "getaddrinfo(3): %s",
-					gai_strerror(error));
+	host = calloc(1, sizeof(*host));
+	if (host == NULL)
+		goto out_nomem;
+
+	host->name = strdup(hostname);
+	host->mon_name = strdup(mon_name);
+	host->my_name = strdup(my_name);
+	if (host->name == NULL ||
+	    host->mon_name == NULL ||
+	    host->my_name == NULL) {
+		free(host->my_name);
+		free(host->mon_name);
+		free(host->name);
+		free(host);
+		goto out_nomem;
 	}
 
+	host->last_used = timestamp;
+	host->timeout = NSM_TIMEOUT;
+	host->retries = 100;		/* force address retry */
+
+	return host;
+
+out_nomem:
+	xlog_warn("Unable to allocate memory");
 	return NULL;
 }
 
 static void smn_forget_host(struct nsm_host *host)
 {
-	unlink(host->path);
-	free(host->path);
+	xlog(D_CALL, "Removing %s (%s, %s) from notify list",
+			host->name, host->mon_name, host->my_name);
+
+	nsm_delete_notified_host(host->name, host->mon_name, host->my_name);
+
+	free(host->my_name);
+	free(host->mon_name);
 	free(host->name);
 	if (host->ai)
 		freeaddrinfo(host->ai);
@@ -158,13 +144,219 @@ static void smn_forget_host(struct nsm_host *host)
 	free(host);
 }
 
+static unsigned int
+smn_get_host(const char *hostname,
+		__attribute__ ((unused)) const struct sockaddr *sap,
+		const struct mon *m, const time_t timestamp)
+{
+	struct nsm_host	*host;
+
+	host = smn_alloc_host(hostname,
+		m->mon_id.mon_name, m->mon_id.my_id.my_name, timestamp);
+	if (host == NULL)
+		return 0;
+
+	insert_host(host);
+	xlog(D_GENERAL, "Added host %s to notify list", hostname);
+	return 1;
+}
+
+#ifdef IPV6_SUPPORTED
+static int smn_socket(void)
+{
+	int sock;
+
+	/*
+	 * Use an AF_INET socket if IPv6 is disabled on the
+	 * local system.
+	 */
+	sock = socket(AF_INET6, SOCK_DGRAM, 0);
+	if (sock == -1) {
+		if (errno != EAFNOSUPPORT) {
+			xlog(L_ERROR, "Failed to create RPC socket: %m");
+			return -1;
+		}
+		sock = socket(AF_INET, SOCK_DGRAM, 0);
+		if (sock < 0) {
+			xlog(L_ERROR, "Failed to create RPC socket: %m");
+			return -1;
+		}
+	} else
+		nsm_family = AF_INET6;
+
+	if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
+		xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m");
+		goto out_close;
+	}
+
+	/*
+	 * TI-RPC over IPv6 (udp6/tcp6) does not handle IPv4.  However,
+	 * since sm-notify open-codes all of its RPC support, it can
+	 * use a single socket and let the local network stack provide
+	 * the correct mapping between address families automatically.
+	 * This is the same thing that is done in the kernel.
+	 */
+	if (nsm_family == AF_INET6) {
+		const int zero = 0;
+		socklen_t zerolen = (socklen_t)sizeof(zero);
+
+		if (setsockopt(sock, SOL_IPV6, IPV6_V6ONLY,
+					(char *)&zero, zerolen) == -1) {
+			xlog(L_ERROR, "setsockopt(3) on RPC socket failed: %m");
+			goto out_close;
+		}
+	}
+
+	return sock;
+
+out_close:
+	(void)close(sock);
+	return -1;
+}
+#else	/* !IPV6_SUPPORTED */
+static int smn_socket(void)
+{
+	int sock;
+
+	sock = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sock == -1) {
+		xlog(L_ERROR, "Failed to create RPC socket: %m");
+		return -1;
+	}
+
+	if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
+		xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m");
+		(void)close(sock);
+		return -1;
+	}
+
+	return sock;
+}
+#endif	/* !IPV6_SUPPORTED */
+
+/*
+ * If admin specified a source address or srcport, then convert those
+ * to a sockaddr and return it.   Otherwise, return an ANYADDR address.
+ */
+__attribute_malloc__
+static struct addrinfo *
+smn_bind_address(const char *srcaddr, const char *srcport)
+{
+	struct addrinfo *ai = NULL;
+	struct addrinfo hint = {
+		.ai_flags	= AI_NUMERICSERV,
+		.ai_family	= nsm_family,
+		.ai_protocol	= (int)IPPROTO_UDP,
+	};
+	int error;
+
+	if (srcaddr == NULL)
+		hint.ai_flags |= AI_PASSIVE;
+
+	if (srcport == NULL)
+		error = getaddrinfo(srcaddr, "", &hint, &ai);
+	else
+		error = getaddrinfo(srcaddr, srcport, &hint, &ai);
+	if (error != 0) {
+		xlog(L_ERROR,
+			"Invalid bind address or port for RPC socket: %s",
+				gai_strerror(error));
+		return NULL;
+	}
+
+	return ai;
+}
+
+#ifdef HAVE_LIBTIRPC
+static int
+smn_bindresvport(int sock, struct sockaddr *sap)
+{
+	return bindresvport_sa(sock, sap);
+}
+
+#else	/* !HAVE_LIBTIRPC */
+static int
+smn_bindresvport(int sock, struct sockaddr *sap)
+{
+	if (sap->sa_family != AF_INET) {
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return bindresvport(sock, (struct sockaddr_in *)(char *)sap);
+}
+#endif	/* !HAVE_LIBTIRPC */
+
+/*
+ * Prepare a socket for sending RPC requests
+ *
+ * Returns a bound datagram socket file descriptor, or -1 if
+ * an error occurs.
+ */
+static int
+smn_create_socket(const char *srcaddr, const char *srcport)
+{
+	int sock, retry_cnt = 0;
+	struct addrinfo *ai;
+
+retry:
+	sock = smn_socket();
+	if (sock == -1)
+		return -1;
+
+	ai = smn_bind_address(srcaddr, srcport);
+	if (ai == NULL) {
+		(void)close(sock);
+		return -1;
+	}
+
+	/* Use source port if provided on the command line,
+	 * otherwise use bindresvport */
+	if (srcport) {
+		if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
+			xlog(L_ERROR, "Failed to bind RPC socket: %m");
+			freeaddrinfo(ai);
+			(void)close(sock);
+			return -1;
+		}
+	} else {
+		struct servent *se;
+
+		if (smn_bindresvport(sock, ai->ai_addr) == -1) {
+			xlog(L_ERROR,
+				"bindresvport on RPC socket failed: %m");
+			freeaddrinfo(ai);
+			(void)close(sock);
+			return -1;
+		}
+
+		/* try to avoid known ports */
+		se = getservbyport((int)nfs_get_port(ai->ai_addr), "udp");
+		if (se != NULL && retry_cnt < 100) {
+			retry_cnt++;
+			freeaddrinfo(ai);
+			(void)close(sock);
+			goto retry;
+		}
+	}
+
+	freeaddrinfo(ai);
+	return sock;
+}
+
 int
 main(int argc, char **argv)
 {
-	int	c;
-	int	force = 0;
+	int	c, sock, force = 0;
+	char *	progname;
 
-	while ((c = getopt(argc, argv, "dm:np:v:qP:f")) != -1) {
+	progname = strrchr(argv[0], '/');
+	if (progname != NULL)
+		progname++;
+	else
+		progname = argv[0];
+
+	while ((c = getopt(argc, argv, "dm:np:v:P:f")) != -1) {
 		switch (c) {
 		case 'f':
 			force = 1;
@@ -176,32 +368,17 @@ main(int argc, char **argv)
 			opt_max_retry = atoi(optarg) * 60;
 			break;
 		case 'n':
-			opt_update_state = 0;
+			opt_update_state = false;
 			break;
 		case 'p':
-			opt_srcport = atoi(optarg);
+			opt_srcport = optarg;
 			break;
 		case 'v':
 			opt_srcaddr = optarg;
 			break;
-		case 'q':
-			opt_quiet = 1;
-			break;
 		case 'P':
-			_SM_BASE_PATH = strdup(optarg);
-			_SM_STATE_PATH = malloc(strlen(optarg)+1+sizeof("state"));
-			_SM_DIR_PATH = malloc(strlen(optarg)+1+sizeof("sm"));
-			_SM_BAK_PATH = malloc(strlen(optarg)+1+sizeof("sm.bak"));
-			if (_SM_BASE_PATH == NULL ||
-			    _SM_STATE_PATH == NULL ||
-			    _SM_DIR_PATH == NULL ||
-			    _SM_BAK_PATH == NULL) {
-				nsm_log(LOG_ERR, "unable to allocate memory");
+			if (!nsm_setup_pathnames(argv[0], optarg))
 				exit(1);
-			}
-			strcat(strcpy(_SM_STATE_PATH, _SM_BASE_PATH), "/state");
-			strcat(strcpy(_SM_DIR_PATH, _SM_BASE_PATH), "/sm");
-			strcat(strcpy(_SM_BAK_PATH, _SM_BASE_PATH), "/sm.bak");
 			break;
 
 		default:
@@ -211,18 +388,26 @@ main(int argc, char **argv)
 
 	if (optind < argc) {
 usage:		fprintf(stderr,
-			"Usage: sm-notify [-dfq] [-m max-retry-minutes] [-p srcport]\n"
-			"            [-P /path/to/state/directory] [-v my_host_name]\n");
+			"Usage: %s -notify [-dfq] [-m max-retry-minutes] [-p srcport]\n"
+			"            [-P /path/to/state/directory] [-v my_host_name]\n",
+			progname);
 		exit(1);
 	}
 
-	log_syslog = 1;
-	openlog("sm-notify", LOG_PID, LOG_DAEMON);
+	xlog_syslog(1);
+	if (opt_debug) {
+		xlog_stderr(1);
+		xlog_config(D_ALL, 1);
+	} else
+		xlog_stderr(0);
+
+	xlog_open(progname);
+	xlog(L_NOTICE, "Version " VERSION " starting");
 
-	if (strcmp(_SM_BASE_PATH, BASEDIR) == 0) {
-		if (record_pid() == 0 && force == 0 && opt_update_state == 1) {
+	if (nsm_is_default_parentdir()) {
+		if (record_pid() == 0 && force == 0 && opt_update_state) {
 			/* already run, don't try again */
-			nsm_log(LOG_NOTICE, "Already notifying clients; Exiting!");
+			xlog(L_NOTICE, "Already notifying clients; Exiting!");
 			exit(0);
 		}
 	}
@@ -231,31 +416,26 @@ usage:		fprintf(stderr,
 		strncpy(nsm_hostname, opt_srcaddr, sizeof(nsm_hostname)-1);
 	} else
 	if (gethostname(nsm_hostname, sizeof(nsm_hostname)) < 0) {
-		nsm_log(LOG_ERR, "Failed to obtain name of local host: %s",
-			strerror(errno));
+		xlog(L_ERROR, "Failed to obtain name of local host: %m");
 		exit(1);
 	}
 
-	backup_hosts(_SM_DIR_PATH, _SM_BAK_PATH);
-	get_hosts(_SM_BAK_PATH);
-
-	/* If there are not hosts to notify, just exit */
-	if (!hosts) {
-		nsm_log(LOG_DEBUG, "No hosts to notify; exiting");
+	(void)nsm_retire_monitored_hosts();
+	if (nsm_load_notify_list(smn_get_host) == 0) {
+		xlog(D_GENERAL, "No hosts to notify; exiting");
 		return 0;
 	}
 
-	/* Get and update the NSM state. This will call sync() */
 	nsm_state = nsm_get_state(opt_update_state);
-	set_kernel_nsm_state(nsm_state);
+	if (nsm_state == 0)
+		exit(1);
+	nsm_update_kernel_state(nsm_state);
 
 	if (!opt_debug) {
-		if (!opt_quiet)
-			printf("Backgrounding to notify hosts...\n");
+		xlog(L_NOTICE, "Backgrounding to notify hosts...\n");
 
 		if (daemon(0, 0) < 0) {
-			nsm_log(LOG_ERR, "unable to background: %s",
-					strerror(errno));
+			xlog(L_ERROR, "unable to background: %m");
 			exit(1);
 		}
 
@@ -264,15 +444,21 @@ usage:		fprintf(stderr,
 		close(2);
 	}
 
-	notify();
+	sock = smn_create_socket(opt_srcaddr, opt_srcport);
+	if (sock == -1)
+		exit(1);
+
+	if (!nsm_drop_privileges(-1))
+		exit(1);
+
+	notify(sock);
 
 	if (hosts) {
 		struct nsm_host	*hp;
 
 		while ((hp = hosts) != 0) {
 			hosts = hp->next;
-			nsm_log(LOG_NOTICE,
-				"Unable to notify %s, giving up",
+			xlog(L_NOTICE, "Unable to notify %s, giving up",
 				hp->name);
 		}
 		exit(1);
@@ -285,69 +471,13 @@ usage:		fprintf(stderr,
  * Notify hosts
  */
 static void
-notify(void)
+notify(const int sock)
 {
-	struct sockaddr_storage address;
-	struct sockaddr *local_addr = (struct sockaddr *)&address;
 	time_t	failtime = 0;
-	int	sock = -1;
-	int retry_cnt = 0;
-
- retry:
-	sock = socket(AF_INET, SOCK_DGRAM, 0);
-	if (sock < 0) {
-		nsm_log(LOG_ERR, "Failed to create RPC socket: %s",
-			strerror(errno));
-		exit(1);
-	}
-	fcntl(sock, F_SETFL, O_NONBLOCK);
-
-	memset(&address, 0, sizeof(address));
-	local_addr->sa_family = AF_INET;	/* Default to IPv4 */
-
-	/* Bind source IP if provided on command line */
-	if (opt_srcaddr) {
-		struct addrinfo *ai = smn_lookup(opt_srcaddr);
-		if (!ai) {
-			nsm_log(LOG_ERR,
-				"Not a valid hostname or address: \"%s\"",
-				opt_srcaddr);
-			exit(1);
-		}
-
-		/* We know it's IPv4 at this point */
-		memcpy(local_addr, ai->ai_addr, ai->ai_addrlen);
-
-		freeaddrinfo(ai);
-	}
-
-	/* Use source port if provided on the command line,
-	 * otherwise use bindresvport */
-	if (opt_srcport) {
-		smn_set_port(local_addr, opt_srcport);
-		if (bind(sock, local_addr, sizeof(struct sockaddr_in)) < 0) {
-			nsm_log(LOG_ERR, "Failed to bind RPC socket: %s",
-				strerror(errno));
-			exit(1);
-		}
-	} else {
-		struct servent *se;
-		struct sockaddr_in *sin = (struct sockaddr_in *)local_addr;
-		(void) bindresvport(sock, sin);
-		/* try to avoid known ports */
-		se = getservbyport(sin->sin_port, "udp");
-		if (se && retry_cnt < 100) {
-			retry_cnt++;
-			close(sock);
-			goto retry;
-		}
-	}
 
 	if (opt_max_retry)
 		failtime = time(NULL) + opt_max_retry;
 
-	drop_privs();
-
 	while (hosts) {
 		struct pollfd	pfd;
 		time_t		now = time(NULL);
@@ -383,7 +513,7 @@ notify(void)
 		if (hosts == NULL)
 			return;
 
-		nsm_log(LOG_DEBUG, "Host %s due in %ld seconds",
+		xlog(D_GENERAL, "Host %s due in %ld seconds",
 				hosts->name, wait);
 
 		pfd.fd = sock;
@@ -405,34 +535,18 @@ notify(void)
 static int
 notify_host(int sock, struct nsm_host *host)
 {
-	struct sockaddr_storage address;
-	struct sockaddr *dest = (struct sockaddr *)&address;
-	socklen_t destlen = sizeof(address);
-	static unsigned int	xid = 0;
-	uint32_t		msgbuf[MAXMSGSIZE], *p;
-	unsigned int		len;
-
-	if (!xid)
-		xid = getpid() + time(NULL);
-	if (!host->xid)
-		host->xid = xid++;
+	struct sockaddr *sap;
+	socklen_t salen;
 
 	if (host->ai == NULL) {
 		host->ai = smn_lookup(host->name);
 		if (host->ai == NULL) {
-			nsm_log(LOG_WARNING,
-				"DNS resolution of %s failed; "
+			xlog_warn("DNS resolution of %s failed; "
 				"retrying later", host->name);
 			return 0;
 		}
 	}
 
-	memset(msgbuf, 0, sizeof(msgbuf));
-	p = msgbuf;
-	*p++ = htonl(host->xid);
-	*p++ = 0;
-	*p++ = htonl(2);
-
 	/* If we retransmitted 4 times, reset the port to force
 	 * a new portmap lookup (in case statd was restarted).
 	 * We also rotate through multiple IP addresses at this
@@ -440,10 +554,7 @@ notify_host(int sock, struct nsm_host *host)
 	 */
 	if (host->retries >= 4) {
 		/* don't rotate if there is only one addrinfo */
-		if (host->ai->ai_next == NULL)
-			memcpy(&host->addr, host->ai->ai_addr,
-						host->ai->ai_addrlen);
-		else {
+		if (host->ai->ai_next != NULL) {
 			struct addrinfo *first = host->ai;
 			struct addrinfo **next = &host->ai;
 
@@ -456,213 +567,100 @@ notify_host(int sock, struct nsm_host *host)
 				next = & (*next)->ai_next;
 			/* put first entry at end */
 			*next = first;
-			memcpy(&host->addr, first->ai_addr,
-						first->ai_addrlen);
 		}
 
-		smn_set_port((struct sockaddr *)&host->addr, 0);
+		nfs_set_port(host->ai->ai_addr, 0);
 		host->retries = 0;
 	}
 
-	memcpy(dest, &host->addr, destlen);
-	if (smn_get_port(dest) == 0) {
-		/* Build a PMAP packet */
-		nsm_log(LOG_DEBUG, "Sending portmap query to %s", host->name);
+	sap = host->ai->ai_addr;
+	salen = host->ai->ai_addrlen;
 
-		smn_set_port(dest, 111);
-		*p++ = htonl(100000);
-		*p++ = htonl(2);
-		*p++ = htonl(3);
-
-		/* Auth and verf */
-		*p++ = 0; *p++ = 0;
-		*p++ = 0; *p++ = 0;
-
-		*p++ = htonl(NSM_PROGRAM);
-		*p++ = htonl(NSM_VERSION);
-		*p++ = htonl(IPPROTO_UDP);
-		*p++ = 0;
-	} else {
-		/* Build an SM_NOTIFY packet */
-		nsm_log(LOG_DEBUG, "Sending SM_NOTIFY to %s", host->name);
-
-		*p++ = htonl(NSM_PROGRAM);
-		*p++ = htonl(NSM_VERSION);
-		*p++ = htonl(NSM_NOTIFY);
-
-		/* Auth and verf */
-		*p++ = 0; *p++ = 0;
-		*p++ = 0; *p++ = 0;
-
-		/* state change */
-		len = strlen(nsm_hostname);
-		*p++ = htonl(len);
-		memcpy(p, nsm_hostname, len);
-		p += (len + 3) >> 2;
-		*p++ = htonl(nsm_state);
-	}
-	len = (p - msgbuf) << 2;
-
-	if (sendto(sock, msgbuf, len, 0, dest, destlen) < 0)
-		nsm_log(LOG_WARNING, "Sending Reboot Notification to "
-			"'%s' failed: errno %d (%s)", host->name, errno, strerror(errno));
+	if (nfs_get_port(sap) == 0)
+		host->xid = nsm_xmit_rpcbind(sock, sap, SM_PROG, SM_VERS);
+	else
+		host->xid = nsm_xmit_notify(sock, sap, salen,
+				SM_PROG, nsm_hostname, nsm_state);
 	
 	return 0;
 }
 
 /*
- * Receive reply from remote host
+ * Extract the returned port number and set up the SM_NOTIFY call.
  */
 static void
-recv_reply(int sock)
+recv_rpcbind_reply(struct sockaddr *sap, struct nsm_host *host, XDR *xdr)
 {
-	struct nsm_host	*hp;
-	struct sockaddr *sap;
-	uint32_t	msgbuf[MAXMSGSIZE], *p, *end;
-	uint32_t	xid;
-	int		res;
-
-	res = recv(sock, msgbuf, sizeof(msgbuf), 0);
-	if (res < 0)
-		return;
-
-	nsm_log(LOG_DEBUG, "Received packet...");
+	uint16_t port = nsm_recv_rpcbind(sap->sa_family, xdr);
 
-	p = msgbuf;
-	end = p + (res >> 2);
-
-	xid = ntohl(*p++);
-	if (*p++ != htonl(1)	/* must be REPLY */
-	 || *p++ != htonl(0)	/* must be ACCEPTED */
-	 || *p++ != htonl(0)	/* must be NULL verifier */
-	 || *p++ != htonl(0)
-	 || *p++ != htonl(0))	/* must be SUCCESS */
-		return;
+	host->send_next = time(NULL);
+	host->xid = 0;
 
-	/* Before we look at the data, find the host struct for
-	   this reply */
-	if ((hp = find_host(xid)) == NULL)
-		return;
-	sap = (struct sockaddr *)&hp->addr;
-
-	if (smn_get_port(sap) == 0) {
-		/* This was a portmap request */
-		unsigned int	port;
-
-		port = ntohl(*p++);
-		if (p > end)
-			goto fail;
-
-		hp->send_next = time(NULL);
-		if (port == 0) {
-			/* No binding for statd. Delay the next
-			 * portmap query for max timeout */
-			nsm_log(LOG_DEBUG, "No statd on %s", hp->name);
-			hp->timeout = NSM_MAX_TIMEOUT;
-			hp->send_next += NSM_MAX_TIMEOUT;
-		} else {
-			smn_set_port(sap, port);
-			if (hp->timeout >= NSM_MAX_TIMEOUT / 4)
-				hp->timeout = NSM_MAX_TIMEOUT / 4;
-		}
-		hp->xid = 0;
+	if (port == 0) {
+		/* No binding for statd... */
+		xlog(D_GENERAL, "No statd on host %s", host->name);
+		host->timeout = NSM_MAX_TIMEOUT;
+		host->send_next += NSM_MAX_TIMEOUT;
 	} else {
-		/* Successful NOTIFY call. Server returns void,
-		 * so nothing we need to do here (except
-		 * check that we didn't read past the end of the
-		 * packet)
-		 */
-		if (p <= end) {
-			nsm_log(LOG_DEBUG, "Host %s notified successfully",
-					hp->name);
-			smn_forget_host(hp);
-			return;
-		}
+		nfs_set_port(sap, port);
+		if (host->timeout >= NSM_MAX_TIMEOUT / 4)
+			host->timeout = NSM_MAX_TIMEOUT / 4;
 	}
 
-fail:	/* Re-insert the host */
-	insert_host(hp);
+	insert_host(host);
 }
 
 /*
- * Back up all hosts from the sm directory to sm.bak
+ * Successful NOTIFY call. Server returns void, so nothing
+ * we need to do here.
  */
 static void
-backup_hosts(const char *dirname, const char *bakname)
+recv_notify_reply(struct nsm_host *host)
 {
-	struct dirent	*de;
-	DIR		*dir;
-
-	if (!(dir = opendir(dirname))) {
-		nsm_log(LOG_WARNING,
-			"Failed to open %s: %s", dirname, strerror(errno));
-		return;
-	}
+	xlog(D_GENERAL, "Host %s notified successfully", host->name);
 
-	while ((de = readdir(dir)) != NULL) {
-		char	src[1024], dst[1024];
-
-		if (de->d_name[0] == '.')
-			continue;
-
-		snprintf(src, sizeof(src), "%s/%s", dirname, de->d_name);
-		snprintf(dst, sizeof(dst), "%s/%s", bakname, de->d_name);
-		if (rename(src, dst) < 0) {
-			nsm_log(LOG_WARNING,
-				"Failed to rename %s -> %s: %m",
-				src, dst);
-		}
-	}
-	closedir(dir);
+	smn_forget_host(host);
 }
 
 /*
- * Get all entries from sm.bak and convert them to host entries
+ * Receive reply from remote host
  */
 static void
-get_hosts(const char *dirname)
+recv_reply(int sock)
 {
-	struct nsm_host	*host;
-	struct dirent	*de;
-	DIR		*dir;
+	struct nsm_host	*hp;
+	struct sockaddr *sap;
+	char msgbuf[NSM_MAXMSGSIZE];
+	uint32_t	xid;
+	ssize_t		msglen;
+	XDR		xdr;
 
-	if (!(dir = opendir(dirname))) {
-		nsm_log(LOG_WARNING,
-			"Failed to open %s: %s", dirname, strerror(errno));
+	memset(msgbuf, 0 , sizeof(msgbuf));
+	msglen = recv(sock, msgbuf, sizeof(msgbuf), 0);
+	if (msglen < 0)
 		return;
-	}
-
-	host = NULL;
-	while ((de = readdir(dir)) != NULL) {
-		struct stat	stb;
-		char		path[1024];
 
-		if (de->d_name[0] == '.')
-			continue;
-		if (host == NULL)
-			host = calloc(1, sizeof(*host));
-		if (host == NULL) {
-			nsm_log(LOG_WARNING, "Unable to allocate memory");
-			return;
-		}
+	xlog(D_GENERAL, "Received packet...");
 
-		snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
-		if (stat(path, &stb) < 0)
-			continue;
+	memset(&xdr, 0, sizeof(xdr));
+	xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE);
+	xid = nsm_parse_reply(&xdr);
+	if (xid == 0)
+		goto out;
 
-		host->last_used = stb.st_mtime;
-		host->timeout = NSM_TIMEOUT;
-		host->path = strdup(path);
-		host->name = strdup(de->d_name);
-		host->retries = 100; /* force address retry */
+	/* Before we look at the data, find the host struct for
+	   this reply */
+	if ((hp = find_host(xid)) == NULL)
+		goto out;
 
-		insert_host(host);
-		host = NULL;
-	}
-	closedir(dir);
+	sap = hp->ai->ai_addr;
+	if (nfs_get_port(sap) == 0)
+		recv_rpcbind_reply(sap, hp, &xdr);
+	else
+		recv_notify_reply(hp);
 
-	if (host)
-		free(host);
+out:
+	xdr_destroy(&xdr);
 }
 
 /*
@@ -712,84 +710,6 @@ find_host(uint32_t xid)
 	return NULL;
 }
 
-
-/*
- * Retrieve the current NSM state
- */
-static unsigned int
-nsm_get_state(int update)
-{
-	char		newfile[PATH_MAX];
-	int		fd, state;
-
-	if ((fd = open(_SM_STATE_PATH, O_RDONLY)) < 0) {
-		if (!opt_quiet) {
-			nsm_log(LOG_WARNING, "%s: %m", _SM_STATE_PATH);
-			nsm_log(LOG_WARNING, "Creating %s, set initial state 1",
-				_SM_STATE_PATH);
-		}
-		state = 1;
-		update = 1;
-	} else {
-		if (read(fd, &state, sizeof(state)) != sizeof(state)) {
-			nsm_log(LOG_WARNING,
-				"%s: bad file size, setting state = 1",
-				_SM_STATE_PATH);
-			state = 1;
-			update = 1;
-		} else {
-			if (!(state & 1))
-				state += 1;
-		}
-		close(fd);
-	}
-
-	if (update) {
-		state += 2;
-		snprintf(newfile, sizeof(newfile),
-				"%s.new", _SM_STATE_PATH);
-		if ((fd = open(newfile, O_CREAT|O_WRONLY, 0644)) < 0) {
-			nsm_log(LOG_ERR, "Cannot create %s: %m", newfile);
-			exit(1);
-		}
-		if (write(fd, &state, sizeof(state)) != sizeof(state)) {
-			nsm_log(LOG_ERR,
-				"Failed to write state to %s", newfile);
-			exit(1);
-		}
-		close(fd);
-		if (rename(newfile, _SM_STATE_PATH) < 0) {
-			nsm_log(LOG_ERR,
-				"Cannot create %s: %m", _SM_STATE_PATH);
-			exit(1);
-		}
-		sync();
-	}
-
-	return state;
-}
-
-/*
- * Log a message
- */
-static void
-nsm_log(int fac, const char *fmt, ...)
-{
-	va_list	ap;
-
-	if (fac == LOG_DEBUG && !opt_debug)
-		return;
-
-	va_start(ap, fmt);
-	if (log_syslog)
-		vsyslog(fac, fmt, ap);
-	else {
-		vfprintf(stderr, fmt, ap);
-		fputs("\n", stderr);
-	}
-	va_end(ap);
-}
-
 /*
  * Record pid in /var/run/sm-notify.pid
  * This file should remain until a reboot, even if the
@@ -799,61 +719,20 @@ nsm_log(int fac, const char *fmt, ...)
 static int record_pid(void)
 {
 	char pid[20];
+	ssize_t len;
 	int fd;
 
-	snprintf(pid, 20, "%d\n", getpid());
+	(void)snprintf(pid, sizeof(pid), "%d\n", (int)getpid());
 	fd = open("/var/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600);
 	if (fd < 0)
 		return 0;
-	if (write(fd, pid, strlen(pid)) != strlen(pid))  {
-		nsm_log(LOG_WARNING, "Writing to pid file failed: errno %d(%s)",
-			errno, strerror(errno));
-	}
-	close(fd);
-	return 1;
-}
 
-/* Drop privileges to match owner of state-directory
- * (in case a reply triggers some unknown bug).
- */
-static void drop_privs(void)
-{
-	struct stat st;
-
-	if (stat(_SM_DIR_PATH, &st) == -1 &&
-	    stat(_SM_BASE_PATH, &st) == -1) {
-		st.st_uid = 0;
-		st.st_gid = 0;
-	}
-
-	if (st.st_uid == 0) {
-		nsm_log(LOG_WARNING,
-			"sm-notify running as root. chown %s to choose different user",
-		    _SM_DIR_PATH);
-		return;
-	}
-
-	setgroups(0, NULL);
-	if (setgid(st.st_gid) == -1
-	    || setuid(st.st_uid) == -1) {
-		nsm_log(LOG_ERR, "Fail to drop privileges");
-		exit(1);
+	len = write(fd, pid, strlen(pid));
+	if ((len < 0) || ((size_t)len != strlen(pid))) {
+		xlog_warn("Writing to pid file failed: errno %d (%m)",
+				errno);
 	}
-}
 
-static void set_kernel_nsm_state(int state)
-{
-	int fd;
-	const char *file = "/proc/sys/fs/nfs/nsm_local_state";
-
-	fd = open(file ,O_WRONLY);
-	if (fd >= 0) {
-		char buf[20];
-		snprintf(buf, sizeof(buf), "%d", state);
-		if (write(fd, buf, strlen(buf)) != strlen(buf)) {
-			nsm_log(LOG_WARNING, "Writing to '%s' failed: errno %d (%s)",
-				file, errno, strerror(errno));
-		}
-		close(fd);
-	}
+	(void)close(fd);
+	return 1;
 }
diff --git a/utils/statd/sm-notify.man b/utils/statd/sm-notify.man
index dd03b8d..163713e 100644
--- a/utils/statd/sm-notify.man
+++ b/utils/statd/sm-notify.man
@@ -1,166 +1,315 @@
-.\"
-.\" sm-notify(8)
+.\"@(#)sm-notify.8"
 .\"
 .\" Copyright (C) 2004 Olaf Kirch <okir@suse.de>
-.TH sm-notify 8 "19 Mar 2007
+.\"
+.\" Rewritten by Chuck Lever <chuck.lever@oracle.com>, 2009.
+.\" Copyright 2009 Oracle.  All rights reserved.
+.\"
+.TH SM-NOTIFY 8 "1 November 2009
 .SH NAME
-sm-notify \- Send out NSM reboot notifications
+sm-notify \- send reboot notifications to NFS peers
 .SH SYNOPSIS
-.BI "/sbin/sm-notify [-dfq] [-m " time "] [-p " port "] [-P " path "] [-v " my_name " ]
+.BI "/usr/sbin/sm-notify [-dfn] [-m " minutes "] [-v " name "] [-p " notify-port "] [-P " path "]
 .SH DESCRIPTION
-File locking over NFS (v2 and v3) requires a facility to notify peers in
-case of a reboot, so that clients can reclaim locks after
-a server crash, and/or
-servers can release locks held by the rebooted client.
+File locks are not part of persistent file system state.
+Lock state is thus lost when a host reboots.
+.PP
+Network file systems must also detect when lock state is lost
+because a remote host has rebooted.
+After an NFS client reboots, an NFS server must release all file locks
+held by applications that were running on that client.
+After a server reboots, a client must remind the
+server of file locks held by applications running on that client.
 .PP
-This is a two-step process: during normal
-operations, a mechanism is required to keep track of which
-hosts need to be informed of a reboot. And of course,
-notifications need to be sent out during reboot.
-The protocol used for this is called NSM, for
-.IR "Network Status Monitor" .
+For NFS version 2 and version 3, the
+.I Network Status Monitor
+protocol (or NSM for short)
+is used to notify NFS peers of reboots.
+On Linux, two separate user-space components constitute the NSM service:
+.TP
+.B sm-notify
+A helper program that notifies NFS peers after the local system reboots
+.TP
+.B rpc.statd
+A daemon that listens for reboot notifications from other hosts, and
+manages the list of hosts to be notified when the local system reboots
 .PP
-This implementation separates these into separate program.
+The local NFS lock manager alerts its local
 .B rpc.statd
-tracks hosts which need to be notified and this
+of each remote peer that should be monitored.
+When the local system reboots, the
 .B sm-notify
-performs the notification.  When
+command notifies the NSM service on monitored peers of the reboot.
+When a remote reboots, that peer notifies the local
+.BR rpc.statd ,
+which in turn passes the reboot notification
+back to the local NFS lock manager.
+.SH NSM OPERATION IN DETAIL
+The first file locking interaction between an NFS client and server causes
+the NFS lock managers on both peers to contact their local NSM service to
+store information about the opposite peer.
+On Linux, the local lock manager contacts
+.BR rpc.statd .
+.PP
+.B rpc.statd
+records information about each monitored NFS peer on persistent storage.
+This information describes how to contact a remote peer
+in case the local system reboots,
+how to recognize which monitored peer is reporting a reboot,
+and how to notify the local lock manager when a monitored peer
+indicates it has rebooted.
+.PP
+An NFS client sends a hostname, known as the client's
+.IR caller_name ,
+in each file lock request.
+An NFS server can use this hostname to send asynchronous GRANT
+calls to a client, or to notify the client it has rebooted.
+.PP
+The Linux NFS server can provide the client's
+.I caller_name
+or the client's network address to
+.BR rpc.statd .
+For the purposes of the NSM protocol,
+this name or address is known as the monitored peer's
+.IR mon_name .
+In addition, the local lock manager tells
 .B rpc.statd
-is started it will typically started
+what it thinks its own hostname is.
+For the purposes of the NSM protocol,
+this hostname is known as
+.IR my_name .
+.PP
+There is no equivalent interaction between an NFS server and a client
+to inform the client of the server's
+.IR caller_name .
+Therefore NFS clients do not actually know what
+.I mon_name
+an NFS server might use in an SM_NOTIFY request.
+The Linux NFS client records the server's hostname used on the mount command
+to identify rebooting NFS servers.
+.SS Reboot notification
+When the local system reboots, the
+.B sm-notify
+command reads the list of monitored peers from persistent storage and
+sends an SM_NOTIFY request to the NSM service on each listed remote peer.
+It uses the
+.I mon_name
+string as the destination.
+To identify which host has rebooted, the
 .B sm-notify
-but this is configurable.
-.SS Operation
-For each NFS client or server machine to be monitored,
+command normally sends the results of
+.BR gethostname (3)
+as the
+.I my_name
+string.
+The remote
 .B rpc.statd
-creates a file in
-.BR /var/lib/nfs/sm ", "
-and removes the file if monitoring is no longer required.
+matches incoming SM_NOTIFY requests using this string,
+or the caller's network address,
+to one or more peers on its own monitor list.
 .PP
-When the machine is rebooted,
+If
+.B rpc.statd
+does not find a peer on its monitor list that matches
+an incoming SM_NOTIFY request,
+the notification is not forwarded to the local lock manager.
+In addition, each peer has its own
+.IR "NSM state number" ,
+a 32-bit integer that is bumped after each reboot by the
 .B sm-notify
-iterates through these files and notifies the peer
-.B statd
-server on those machines.
+command.
+.B rpc.statd
+uses this number to distinguish between actual reboots
+and replayed notifications.
 .PP
-Each machine has an
-.I "NSM state" ,
-which is basically an integer counter that is incremented
-each time the machine reboots. This counter is stored
-in
-.BR /var/lib/nfs/state ,
-and updated by
-.BR sm-notify .
-.SS Security
-.B sm-notify
-has little need for root privileges and so drops them as soon as
-possible.
-It continues to need to make changes to the
-.B sm
-and
-.B sm.bak
-directories so to be able to drop privileges, these must be writable
-by a non-privileged user.  If these directories are owned by a
-non-root user,
-.B sm-notify
-will drop privilege to match that user once it has created sockets for
-sending out request (for which it needs privileged) but before it
-processes any reply (which is the most likely source of possible
-privilege abuse).
+Part of NFS lock recovery is rediscovering
+which peers need to be monitored again.
+The
+.B sm-notify
+command clears the monitor list on persistent storage after each reboot.
 .SH OPTIONS
 .TP
-.BI -m " failtime
-When notifying hosts,
+.B -d
+Keeps
+.B sm-notify
+attached to its controlling terminal and running in the foreground
+so that notification progress may be monitored directly.
+.TP
+.B -f
+Send notifications even if
+.B sm-notify
+has already run since the last system reboot.
+.TP
+.BI -m " retry-time
+Specifies the length of time, in minutes, to continue retrying
+notifications to unresponsive hosts.
+If this option is not specified,
 .B sm-notify
-will try to contact each host for up to 15 minutes,
-and will give up if unable to reach it within this time
-frame.
+attempts to send notifications for 15 minutes.
+Specifying a value of 0 causes
+.B sm-notify
+to continue sending notifications to unresponsive peers
+until it is manually killed.
+.IP
+Notifications are retried if sending fails,
+the remote does not respond,
+the remote's NSM service is not registered,
+or if there is a DNS failure
+which prevents the remote's
+.I mon_name
+from being resolved to an address.
 .IP
-Using the
-.B -m
-option, you can override this. A value of 0 tells
-sm-notify to retry indefinitely; any other value is
-interpreted as the maximum retry time in minutes.
+Hosts are not removed from the notification list until a valid
+reply has been received.
+However, the SM_NOTIFY procedure has a void result.
+There is no way for
+.B sm-notify
+to tell if the remote recognized the sender and has started
+appropriate lock recovery.
 .TP
-.BI -v " ipaddr-or-hostname
-This option tells
-.B sm-notify
-to bind to the specified
-.IR ipaddr ,
-(or the ipaddr of the given
-.IR hostname )
-so that all notification packets originate from this address.
-This is useful for NFS failover.  The given name is also used as the
-.I name
-of this host in the NSM request.
+.B -n
+Prevents
+.B sm-notify
+from updating the local system's NSM state number.
 .TP
 .BI -p " port
-instructs
+Specifies the source port number
 .B sm-notify
-to bind to the indicated IP
-.IR port
-number. If this option is not given, it will try to bind to
-a randomly chosen privileged port below 1024.
-.TP
-.B -q
-Be quiet. This suppresses all messages except error
-messages while collecting the list of hosts.
+should use when sending reboot notifications.
+If this option is not specified, a randomly chosen ephemeral port is used.
+.IP
+This option can be used to traverse a firewall between client and server.
 .TP
-.BI -P " /path/to/state/directory
-If
+.BI "\-P, " "" \-\-state\-directory\-path " pathname
+Specifies the pathname of the parent directory
+where NSM state information resides.
+If this option is not specified,
+.B sm-notify
+uses
+.I /var/lib/nfs
+by default.
+.IP
+After starting,
 .B sm-notify
-should look in a no-standard place of state file, the path can be
-given here.  The directories
-.B sm
-and
-.B sm.bak
-and the file
-.B state
-must exist in that directory with the standard names.
+attempts to set its effective UID and GID to the owner
+and group of this directory.
 .TP
-.B -f
-If the state path has not been reset with
-.BR -P ,
+.BI -v " ipaddr " | " hostname
+Specifies the network address from which to send reboot notifications,
+and the
+.I mon_name
+argument to use when sending SM_NOTIFY requests.
+If this option is not specified,
 .B sm-notify
-will normally create a file in
-.B /var/run
-to indicate that it has been
-run.  If this file is found when
+uses a wildcard address as the transport bind address,
+and uses the results of
+.BR gethostname (3)
+as the
+.I mon_name
+argument.
+.IP
+The
+.I ipaddr
+form can be expressed as either an IPv4 or an IPv6 presentation address.
+.IP
+This option can be useful in multi-homed configurations where
+the remote requires notification from a specific network address.
+.SH SECURITY
+The
 .B sm-notify
-starts, it will not run again (as it is normally only needed once per
-reboot).
-If
-.B -f
-(for
-.BR force )
-is given,
+command must be started as root to acquire privileges needed
+to access the state information database.
+It drops root privileges
+as soon as it starts up to reduce the risk of a privilege escalation attack.
+.PP
+During normal operation,
+the effective user ID it chooses is the owner of the state directory.
+This allows it to continue to access files in that directory after it
+has dropped its root privileges.
+To control which user ID
+.B rpc.statd
+chooses, simply use
+.BR chown (1)
+to set the owner of
+the state directory.
+.SH ADDITIONAL NOTES
+Lock recovery after a reboot is critical to maintaining data integrity
+and preventing unnecessary application hangs.
+.PP
+To help
+.B rpc.statd
+match SM_NOTIFY requests to NLM requests, a number of best practices
+should be observed, including:
+.IP
+The UTS nodename of your systems should match the DNS names that NFS
+peers use to contact them
+.IP
+The UTS nodenames of your systems should always be fully qualified domain names
+.IP
+The forward and reverse DNS mapping of the UTS nodenames should be
+consistent
+.IP
+The hostname the client uses to mount the server should match the server's
+.I mon_name
+in SM_NOTIFY requests it sends
+.IP
+The use of network addresses as a
+.I mon_name
+or a
+.I my_name
+string should be avoided when
+interoperating with non-Linux NFS implementations.
+.PP
+Unmounting an NFS file system does not necessarily stop
+either the NFS client or server from monitoring each other.
+Both may continue monitoring each other for a time in case subsequent
+NFS traffic between the two results in fresh mounts and additional
+file locking.
+.PP
+On Linux, if the
+.B lockd
+kernel module is unloaded during normal operation,
+all remote NFS peers are unmonitored.
+This can happen on an NFS client, for example,
+if an automounter removes all NFS mount
+points due to inactivity.
+.SS IPv6 and TI-RPC support
+TI-RPC is a pre-requisite for supporting NFS on IPv6.
+If TI-RPC support is built into the
 .B sm-notify
-will run even if the file in
-.B /var/run
-is present.
-.TP
-.B -n
-Do not update the NSM state. This is for testing only.  Setting this
-flag implies
-.BR -f .
-.TP
-.B -d
-Enables debugging.
-By default,
+command ,it will choose an appropriate IPv4 or IPv6 transport
+based on the network address returned by DNS for each remote peer.
+It should be fully compatible with remote systems
+that do not support TI-RPC or IPv6.
+.PP
+Currently, the
 .B sm-notify
-forks and puts itself in the background after obtaining the
-list of hosts from
-.BR /var/lib/nfs/sm .
+command supports sending notification only via datagram transport protocols.
 .SH FILES
-.BR /var/lib/nfs/state
-.br
-.BR /var/lib/nfs/sm/*
+.TP 2.5i
+.I /var/lib/nfs/sm
+directory containing monitor list
+.TP 2.5i
+.I /var/lib/nfs/sm.bak
+directory containing notify list
+.TP 2.5i
+.I /var/lib/nfs/state
+NSM state number for this host
+.TP 2.5i
+.I /proc/sys/fs/nfs/nsm_local_state
+kernel's copy of the NSM state number
+.SH SEE ALSO
+.BR rpc.statd (8),
+.BR nfs (5),
+.BR uname (2),
+.BR hostname (7)
+.PP
+RFC 1094 - "NFS: Network File System Protocol Specification"
 .br
-.BR /var/lib/nfs/sm.bak/*
+RFC 1813 - "NFS Version 3 Protocol Specification"
 .br
-.BR /var/run/sm-notify.pid
-.SH SEE ALSO
-.BR rpc.nfsd(8),
-.BR portmap(8)
+OpenGroup Protocols for Interworking: XNFS, Version 3W - Chapter 11
 .SH AUTHORS
-.br
 Olaf Kirch <okir@suse.de>
+.br
+Chuck Lever <chuck.lever@oracle.com>
diff --git a/utils/statd/sm_inter.x b/utils/statd/sm_inter.x
deleted file mode 100644
index d8e0ad7..0000000
--- a/utils/statd/sm_inter.x
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 1986 Sun Microsystems, Inc.
- * Modified by Jeffrey A. Uphoff, 1995, 1997-1999.
- * Modified by Olaf Kirch, 1996.
- * Modified by H.J. Lu, 1998.
- *
- * NSM for Linux.
- */
-
-/*
- * Copyright (c) 2009, Sun Microsystems, Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * - Redistributions of source code must retain the above copyright notice,
- *   this list of conditions and the following disclaimer.
- * - 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.
- * - Neither the name of Sun Microsystems, Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
- */
-
-/*
- * Status monitor protocol specification
- */
-
-#ifdef RPC_CLNT
-%#include <string.h>
-#endif
-
-program SM_PROG { 
-	version SM_VERS  {
-		/* res_stat = stat_succ if status monitor agrees to monitor */
-		/* res_stat = stat_fail if status monitor cannot monitor */
-		/* if res_stat == stat_succ, state = state number of site sm_name */
-		struct sm_stat_res			 SM_STAT(struct sm_name) = 1;
-
-		/* res_stat = stat_succ if status monitor agrees to monitor */
-		/* res_stat = stat_fail if status monitor cannot monitor */
-		/* stat consists of state number of local site */
-		struct sm_stat_res			 SM_MON(struct mon) = 2;
-
-		/* stat consists of state number of local site */
-		struct sm_stat				 SM_UNMON(struct mon_id) = 3;
-
-		/* stat consists of state number of local site */
-		struct sm_stat				 SM_UNMON_ALL(struct my_id) = 4;
-
-		void					 SM_SIMU_CRASH(void) = 5;
-
-		void					 SM_NOTIFY(struct stat_chge) = 6;
-
-	} = 1;
-} = 100024;
-
-const	SM_MAXSTRLEN = 1024;
-const	SM_PRIV_SIZE = 16;
-
-struct sm_name {
-	string mon_name<SM_MAXSTRLEN>;
-};
-
-struct my_id {
-	string	 my_name<SM_MAXSTRLEN>;		/* name of the site iniates the monitoring request*/
-	int	my_prog;			/* rpc program # of the requesting process */
-	int	my_vers;			/* rpc version # of the requesting process */
-	int	my_proc;			/* rpc procedure # of the requesting process */
-};
-
-struct mon_id {
-	string	mon_name<SM_MAXSTRLEN>;		/* name of the site to be monitored */
-	struct my_id my_id;
-};
-
-
-struct mon {
-	struct mon_id mon_id;
-	opaque priv[SM_PRIV_SIZE]; 		/* private information to store at monitor for requesting process */
-};
-
-struct stat_chge {
-	string	mon_name<SM_MAXSTRLEN>;		/* name of the site that had the state change */
-	int     state;
-};
-
-/*
- * state # of status monitor monitonically increases each time
- * status of the site changes:
- * an even number (>= 0) indicates the site is down and
- * an odd number (> 0) indicates the site is up;
- */
-struct sm_stat {
-	int state;		/* state # of status monitor */
-};
-
-enum res {
-	stat_succ = 0,		/* status monitor agrees to monitor */
-	stat_fail = 1		/* status monitor cannot monitor */
-};
-
-struct sm_stat_res {
-	res res_stat;
-	int state;
-};
-
-/* 
- * structure of the status message sent back by the status monitor
- * when monitor site status changes
- */
-struct status {
-	string mon_name<SM_MAXSTRLEN>;
-	int state;
-	opaque priv[SM_PRIV_SIZE]; /* stored private information */
-};
-
-%#define SM_INTER_X
diff --git a/utils/statd/stat.c b/utils/statd/stat.c
index 799239f..8d8b65e 100644
--- a/utils/statd/stat.c
+++ b/utils/statd/stat.c
@@ -12,7 +12,7 @@
 #include <netdb.h>
 #include "statd.h"
 
-/* 
+/*
  * Services SM_STAT requests.
  *
  * According the the X/Open spec's on this procedure: "Implementations
@@ -37,18 +37,23 @@
  *      other way to get it.
  *   3/ That's what we always did in the past.
  */
-struct sm_stat_res * 
-sm_stat_1_svc (struct sm_name *argp, struct svc_req *rqstp)
+struct sm_stat_res *
+sm_stat_1_svc(struct sm_name *argp,
+		__attribute__ ((unused)) struct svc_req *rqstp)
 {
   static sm_stat_res result;
+  char *name;
+
+  xlog(D_CALL, "Received SM_STAT from %s", argp->mon_name);
 
-  if (gethostbyname (argp->mon_name) == NULL) {
-    note (N_WARNING, "gethostbyname error for %s", argp->mon_name);
+  name = statd_canonical_name(argp->mon_name);
+  if (name == NULL) {
     result.res_stat = STAT_FAIL;
-    dprintf (N_DEBUG, "STAT_FAIL for %s", argp->mon_name);
+    xlog (D_GENERAL, "STAT_FAIL for %s", argp->mon_name);
   } else {
     result.res_stat = STAT_SUCC;
-    dprintf (N_DEBUG, "STAT_SUCC for %s", argp->mon_name);
+    xlog (D_GENERAL, "STAT_SUCC for %s", argp->mon_name);
+    free(name);
   }
   result.state = MY_STATE;
   return(&result);
diff --git a/utils/statd/statd.c b/utils/statd/statd.c
index 1c5247e..01fdb41 100644
--- a/utils/statd/statd.c
+++ b/utils/statd/statd.c
@@ -25,33 +25,21 @@
 #include <sys/resource.h>
 #include <sys/wait.h>
 #include <grp.h>
+
 #include "statd.h"
-#include "version.h"
 #include "nfslib.h"
+#include "nsm.h"
 
 /* Socket operations */
 #include <sys/types.h>
 #include <sys/socket.h>
 
-/* Added to enable specification of state directory path at run-time
- * j_carlos_gomez@yahoo.com
- */
-
-char * DIR_BASE = DEFAULT_DIR_BASE;
-
-char *  SM_DIR = DEFAULT_SM_DIR;
-char *  SM_BAK_DIR =  DEFAULT_SM_BAK_DIR;
-char *  SM_STAT_PATH = DEFAULT_SM_STAT_PATH;
-
-/* ----- end of state directory path stuff ------- */
-
 int	run_mode = 0;		/* foreground logging mode */
 
 /* LH - I had these local to main, but it seemed silly to have 
  * two copies of each - one in main(), one static in log.c... 
  * It also eliminates the 256-char static in log.c */
-char *name_p = NULL;
-const char *version_p = NULL;
+static char *name_p = NULL;
 
 /* PRC: a high-availability callout program can be specified with -H
  * When this is done, the program will receive callouts whenever clients
@@ -75,7 +63,6 @@ static struct option longopts[] =
 };
 
 extern void sm_prog_1 (struct svc_req *, register SVCXPRT *);
-static void load_state_number(void);
 
 #ifdef SIMULATIONS
 extern void simulator (int, char **);
@@ -88,11 +75,8 @@ extern void simulator (int, char **);
 static void 
 sm_prog_1_wrapper (struct svc_req *rqstp, register SVCXPRT *transp)
 {
-	struct sockaddr_in *sin = nfs_getrpccaller_in(transp);
-
 	/* remote host authorization check */
-	if (sin->sin_family == AF_INET &&
-	    !check_default("statd", sin, rqstp->rq_proc, SM_PROG)) {
+	if (!check_default("statd", nfs_getrpccaller(transp), SM_PROG)) {
 		svcerr_auth (transp, AUTH_FAILED);
 		return;
 	}
@@ -103,23 +87,26 @@ sm_prog_1_wrapper (struct svc_req *rqstp, register SVCXPRT *transp)
 #define sm_prog_1 sm_prog_1_wrapper
 #endif
 
+static void
+statd_unregister(void) {
+	nfs_svc_unregister(SM_PROG, SM_VERS);
+}
+
 /*
  * Signal handler.
  */
 static void 
 killer (int sig)
 {
-	note (N_FATAL, "Caught signal %d, un-registering and exiting.", sig);
-	pmap_unset (SM_PROG, SM_VERS);
-
-	exit (0);
+	statd_unregister ();
+	xlog_err ("Caught signal %d, un-registering and exiting", sig);
 }
 
 static void
 sigusr (int sig)
 {
 	extern void my_svc_exit (void);
-	dprintf (N_DEBUG, "Caught signal %d, re-notifying (state %d).", sig,
+	xlog(D_GENERAL, "Caught signal %d, re-notifying (state %d)", sig,
 								MY_STATE);
 	my_svc_exit();
 }
@@ -140,8 +127,11 @@ static void log_modes(void)
 		strcat(buf,"No-Daemon ");
 	if (run_mode & MODE_LOG_STDERR)
 		strcat(buf,"Log-STDERR ");
+#ifdef HAVE_LIBTIRPC
+	strcat(buf, "TI-RPC ");
+#endif
 
-	note(N_WARNING,buf);
+	xlog_warn(buf);
 }
 
 /*
@@ -175,13 +165,12 @@ static void create_pidfile(void)
 	unlink(pidfile);
 	fp = fopen(pidfile, "w");
 	if (!fp)
-		die("Opening %s failed: %s\n",
-		    pidfile, strerror(errno));
+		xlog_err("Opening %s failed: %m\n", pidfile);
 	fprintf(fp, "%d\n", getpid());
 	pidfd = dup(fileno(fp));
 	if (fclose(fp) < 0) {
-		note(N_WARNING, "Flushing pid file failed: errno %d (%s)\n",
-			errno, strerror(errno));
+		xlog_warn("Flushing pid file failed: errno %d (%m)\n",
+			errno);
 	}
 }
 
@@ -189,42 +178,10 @@ static void truncate_pidfile(void)
 {
 	if (pidfd >= 0) {
 		if (ftruncate(pidfd, 0) < 0) {
-			note(N_WARNING, "truncating pid file failed: errno %d (%s)\n",
-				errno, strerror(errno));
-		}
-	}
-}
-
-static void drop_privs(void)
-{
-	struct stat st;
-
-	if (stat(SM_DIR, &st) == -1 &&
-	    stat(DIR_BASE, &st) == -1) {
-		st.st_uid = 0;
-		st.st_gid = 0;
-	}
-
-	if (st.st_uid == 0) {
-		note(N_WARNING, "statd running as root. chown %s to choose different user\n",
-		    SM_DIR);
-		return;
-	}
-	/* better chown the pid file before dropping, as if it
-	 * if over nfs we might loose access
-	 */
-	if (pidfd >= 0) {
-		if (fchown(pidfd, st.st_uid, st.st_gid) < 0) {
-			note(N_ERROR, "Unable to change owner of %s: %d (%s)",
-					SM_DIR, strerror (errno));
+			xlog_warn("truncating pid file failed: errno %d (%m)\n",
+				errno);
 		}
 	}
-	setgroups(0, NULL);
-	if (setgid(st.st_gid) == -1
-	    || setuid(st.st_uid) == -1) {
-		note(N_ERROR, "Fail to drop privileges");
-		exit(1);
-	}
 }
 
 static void run_sm_notify(int outport)
@@ -266,6 +223,8 @@ int main (int argc, char **argv)
 
 	/* Default: daemon mode, no other options */
 	run_mode = 0;
+	xlog_stderr(0);
+	xlog_syslog(1);
 
 	/* Set the basename */
 	if ((name_p = strrchr(argv[0],'/')) != NULL) {
@@ -274,13 +233,6 @@ int main (int argc, char **argv)
 		name_p = argv[0];
 	}
 
-	/* Get the version */
-	if ((version_p = strrchr(VERSION,' ')) != NULL) {
-		version_p++;
-	} else {
-		version_p = VERSION;
-	}
-	
 	/* Set hostname */
 	MY_NAME = NULL;
 
@@ -289,7 +241,7 @@ int main (int argc, char **argv)
 		switch (arg) {
 		case 'V':	/* Version */
 		case 'v':
-			printf("%s version %s\n",name_p,version_p);
+			printf("%s version " VERSION "\n",name_p);
 			exit(0);
 		case 'F':	/* Foreground/nodaemon mode */
 			run_mode |= MODE_NODAEMON;
@@ -326,34 +278,8 @@ int main (int argc, char **argv)
 			MY_NAME = xstrdup(optarg);
 			break;
 		case 'P':
-
-			if ((DIR_BASE = xstrdup(optarg)) == NULL) {
-				fprintf(stderr, "%s: xstrdup(%s) failed!\n",
-					argv[0], optarg);
-				exit(1);
-			}
-
-			SM_DIR = xmalloc(strlen(DIR_BASE) + 1 + sizeof("sm"));
-			SM_BAK_DIR = xmalloc(strlen(DIR_BASE) + 1 + sizeof("sm.bak"));
-			SM_STAT_PATH = xmalloc(strlen(DIR_BASE) + 1 + sizeof("state"));
-
-			if ((SM_DIR == NULL) 
-			    || (SM_BAK_DIR == NULL) 
-			    || (SM_STAT_PATH == NULL)) {
-
-				fprintf(stderr, "%s: xmalloc() failed!\n",
-					argv[0]);
+			if (!nsm_setup_pathnames(argv[0], optarg))
 				exit(1);
-			}
-			if (DIR_BASE[strlen(DIR_BASE)-1] == '/') {
-				sprintf(SM_DIR, "%ssm", DIR_BASE );
-				sprintf(SM_BAK_DIR, "%ssm.bak", DIR_BASE );
-				sprintf(SM_STAT_PATH, "%sstate", DIR_BASE );
-			} else {
-				sprintf(SM_DIR, "%s/sm", DIR_BASE );
-				sprintf(SM_BAK_DIR, "%s/sm.bak", DIR_BASE );
-				sprintf(SM_STAT_PATH, "%s/state", DIR_BASE );
-			}
 			break;
 		case 'H': /* PRC: specify the ha-callout program */
 			if ((ha_callout_prog = xstrdup(optarg)) == NULL) {
@@ -383,7 +309,6 @@ int main (int argc, char **argv)
 		run_sm_notify(out_port);
 	}
 
-
 	if (!(run_mode & MODE_NODAEMON)) {
 		run_mode &= ~MODE_LOG_STDERR;	/* Never log to console in
 						   daemon mode. */
@@ -432,10 +357,6 @@ int main (int argc, char **argv)
 		/* Child.	*/
 		close(pipefds[0]);
 		setsid ();
-		if (chdir (DIR_BASE) == -1) {
-			perror("statd: Could not chdir");
-			exit(1);
-		}
 
 		while (pipefds[1] <= 2) {
 			pipefds[1] = dup(pipefds[1]);
@@ -455,7 +376,13 @@ int main (int argc, char **argv)
 
 	/* Child. */
 
-	log_init (/*name_p,version_p*/);
+	if (run_mode & MODE_LOG_STDERR) {
+		xlog_syslog(0);
+		xlog_stderr(1);
+		xlog_config(D_ALL, 1);
+	}
+	xlog_open(name_p);
+	xlog(L_NOTICE, "Version " VERSION " starting");
 
 	log_modes();
 
@@ -495,25 +422,48 @@ int main (int argc, char **argv)
 	 * pass on any SM_NOTIFY that arrives
 	 */
 	load_state();
-	load_state_number();
-	pmap_unset (SM_PROG, SM_VERS);
 
-	/* this registers both UDP and TCP services */
-	rpc_init("statd", SM_PROG, SM_VERS, sm_prog_1, port);
+	MY_STATE = nsm_get_state(0);
+	if (MY_STATE == 0)
+		exit(1);
+	xlog(D_GENERAL, "Local NSM state number: %d", MY_STATE);
+	nsm_update_kernel_state(MY_STATE);
+
+	/*
+	 * ORDER
+	 * Clear old listeners while still root, to override any
+	 * permission checking done by rpcbind.
+	 */
+	statd_unregister();
+
+	/*
+	 * ORDER
+	 */
+	if (!nsm_drop_privileges(pidfd))
+		exit(1);
+
+	/*
+	 * ORDER
+	 * Create RPC listeners after dropping privileges.  This permits
+	 * statd to unregister its own listeners when it exits.
+	 */
+	if (nfs_svc_create("statd", SM_PROG, SM_VERS, sm_prog_1, port) == 0) {
+		xlog(L_ERROR, "failed to create RPC listeners, exiting");
+		exit(1);
+	}
+	atexit(statd_unregister);
 
 	/* If we got this far, we have successfully started, so notify parent */
 	if (pipefds[1] > 0) {
 		status = 0;
 		if (write(pipefds[1], &status, 1) != 1) {
-			note(N_WARNING, "writing to parent pipe failed: errno %d (%s)\n",
+			xlog_warn("writing to parent pipe failed: errno %d (%s)\n",
 				errno, strerror(errno));
 		}
 		close(pipefds[1]);
 		pipefds[1] = -1;
 	}
 
-	drop_privs();
-
 	for (;;) {
 		/*
 		 * Handle incoming requests:  SM_NOTIFY socket requests, as
@@ -541,29 +491,3 @@ int main (int argc, char **argv)
 	}
 	return 0;
 }
-
-static void
-load_state_number(void)
-{
-	int fd;
-	const char *file = "/proc/sys/fs/nfs/nsm_local_state";
-
-	if ((fd = open(SM_STAT_PATH, O_RDONLY)) == -1)
-		return;
-
-	if (read(fd, &MY_STATE, sizeof(MY_STATE)) != sizeof(MY_STATE)) {
-		note(N_WARNING, "Unable to read state from '%s': errno %d (%s)",
-				SM_STAT_PATH, errno, strerror(errno));
-	}
-	close(fd);
-	fd = open(file, O_WRONLY);
-	if (fd >= 0) {
-		char buf[20];
-		snprintf(buf, sizeof(buf), "%d", MY_STATE);
-		if (write(fd, buf, strlen(buf)) != strlen(buf))
-			note(N_WARNING, "Writing to '%s' failed: errno %d (%s)",
-				file, errno, strerror(errno));
-		close(fd);
-	}
-
-}
diff --git a/utils/statd/statd.h b/utils/statd/statd.h
index 88ba208..e89e666 100644
--- a/utils/statd/statd.h
+++ b/utils/statd/statd.h
@@ -11,29 +11,7 @@
 
 #include "sm_inter.h"
 #include "system.h"
-#include "log.h"
-
-/*
- * Paths and filenames.
- */
-#if defined(NFS_STATEDIR)
-# define DEFAULT_DIR_BASE	NFS_STATEDIR "/"
-#else
-# define DEFAULT_DIR_BASE	"/var/lib/nfs/"
-#endif
-
-#define DEFAULT_SM_DIR		DEFAULT_DIR_BASE "sm"
-#define DEFAULT_SM_BAK_DIR	DEFAULT_DIR_BASE "sm.bak"
-#define DEFAULT_SM_STAT_PATH	DEFAULT_DIR_BASE "state"
-
-/* Added to support run-time specification of state directory path.
- * j_carlos_gomez@yahoo.com
- */
-
-extern char * DIR_BASE;
-extern char *  SM_DIR;
-extern char *  SM_BAK_DIR;
-extern char *  SM_STAT_PATH;
+#include "xlog.h"
 
 /*
  * Status definitions.
@@ -44,7 +22,12 @@ extern char *  SM_STAT_PATH;
 /*
  * Function prototypes.
  */
-extern void	change_state(void);
+extern _Bool	statd_matchhostname(const char *hostname1, const char *hostname2);
+extern _Bool	statd_present_address(const struct sockaddr *sap, char *buf,
+					const size_t buflen);
+__attribute_malloc__
+extern char *	statd_canonical_name(const char *hostname);
+
 extern void	my_svc_run(void);
 extern void	notify_hosts(void);
 extern void	shuffle_dirs(void);
@@ -53,7 +36,6 @@ extern int	process_notify_list(void);
 extern int	process_reply(FD_SET_TYPE *);
 extern char *	xstrdup(const char *);
 extern void *	xmalloc(size_t);
-extern void	xunlink (char *, char *);
 extern void	load_state(void);
 
 /*
@@ -84,10 +66,3 @@ extern int run_mode;
  * another host.... */
 #define STATIC_HOSTNAME 8	/* Always use the hostname set by -n */
 #define	MODE_NO_NOTIFY	16	/* Don't notify peers of a reboot */
-/*
- * Program name and version pointers -- See statd.c for the reasoning
- * as to why they're global.
- */
-extern char *name_p;		/* program basename */
-extern const char *version_p;	/* program version */
-
diff --git a/utils/statd/statd.man b/utils/statd/statd.man
index e8be9f3..ffc5e95 100644
--- a/utils/statd/statd.man
+++ b/utils/statd/statd.man
@@ -1,191 +1,400 @@
-.\"
-.\" statd(8)
+.\"@(#)rpc.statd.8"
 .\"
 .\" Copyright (C) 1999 Olaf Kirch <okir@monad.swb.de>
 .\" Modified by Jeffrey A. Uphoff, 1999, 2002, 2005.
 .\" Modified by Lon Hohberger, 2000.
 .\" Modified by Paul Clements, 2004.
-.TH rpc.statd 8 "31 Aug 2004"
+.\"
+.\" Rewritten by Chuck Lever <chuck.lever@oracle.com>, 2009.
+.\" Copyright 2009 Oracle.  All rights reserved.
+.\"
+.TH RPC.STATD 8 "1 November 2009
 .SH NAME
-rpc.statd \- NSM status monitor
+rpc.statd \- NSM service daemon
 .SH SYNOPSIS
-.B "rpc.statd [-FNL] [-d] [-?] [-n " name "] [-o " port "] [-p " port "] [-H " prog "] [-V]"
+.BI "rpc.statd [-dh?FLNvVw] [-H " prog "] [-n " my-name "] [-o " outgoing-port "] [-p " listener-port "] [-P " path " ]
 .SH DESCRIPTION
-The
+File locks are not part of persistent file system state.
+Lock state is thus lost when a host reboots.
+.PP
+Network file systems must also detect when lock state is lost
+because a remote host has rebooted.
+After an NFS client reboots, an NFS server must release all file locks
+held by applications that were running on that client.
+After a server reboots, a client must remind the
+server of file locks held by applications running on that client.
+.PP
+For NFS version 2 [RFC1094] and NFS version 3 [RFC1813], the
+.I Network Status Monitor
+protocol (or NSM for short)
+is used to notify NFS peers of reboots.
+On Linux, two separate user-space components constitute the NSM service:
+.TP
 .B rpc.statd
-server implements the NSM (Network Status Monitor) RPC protocol.
-This service is somewhat misnamed, since it doesn't actually provide
-active monitoring as one might suspect; instead, NSM implements a
-reboot notification service. It is used by the NFS file locking service,
-.BR rpc.lockd ,
-to implement lock recovery when the NFS server machine crashes and
-reboots.
-.SS Operation
-For each NFS client or server machine to be monitored,
-.B rpc.statd
-creates a file in
-.BR /var/lib/nfs/sm .
-When starting, it normally runs
+A daemon that listens for reboot notifications from other hosts, and
+manages the list of hosts to be notified when the local system reboots
+.TP
+.B sm-notify
+A helper program that notifies NFS peers after the local system reboots
+.PP
+The local NFS lock manager alerts its local
+.B rpc.statd
+of each remote peer that should be monitored.
+When the local system reboots, the
 .B sm-notify
-to iterate through these files and notify the
-peer
+command notifies the NSM service on monitored peers of the reboot.
+When a remote reboots, that peer notifies the local
+.BR rpc.statd ,
+which in turn passes the reboot notification
+back to the local NFS lock manager.
+.SH NSM OPERATION IN DETAIL
+The first file locking interaction between an NFS client and server causes
+the NFS lock managers on both peers to contact their local NSM service to
+store information about the opposite peer.
+On Linux, the local lock manager contacts
+.BR rpc.statd .
+.PP
+.B rpc.statd
+records information about each monitored NFS peer on persistent storage.
+This information describes how to contact a remote peer
+in case the local system reboots,
+how to recognize which monitored peer is reporting a reboot,
+and how to notify the local lock manager when a monitored peer
+indicates it has rebooted.
+.PP
+An NFS client sends a hostname, known as the client's
+.IR caller_name ,
+in each file lock request.
+An NFS server can use this hostname to send asynchronous GRANT
+calls to a client, or to notify the client it has rebooted.
+.PP
+The Linux NFS server can provide the client's
+.I caller_name
+or the client's network address to
+.BR rpc.statd .
+For the purposes of the NSM protocol,
+this name or address is known as the monitored peer's
+.IR mon_name .
+In addition, the local lock manager tells
 .B rpc.statd
-on those machines.
+what it thinks its own hostname is.
+For the purposes of the NSM protocol,
+this hostname is known as
+.IR my_name .
+.PP
+There is no equivalent interaction between an NFS server and a client
+to inform the client of the server's
+.IR caller_name .
+Therefore NFS clients do not actually know what
+.I mon_name
+an NFS server might use in an SM_NOTIFY request.
+The Linux NFS client uses the server hostname from the mount command
+to identify rebooting NFS servers.
+.SS Reboot notification
+When the local system reboots, the
+.B sm-notify
+command reads the list of monitored peers from persistent storage and
+sends an SM_NOTIFY request to the NSM service on each listed remote peer.
+It uses the
+.I mon_name
+string as the destination.
+To identify which host has rebooted, the
+.B sm-notify
+command normally sends the results of
+.BR gethostname (3)
+as the
+.I my_name
+string.
+The remote
+.B rpc.statd
+matches incoming SM_NOTIFY requests using this string,
+or the caller's network address,
+to one or more peers on its own monitor list.
+.PP
+If
+.B rpc.statd
+does not find a peer on its monitor list that matches
+an incoming SM_NOTIFY request,
+the notification is not forwarded to the local lock manager.
+In addition, each peer has its own
+.IR "NSM state number" ,
+a 32-bit integer that is bumped after each reboot by the
+.B sm-notify
+command.
+.B rpc.statd
+uses this number to distinguish between actual reboots
+and replayed notifications.
+.PP
+Part of NFS lock recovery is rediscovering
+which peers need to be monitored again.
+The
+.B sm-notify
+command clears the monitor list on persistent storage after each reboot.
 .SH OPTIONS
 .TP
-.B -F
-By default,
+.BR -d , " --no-syslog
+Causes
 .B rpc.statd
-forks and puts itself in the background when started. The
-.B -F
-argument tells it to remain in the foreground. This option is
-mainly for debugging purposes.
-.TP
-.B -d
-By default,
-.B rpc.statd
-sends logging messages via
-.BR syslog (3)
-to system log.  The
-.B -d
-argument forces it to log verbose output to
-.B stderr
-instead. This option is mainly for debugging purposes, and may only
-be used in conjunction with the
+to write log messages on
+.I stderr
+instead of to the system log,
+if the
 .B -F
-parameter.
+option was also specified.
 .TP
-.BI "\-n," "" " \-\-name " name 
-specify a name for
-.B rpc.statd
-to use as the local hostname. By default,
-.BR rpc.statd
-will call
-.BR gethostname (2)
-to get the local hostname. Specifying
-a local hostname may be useful for machines with more than one
-interfaces.
+.BR -F , " --foreground
+Keeps
+.B rpc.statd
+attached to its controlling terminal so that NSM
+operation can be monitored directly or run under a debugger.
+If this option is not specified,
+.B rpc.statd
+backgrounds itself soon after it starts.
 .TP
-.BI "\-o," "" " \-\-outgoing\-port "  port
-specify a port for
-.B rpc.statd
-to send outgoing status requests from.  By default,
-.BR rpc.statd
-will ask
-.BR portmap (8)
-to assign it a port number.  As of this writing, there is not
-a standard port number that
-.BR portmap
-always or usually assigns.  Specifying
-a port may be useful when implementing a firewall.
+.BR -h , " -?" , " --help
+Causes
+.B rpc.statd
+to display usage information on
+.I stderr
+and then exit.
 .TP
-.BI "\-p," "" " \-\-port " port
-specify a port for
-.B rpc.statd
-to listen on.  By default,
-.BR rpc.statd
-will ask
-.BR portmap (8)
-to assign it a port number.  As of this writing, there is not
-a standard port number that
-.BR portmap
-always or usually assigns.  Specifying
-a port may be useful when implementing a firewall.
+.BI "\-H," "" " \-\-ha-callout " prog
+Specifies a high availability callout program.
+If this option is not specified, no callouts are performed.
+See the
+.B High-availability callouts
+section below for details.
 .TP
-.BI "\-P," "" " \-\-state\-directory\-path "  directory
-specify a directory in which to place statd state information.
-If this option is not specified the default of 
-.BR /var/lib/nfs
-is used.
+.BR -L , " --no-notify
+Prevents
+.B rpc.statd
+from running the
+.B sm-notify
+command when it starts up,
+preserving the existing NSM state number and monitor list.
+.IP
+Note: the
+.B sm-notify
+command contains a check to ensure it runs only once after each system reboot.
+This prevents spurious reboot notification if
+.B rpc.statd
+restarts without the
+.B -L
+option.
 .TP
-.B -N
-Causes statd to run in the notify-only mode. When started in this mode, the
-statd program will check its state directory, send notifications to any
-monitored nodes, and exit once the notifications have been sent. This mode is
-used to enable Highly Available NFS implementations (i.e. HA-NFS).
-This mode is deprecated \-
+.BI "\-n, " "" "\-\-name " ipaddr " | " hostname
+Specifies the bind address used for RPC listener sockets.
+The
+.I ipaddr
+form can be expressed as either an IPv4 or an IPv6 presentation address.
+If this option is not specified,
+.B rpc.statd
+uses a wildcard address as the transport bind address.
+.IP
+This string is also passed to the
 .B sm-notify
-should be used directly instead.
+command to be used as the source address from which
+to send reboot notification requests.
+See
+.BR sm-notify (8)
+for details.
 .TP
-.BR -L , " --no-notify
-Inhibits the running of
-.BR sm-notify .
-If
+.BR -N
+Causes
+.B rpc.statd
+to run the
 .B sm-notify
-is run by some other script at boot time, there is no need for
-.B statd
-to start sm-notify itself.  This can be appropriate if starting of
-statd needs to be delayed until it is actually need.  In such cases
+command, and then exit.
+Since the
+.B sm-notify
+command can also be run directly, this option is deprecated.
+.TP
+.BI "\-o," "" " \-\-outgoing\-port "  port
+Specifies the source port number the
 .B sm-notify
-should still be run at boot time.
+command should use when sending reboot notifications.
+See
+.BR sm-notify (8)
+for details.
 .TP
-.BI "\-H, " "" " \-\-ha-callout " prog
-Specify a high availability callout program, which will receive callouts
-for all client monitor and unmonitor requests. This allows
+.BI "\-p," "" " \-\-port " port
+Specifies the port number used for RPC listener sockets.
+If this option is not specified,
 .B rpc.statd
-to be used in a High Availability NFS (HA-NFS) environment. The
-program will be run with 3 arguments:  The first is either
-.B add-client
-or
-.B del-client
-depending on the reason for the callout.
-The second will be the name of the client.
-The third will be the name of the server as known to the client.
+chooses a random ephemeral port for each listener socket.
+.IP
+This option can be used to fix the port value of its listeners when
+SM_NOTIFY requests must traverse a firewall between clients and servers.
 .TP
-.B -?
-Causes
+.BI "\-P, " "" \-\-state\-directory\-path " pathname
+Specifies the pathname of the parent directory
+where NSM state information resides.
+If this option is not specified,
 .B rpc.statd
-to print out command-line help and exit.
+uses
+.I /var/lib/nfs
+by default.
+.IP
+After starting,
+.B rpc.statd
+attempts to set its effective UID and GID to the owner
+and group of this directory.
 .TP
-.B -V
+.BR -v ", " -V ", " --version
 Causes
 .B rpc.statd
-to print out version information and exit.
-
-
-
-.SH TCP_WRAPPERS SUPPORT
-This
+to display version information on
+.I stderr
+and then exit.
+.SH SECURITY
+The
 .B rpc.statd
-version is protected by the
+daemon must be started as root to acquire privileges needed
+to create sockets with privileged source ports, and to access the
+state information database.
+Because
+.B rpc.statd
+maintains a long-running network service, however, it drops root privileges
+as soon as it starts up to reduce the risk of a privilege escalation attack.
+.PP
+During normal operation,
+the effective user ID it chooses is the owner of the state directory.
+This allows it to continue to access files in that directory after it
+has dropped its root privileges.
+To control which user ID
+.B rpc.statd
+chooses, simply use
+.BR chown (1)
+to set the owner of
+the state directory.
+.PP
+You can also protect your
+.B rpc.statd
+listeners using the
+.B tcp_wrapper
+library or
+.BR iptables (8).
+To use the
 .B tcp_wrapper
-library. You have to give the clients access to
-.B rpc.statd
-if they should be allowed to use it. To allow connects from clients of
-the .bar.com domain you could use the following line in /etc/hosts.allow:
-
-statd: .bar.com
-
-You have to use the daemon name 
+library, add the hostnames of peers that should be allowed access to
+.IR /etc/hosts.allow .
+Use the daemon name
 .B statd
-for the daemon name (even if the binary has a different name).
-
-For further information please have a look at the
+even if the
+.B rpc.statd
+binary has a different filename.
+.P
+For further information see the
 .BR tcpd (8)
 and
 .BR hosts_access (5)
-manual pages.
-
-.SH SIGNALS
-.BR SIGUSR1
-causes
-.B rpc.statd
-to re-read the notify list from disk
-and send notifications to clients. This can be used in High Availability NFS
-(HA-NFS) environments to notify clients to reacquire file locks upon takeover
-of an NFS export from another server.
-
+man pages.
+.SH ADDITIONAL NOTES
+Lock recovery after a reboot is critical to maintaining data integrity
+and preventing unnecessary application hangs.
+.PP
+To help
+.B rpc.statd
+match SM_NOTIFY requests to NLM requests, a number of best practices
+should be observed, including:
+.IP
+The UTS nodename of your systems should match the DNS names that NFS
+peers use to contact them
+.IP
+The UTS nodenames of your systems should always be fully qualified domain names
+.IP
+The forward and reverse DNS mapping of the UTS nodenames should be
+consistent
+.IP
+The hostname the client uses to mount the server should match the server's
+.I mon_name
+in SM_NOTIFY requests it sends
+.IP
+The use of network addresses as a
+.I mon_name
+or a
+.I my_name
+string should be avoided when
+interoperating with non-Linux NFS implementations.
+.PP
+Unmounting an NFS file system does not necessarily stop
+either the NFS client or server from monitoring each other.
+Both may continue monitoring each other for a time in case subsequent
+NFS traffic between the two results in fresh mounts and additional
+file locking.
+.PP
+On Linux, if the
+.B lockd
+kernel module is unloaded during normal operation,
+all remote NFS peers are unmonitored.
+This can happen on an NFS client, for example,
+if an automounter removes all NFS mount
+points due to inactivity.
+.SS High-availability callouts
+.B rpc.statd
+can exec a special callout program during processing of
+successful SM_MON, SM_UNMON, and SM_UNMON_ALL requests.
+Such a program may be used in High Availability NFS (HA-NFS)
+environments to track lock state that may need to be migrated after
+a system reboot.
+.PP
+The name of the callout program is specified with the
+.B -H
+option.
+The program is run with 3 arguments:
+The first is either
+.B add-client
+or
+.B del-client
+depending on the reason for the callout.
+The second is the
+.I mon_name
+of the monitored peer.
+The third is the
+.I caller_name
+of the requesting lock manager.
+.SS IPv6 and TI-RPC support
+TI-RPC is a pre-requisite for supporting NFS on IPv6.
+If TI-RPC support is built into
+.BR rpc.statd ,
+it attempts to start listeners on network transports marked
+'visible' in
+.IR /etc/netconfig .
+As long as at least one network transport listener starts successfully,
+.B rpc.statd
+will operate.
 .SH FILES
-.BR /var/lib/nfs/state
+.TP 2.5i
+.I /var/lib/nfs/sm
+directory containing monitor list
+.TP 2.5i
+.I /var/lib/nfs/sm.bak
+directory containing notify list
+.TP 2.5i
+.I /var/lib/nfs/state
+NSM state number for this host
+.TP 2.5i
+.I /var/run/run.statd.pid
+pid file
+.TP 2.5i
+.I /etc/netconfig
+network transport capability database
+.SH SEE ALSO
+.BR sm-notify (8),
+.BR nfs (5),
+.BR rpc.nfsd (8),
+.BR rpcbind (8),
+.BR tcpd (8),
+.BR hosts_access (5),
+.BR iptables (8),
+.BR netconfig (5)
+.sp
+RFC 1094 - "NFS: Network File System Protocol Specification"
 .br
-.BR /var/lib/nfs/sm/*
+RFC 1813 - "NFS Version 3 Protocol Specification"
 .br
-.BR /var/lib/nfs/sm.bak/*
-.SH SEE ALSO
-.BR rpc.nfsd(8),
-.BR portmap(8)
+OpenGroup Protocols for Interworking: XNFS, Version 3W - Chapter 11
 .SH AUTHORS
-.br
 Jeff Uphoff <juphoff@users.sourceforge.net>
 .br
 Olaf Kirch <okir@monad.swb.de>
@@ -195,3 +404,5 @@ H.J. Lu <hjl@gnu.org>
 Lon Hohberger <hohberger@missioncriticallinux.com>
 .br
 Paul Clements <paul.clements@steeleye.com>
+.br
+Chuck Lever <chuck.lever@oracle.com>
diff --git a/utils/statd/svc_run.c b/utils/statd/svc_run.c
index 14ee663..d98ecee 100644
--- a/utils/statd/svc_run.c
+++ b/utils/statd/svc_run.c
@@ -101,12 +101,12 @@ my_svc_run(void)
 
 			tv.tv_sec  = NL_WHEN(notify) - now;
 			tv.tv_usec = 0;
-			dprintf(N_DEBUG, "Waiting for reply... (timeo %d)",
+			xlog(D_GENERAL, "Waiting for reply... (timeo %d)",
 							tv.tv_sec);
 			selret = select(FD_SETSIZE, &readfds,
 				(void *) 0, (void *) 0, &tv);
 		} else {
-			dprintf(N_DEBUG, "Waiting for client connections.");
+			xlog(D_GENERAL, "Waiting for client connections");
 			selret = select(FD_SETSIZE, &readfds,
 				(void *) 0, (void *) 0, (struct timeval *) 0);
 		}
@@ -116,8 +116,7 @@ my_svc_run(void)
 			if (errno == EINTR || errno == ECONNREFUSED
 			 || errno == ENETUNREACH || errno == EHOSTUNREACH)
 				continue;
-			note(N_ERROR, "my_svc_run() - select: %s",
-				strerror (errno));
+			xlog(L_ERROR, "my_svc_run() - select: %m");
 			return;
 
 		case 0:
diff --git a/utils/statd/version.h b/utils/statd/version.h
deleted file mode 100644
index 12f16bd..0000000
--- a/utils/statd/version.h
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * Copyright (C) 1997-1999 Jeffrey A. Uphoff
- *
- * NSM for Linux.
- */
-
-#define STATD_RELEASE "1.1.1"