[nfs-utils] Update to the latest RC release: nfs-utils-1.2.6-rc7

Steve Dickson steved at fedoraproject.org
Thu May 3 20:51:57 UTC 2012


commit 2eff033677f46b4c62ab44553242bf4b255e3dab
Author: Steve Dickson <steved at redhat.com>
Date:   Thu May 3 15:23:17 2012 -0400

    Update to the latest RC release: nfs-utils-1.2.6-rc7
    
    Signed-off-by: Steve Dickson <steved at redhat.com>

 nfs-utils-1.2.4-mountshortcut.patch         |   52 -
 nfs-utils-1.2.5-gssd-nolibgssapi-krb5.patch |   26 -
 nfs-utils-1.2.5-gssd-usercreds.patch        |   96 -
 nfs-utils-1.2.5-libidmap-hide-syms.patch    |   26 -
 nfs-utils-1.2.5-nfsd-new-default.patch      |   36 -
 nfs-utils-1.2.6-rc7.patch                   | 4192 +++++++++++++++++++++++++++
 nfs-utils.spec                              |   18 +-
 7 files changed, 4198 insertions(+), 248 deletions(-)
---
diff --git a/nfs-utils-1.2.6-rc7.patch b/nfs-utils-1.2.6-rc7.patch
new file mode 100644
index 0000000..e702124
--- /dev/null
+++ b/nfs-utils-1.2.6-rc7.patch
@@ -0,0 +1,4192 @@
+diff --git a/.gitignore b/.gitignore
+index 7bd9921..96f9750 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -49,6 +49,7 @@ utils/rquotad/rquotad
+ utils/rquotad/rquota.h
+ utils/rquotad/rquota_xdr.c
+ utils/showmount/showmount
++utils/nfsdcld/nfsdcld
+ utils/statd/statd
+ tools/locktest/testlk
+ tools/getiversion/getiversion
+diff --git a/README b/README
+index e7588cf..348f5d4 100644
+--- a/README
++++ b/README
+@@ -15,6 +15,8 @@ libraries.  They are available from
+    http://www.citi.umich.edu/projects/nfsv4/linux/libnfsidmap/
+ Otherwise use --disable-nfsv4
+ 
++To use the nfsdcld tracking daemon, nfsv4 support must be enabled,
++and the libsqlite3 development libraries must be installed.
+ 
+ 1. COMPILING
+ 
+@@ -80,7 +82,7 @@ scripts can be written to work correctly.
+        and starting the nfsd server is not important.
+        idmapd is only needed for NFSv4 support.
+        svcgssd is only needed if exportfs NFS filesystem with crypto-
+-       security (Kerberos or SPKM3).
++       security (Kerberos).
+ 
+    C/ exportfs -av ; rpc.mountd
+        It is important that exportfs be run before mountd so that
+@@ -106,12 +108,31 @@ scripts can be written to work correctly.
+        the lock.
+        rpc.statd is only needed for NFSv2 and NFSv3 support.
+ 
+-   E/ rpc.nfsd
++   E/ nfsdcld
++       This daemon is only needed on kernels that support the nfsdcld
++       upcall, and only if the legacy client ID tracking isn't used. It
++       is also not needed if the server does not support NFSv4.
++
++       To determine whether you need this or not, do the following:
++
++           # cat /proc/fs/nfsd/versions
++
++       That should yield a list of NFS versions that this kernel supports,
++       if "4" or later is not in that list, or they are prefixed with a "-"
++       then you don't need to run this daemon. Next:
++
++           # cat /proc/fs/nfsd/nfsv4recoverydir
++
++       If that file is not present, or the directory that the above command
++       outputs is not present, then this daemon is required in order to
++       support lock recovery by the clients when the server reboots.
++
++   F/ rpc.nfsd
+        Starting nfsd will automatically start lockd.  The nfs server
+        will now be fully active and respond to any requests from
+        clients.
+        
+-   F/ sm-notify
++   G/ sm-notify
+        This will notify any client which might have locks from before
+        a reboot to try to reclaim their locks.  This should start
+        immediately after rpc.nfsd is started so that clients have a
+@@ -130,7 +151,7 @@ scripts can be written to work correctly.
+    B/ gssd ; idmapd
+       idmapd should be started before mounting any NFSv4 filesystems.
+       gssd should be started before mounting any NFS filesystems
+-      securely (with Kerberos of SPKM3).
++      securely (with Kerberos).
+ 
+    C/ statd should be run before any NFSv2 or NFSv3 filesystem is
+       mounted with remote locking (i.e. without -o nolock).
+diff --git a/aclocal/ipv6.m4 b/aclocal/ipv6.m4
+index 5ee8fb6..75a8582 100644
+--- a/aclocal/ipv6.m4
++++ b/aclocal/ipv6.m4
+@@ -2,11 +2,6 @@ dnl Checks for IPv6 support
+ dnl
+ AC_DEFUN([AC_IPV6], [
+ 
+-  AC_CHECK_DECL([AI_ADDRCONFIG],
+-                [AC_DEFINE([HAVE_DECL_AI_ADDRCONFIG], 1,
+-                           [Define this to 1 if AI_ADDRCONFIG macro is defined])], ,
+-                [ #include <netdb.h> ])
+-
+   if test "$enable_ipv6" = yes; then
+ 
+     dnl TI-RPC required for IPv6
+@@ -15,15 +10,11 @@ AC_DEFUN([AC_IPV6], [
+     fi
+ 
+     dnl IPv6-enabled networking functions required for IPv6
+-    AC_CHECK_FUNCS([getifaddrs getnameinfo bindresvport_sa], ,
++    AC_CHECK_FUNCS([getifaddrs getnameinfo], ,
+                    [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
+-    AC_CHECK_DECL([AI_ADDRCONFIG], ,
+-                  [AC_MSG_ERROR([full getaddrinfo(3) implementation needed for IPv6 support])],
+-                  [ #include <netdb.h> ])
+-
++    AC_CHECK_LIB([tirpc], [bindresvport_sa], [:],
++		 [AC_MSG_ERROR([Missing library functions needed for IPv6.])])
+   fi
+ 
+ ])dnl
+diff --git a/aclocal/kerberos5.m4 b/aclocal/kerberos5.m4
+index dfa5738..7574e2d 100644
+--- a/aclocal/kerberos5.m4
++++ b/aclocal/kerberos5.m4
+@@ -31,7 +31,7 @@ AC_DEFUN([AC_KERBEROS_V5],[
+     fi
+     if test "$K5CONFIG" != ""; then
+       KRBCFLAGS=`$K5CONFIG --cflags`
+-      KRBLIBS=`$K5CONFIG --libs gssapi`
++      KRBLIBS=`$K5CONFIG --libs`
+       K5VERS=`$K5CONFIG --version | head -n 1 | awk '{split($(4),v,"."); if (v@<:@"3"@:>@ == "") v@<:@"3"@:>@ = "0"; print v@<:@"1"@:>@v@<:@"2"@:>@v@<:@"3"@:>@ }'`
+       AC_DEFINE_UNQUOTED(KRB5_VERSION, $K5VERS, [Define this as the Kerberos version number])
+       if test -f $dir/include/gssapi/gssapi_krb5.h -a \
+diff --git a/aclocal/libevent.m4 b/aclocal/libevent.m4
+index 3c962b3..b5ac00f 100644
+--- a/aclocal/libevent.m4
++++ b/aclocal/libevent.m4
+@@ -2,8 +2,9 @@ dnl Checks for libevent
+ AC_DEFUN([AC_LIBEVENT], [
+ 
+   dnl Check for libevent, but do not add -levent to LIBS
+-  AC_CHECK_LIB([event], [event_dispatch], [libevent=1],
++  AC_CHECK_LIB([event], [event_dispatch], [LIBEVENT=-levent],
+                [AC_MSG_ERROR([libevent not found.])])
++  AC_SUBST(LIBEVENT)
+ 
+   AC_CHECK_HEADERS([event.h], ,
+                    [AC_MSG_ERROR([libevent headers not found.])])
+diff --git a/aclocal/libnfsidmap.m4 b/aclocal/libnfsidmap.m4
+index 484b1ec..ae697e8 100644
+--- a/aclocal/libnfsidmap.m4
++++ b/aclocal/libnfsidmap.m4
+@@ -3,7 +3,7 @@ dnl
+ AC_DEFUN([AC_LIBNFSIDMAP], [
+ 
+   dnl Check for libnfsidmap, but do not add -lnfsidmap to LIBS
+-  AC_CHECK_LIB([nfsidmap], [nfs4_init_name_mapping], [libnfsidmap=1],
++  AC_CHECK_LIB([nfsidmap], [nfs4_init_name_mapping], [LIBNFSIDMAP=-lnfsidmap],
+                [AC_MSG_ERROR([libnfsidmap not found.])])
+ 
+   AC_CHECK_HEADERS([nfsidmap.h], ,
+@@ -14,7 +14,10 @@ AC_DEFUN([AC_LIBNFSIDMAP], [
+                [AC_DEFINE([HAVE_NFS4_SET_DEBUG], 1,
+                           [Define to 1 if you have the `nfs4_set_debug' function.])])
+ 
+-  dnl only enable nfsidmap when libnfsidmap supports it
+-  AC_CHECK_LIB([nfsidmap], [nfs4_owner_to_uid])
++  dnl nfs4_owner_to_uid() doesn't appear in all versions of libnfsidmap
++  dnl We just need this test to set $ac_cv_lib_nfsidmap_nfs4_owner_to_uid
++  AC_CHECK_LIB([nfsidmap], [nfs4_owner_to_uid], [:])
++
++  AC_SUBST(LIBNFSIDMAP)
+ 
+ ])dnl
+diff --git a/aclocal/libsqlite3.m4 b/aclocal/libsqlite3.m4
+new file mode 100644
+index 0000000..73d1e46
+--- /dev/null
++++ b/aclocal/libsqlite3.m4
+@@ -0,0 +1,33 @@
++dnl Checks for matching sqlite3 header and library, and
++dnl sufficient sqlite3 version.
++dnl
++AC_DEFUN([AC_SQLITE3_VERS], [
++  AC_CHECK_HEADERS([sqlite3.h], ,)
++
++  dnl look for the library; do not add to LIBS if found
++  AC_CHECK_LIB([sqlite3], [sqlite3_libversion_number], [LIBSQLITE=-lsqlite3], ,)
++  AC_SUBST(LIBSQLITE)
++
++  AC_MSG_CHECKING(for suitable sqlite3 version)
++
++  AC_CACHE_VAL([libsqlite3_cv_is_recent],
++   [
++    saved_LIBS="$LIBS"
++    LIBS=-lsqlite3
++    AC_TRY_RUN([
++	#include <stdio.h>
++	#include <sqlite3.h>
++	int main()
++	{
++		int vers = sqlite3_libversion_number();
++
++		return vers != SQLITE_VERSION_NUMBER ||
++			vers < 3003000;
++	}
++       ], [libsqlite3_cv_is_recent=yes], [libsqlite3_cv_is_recent=no],
++       [libsqlite3_cv_is_recent=unknown])
++    LIBS="$saved_LIBS"])
++
++  AC_MSG_RESULT($libsqlite3_cv_is_recent)
++  AM_CONDITIONAL(CONFIG_SQLITE3, [test "$libsqlite3_cv_is_recent" = "yes"])
++])dnl
+diff --git a/aclocal/libtirpc.m4 b/aclocal/libtirpc.m4
+index 9f0fde0..19b8361 100644
+--- a/aclocal/libtirpc.m4
++++ b/aclocal/libtirpc.m4
+@@ -13,8 +13,8 @@ AC_DEFUN([AC_LIBTIRPC], [
+ 
+   if test "$enable_tirpc" != "no"; then
+ 
+-    dnl look for the library; add to LIBS if found
+-    AC_CHECK_LIB([tirpc], [clnt_tli_create], ,
++    dnl look for the library
++    AC_CHECK_LIB([tirpc], [clnt_tli_create], [:],
+                  [if test "$enable_tirpc" = "yes"; then
+ 			AC_MSG_ERROR([libtirpc not found.])
+ 		  else
+@@ -37,4 +37,15 @@ AC_DEFUN([AC_LIBTIRPC], [
+ 
+   fi
+ 
++  dnl now set $LIBTIRPC accordingly
++  if test "$enable_tirpc" != "no"; then
++    AC_DEFINE([HAVE_LIBTIRPC], 1,
++              [Define to 1 if you have and wish to use libtirpc.])
++    LIBTIRPC="-ltirpc"
++  else
++    LIBTIRPC=""
++  fi
++
++  AC_SUBST(LIBTIRPC)
++
+ ])dnl
+diff --git a/configure.ac b/configure.ac
+index 80fb39d..20c452b 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -24,9 +24,8 @@ AC_ARG_WITH(statedir,
+ 	statedir=/var/lib/nfs)
+ 	AC_SUBST(statedir)
+ AC_ARG_WITH(statdpath,
+-	[AC_HELP_STRING([--with-statdpath=/foo @<:@default=/var/lib/nfs@:>@],
+-		[define statd's state dir as /foo instead of the NFS statedir]
+-	)],
++	[AC_HELP_STRING([--with-statdpath=/foo],
++			[define the statd state dir as /foo instead of the NFS statedir @<:@default=/var/lib/nfs@:>@])],
+ 	statdpath=$withval,
+ 	statdpath=$statedir
+ 	)
+@@ -186,6 +185,12 @@ else
+ 	AM_CONDITIONAL(MOUNT_CONFIG, [test "$enable_mount" = "yes"])
+ fi
+ 
++AC_ARG_ENABLE(nfsdcld,
++	[AC_HELP_STRING([--enable-nfsdcld],
++			[Create nfsdcld NFSv4 clientid tracking daemon. @<:@default=no@:>@])],
++	enable_nfsdcld=$enableval,
++	enable_nfsdcld="no")
++
+ dnl Check for TI-RPC library and headers
+ AC_LIBTIRPC
+ 
+@@ -249,6 +254,8 @@ AC_CHECK_FUNC([getservbyname], ,
+ 
+ AC_CHECK_LIB([crypt], [crypt], [LIBCRYPT="-lcrypt"])
+ 
++AC_CHECK_LIB([dl], [dlclose], [LIBDL="-ldl"])
++
+ if test "$enable_nfsv4" = yes; then
+   dnl check for libevent libraries and headers
+   AC_LIBEVENT
+@@ -259,12 +266,32 @@ if test "$enable_nfsv4" = yes; then
+   dnl check for the keyutils libraries and headers
+   AC_KEYUTILS
+ 
++  dnl Check for sqlite3
++  AC_SQLITE3_VERS
++
++  if test "$enable_nfsdcld" = "yes"; then
++	AC_CHECK_HEADERS([libgen.h sys/inotify.h], ,
++		AC_MSG_ERROR([Cannot find header needed for nfsdcld]))
++
++  	if test "$libsqlite3_cv_is_recent" != "yes" ; then
++		AC_MSG_ERROR([nfsdcld requires sqlite3])
++	fi
++  fi
++
++  AM_CONDITIONAL(CONFIG_NFSDCLD, [test "$enable_nfsdcld" = "yes" ])
++
+   dnl librpcsecgss already has a dependency on libgssapi,
+   dnl but we need to make sure we get the right version
+   if test "$enable_gss" = yes; then
+     AC_RPCSEC_VERSION
+   fi
+ fi
++
++if test "$enable_nfsv41" = yes; then
++  AC_CHECK_LIB([devmapper], [dm_task_create], [LIBDEVMAPPER="-ldevmapper"], AC_MSG_ERROR([libdevmapper needed]))
++  AC_CHECK_HEADER(libdevmapper.h, , AC_MSG_ERROR([Cannot find devmapper header file libdevmapper.h]))
++fi
++
+ dnl enable nfsidmap when its support by libnfsidmap
+ AM_CONDITIONAL(CONFIG_NFSIDMAP, [test "$ac_cv_header_keyutils_h$ac_cv_lib_nfsidmap_nfs4_owner_to_uid" = "yesyes"])
+ 
+@@ -293,6 +320,7 @@ AC_SUBST(LIBSOCKET)
+ AC_SUBST(LIBCRYPT)
+ AC_SUBST(LIBBSD)
+ AC_SUBST(LIBBLKID)
++AC_SUBST(LIBDL)
+ 
+ if test "$enable_libmount" != no; then
+    AC_CHECK_LIB(mount, mnt_context_do_mount, [LIBMOUNT="-lmount"], AC_MSG_ERROR([libmount needed]))
+@@ -308,9 +336,6 @@ if test "$enable_gss" = yes; then
+   dnl 'gss' also depends on nfsidmap.h - at least for svcgssd_proc.c
+   AC_LIBNFSIDMAP
+ 
+-  AC_CHECK_HEADERS([spkm3.h], ,
+-                   [AC_MSG_WARN([Could not locate SPKM3 header; will not have SPKM3 support])])
+-
+   dnl Check for Kerberos V5
+   AC_KERBEROS_V5
+ 
+@@ -330,7 +355,7 @@ AC_CHECK_HEADERS([arpa/inet.h fcntl.h libintl.h limits.h \
+                  stdlib.h string.h sys/file.h sys/ioctl.h sys/mount.h \
+                  sys/param.h sys/socket.h sys/time.h sys/vfs.h \
+                  syslog.h unistd.h com_err.h et/com_err.h \
+-                 ifaddrs.h])
++                 ifaddrs.h nfs-plugin.h])
+ 
+ dnl *************************************************************
+ dnl Checks for typedefs, structures, and compiler characteristics
+@@ -452,6 +477,7 @@ AC_CONFIG_FILES([
+ 	tools/nfs-iostat/Makefile
+ 	utils/Makefile
+ 	utils/blkmapd/Makefile
++	utils/nfsdcld/Makefile
+ 	utils/exportfs/Makefile
+ 	utils/gssd/Makefile
+ 	utils/idmapd/Makefile
+@@ -462,6 +488,7 @@ AC_CONFIG_FILES([
+ 	utils/nfsidmap/Makefile
+ 	utils/showmount/Makefile
+ 	utils/statd/Makefile
++	utils/osd_login/Makefile
+ 	tests/Makefile
+ 	tests/nsm_client/Makefile])
+ AC_OUTPUT
+diff --git a/support/include/cld.h b/support/include/cld.h
+new file mode 100644
+index 0000000..f14a9ab
+--- /dev/null
++++ b/support/include/cld.h
+@@ -0,0 +1,56 @@
++/*
++ * Upcall description for nfsdcld communication
++ *
++ * Copyright (c) 2012 Red Hat, Inc.
++ * Author(s): Jeff Layton <jlayton at 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., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++#ifndef _NFSD_CLD_H
++#define _NFSD_CLD_H
++
++/* latest upcall version available */
++#define CLD_UPCALL_VERSION 1
++
++/* defined by RFC3530 */
++#define NFS4_OPAQUE_LIMIT 1024
++
++enum cld_command {
++	Cld_Create,		/* create a record for this cm_id */
++	Cld_Remove,		/* remove record of this cm_id */
++	Cld_Check,		/* is this cm_id allowed? */
++	Cld_GraceDone,		/* grace period is complete */
++};
++
++/* representation of long-form NFSv4 client ID */
++struct cld_name {
++	uint16_t	cn_len;				/* length of cm_id */
++	unsigned char	cn_id[NFS4_OPAQUE_LIMIT];	/* client-provided */
++} __attribute__((packed));
++
++/* message struct for communication with userspace */
++struct cld_msg {
++	uint8_t		cm_vers;		/* upcall version */
++	uint8_t		cm_cmd;			/* upcall command */
++	int16_t		cm_status;		/* return code */
++	uint32_t	cm_xid;			/* transaction id */
++	union {
++		int64_t		cm_gracetime;	/* grace period start time */
++		struct cld_name	cm_name;
++	} __attribute__((packed)) cm_u;
++} __attribute__((packed));
++
++#endif /* !_NFSD_CLD_H */
+diff --git a/support/include/exportfs.h b/support/include/exportfs.h
+index 01e87dd..99916e5 100644
+--- a/support/include/exportfs.h
++++ b/support/include/exportfs.h
+@@ -32,6 +32,10 @@ enum {
+ 	FSLOC_STUB
+ };
+ 
++#ifndef EXP_LOCKFILE
++#define EXP_LOCKFILE "/var/lib/nfs/export-lock"
++#endif
++
+ typedef struct mclient {
+ 	struct mclient *	m_next;
+ 	char *			m_hostname;
+diff --git a/support/include/nfs/debug.h b/support/include/nfs/debug.h
+index d391e91..dbec5ba 100644
+--- a/support/include/nfs/debug.h
++++ b/support/include/nfs/debug.h
+@@ -76,6 +76,9 @@ enum {
+ #define NFSDBG_CALLBACK		0x0100
+ #define NFSDBG_CLIENT		0x0200
+ #define NFSDBG_MOUNT		0x0400
++#define NFSDBG_FSCACHE		0x0800
++#define NFSDBG_PNFS			0x1000
++#define NFSDBG_PNFS_LD		0x2000
+ #define NFSDBG_ALL		0xFFFF
+ 
+ #endif /* _NFS_DEBUG_H */
+diff --git a/support/include/pseudoflavors.h b/support/include/pseudoflavors.h
+index c21087b..deb052b 100644
+--- a/support/include/pseudoflavors.h
++++ b/support/include/pseudoflavors.h
+@@ -4,9 +4,6 @@
+ #define RPC_AUTH_GSS_LKEY       390006
+ #define RPC_AUTH_GSS_LKEYI      390007
+ #define RPC_AUTH_GSS_LKEYP      390008
+-#define RPC_AUTH_GSS_SPKM       390009
+-#define RPC_AUTH_GSS_SPKMI      390010
+-#define RPC_AUTH_GSS_SPKMP      390011
+ 
+ struct flav_info {
+ 	char    *flavour;
+diff --git a/support/nfs/conffile.c b/support/nfs/conffile.c
+index fa0dc6b..5015e94 100644
+--- a/support/nfs/conffile.c
++++ b/support/nfs/conffile.c
+@@ -49,6 +49,8 @@
+ #include "conffile.h"
+ #include "xlog.h"
+ 
++#pragma GCC visibility push(hidden)
++
+ static void conf_load_defaults(void);
+ static int conf_set(int , char *, char *, char *, 
+ 	char *, int , int );
+@@ -211,7 +213,7 @@ static void
+ conf_parse_line(int trans, char *line, size_t sz)
+ {
+ 	char *val, *ptr;
+-	size_t i;
++	size_t i, valsize;
+ 	size_t j;
+ 	static char *section = 0;
+ 	static char *arg = 0;
+@@ -256,13 +258,14 @@ conf_parse_line(int trans, char *line, size_t sz)
+ 			val++, j++;
+ 		if (*val)
+ 			i = j;
+-		section = malloc(i);
++		section = malloc(i+1);
+ 		if (!section) {
+ 			xlog_warn("conf_parse_line: %d: malloc (%lu) failed", ln,
+ 						(unsigned long)i);
+ 			return;
+ 		}
+ 		strncpy(section, line, i);
++		section[i] = '\0';
+ 
+ 		if (arg) 
+ 			free(arg);
+@@ -297,23 +300,16 @@ conf_parse_line(int trans, char *line, size_t sz)
+ 			}
+ 			line[strcspn (line, " \t=")] = '\0';
+ 			val = line + i + 1 + strspn (line + i + 1, " \t");
++			valsize = 0;
++			while (val[valsize++]);
+ 
+-			/* Skip trailing comments, if any */
+-			for (j = 0; j < sz - (val - line); j++) {
+-				if (val[j] == '#' || val[j] == ';') {
++			/* Skip trailing spaces and comments */
++			for (j = 0; j < valsize; j++) {
++				if (val[j] == '#' || val[j] == ';' || isspace(val[j])) {
+ 					val[j] = '\0';
+ 					break;
+ 				}
+ 			}
+-
+-			/* Skip trailing whitespace, if any */
+-			for (j--; j > 0; j--) {
+-				if (isspace(val[j]))
+-					val[j] = '\0';
+-				else 
+-					break;
+-			}
+-
+ 			/* XXX Perhaps should we not ignore errors?  */
+ 			conf_set(trans, section, arg, line, val, 0, 0);
+ 			return;
+diff --git a/support/nfs/exports.c b/support/nfs/exports.c
+index c96500f..84a2b08 100644
+--- a/support/nfs/exports.c
++++ b/support/nfs/exports.c
+@@ -39,12 +39,6 @@ struct flav_info flav_map[] = {
+ 	{ "krb5",	RPC_AUTH_GSS_KRB5	},
+ 	{ "krb5i",	RPC_AUTH_GSS_KRB5I	},
+ 	{ "krb5p",	RPC_AUTH_GSS_KRB5P	},
+-	{ "lipkey",	RPC_AUTH_GSS_LKEY	},
+-	{ "lipkey-i",	RPC_AUTH_GSS_LKEYI	},
+-	{ "lipkey-p",	RPC_AUTH_GSS_LKEYP	},
+-	{ "spkm3",	RPC_AUTH_GSS_SPKM	},
+-	{ "spkm3i",	RPC_AUTH_GSS_SPKMI	},
+-	{ "spkm3p",	RPC_AUTH_GSS_SPKMP	},
+ 	{ "unix",	AUTH_UNIX		},
+ 	{ "sys",	AUTH_SYS		},
+ 	{ "null",	AUTH_NULL		},
+diff --git a/support/nfs/nfsctl.c b/support/nfs/nfsctl.c
+index 89fa1a4..fec775f 100644
+--- a/support/nfs/nfsctl.c
++++ b/support/nfs/nfsctl.c
+@@ -11,16 +11,22 @@
+ #endif
+ 
+ #include <unistd.h>
++#include <errno.h>
+ #include <asm/unistd.h>
+ #include "nfslib.h"
+ 
+ /* compatibility hack... */
+-#ifndef __NR_nfsctl
++#if !defined(__NR_nfsctl) && defined(__NR_nfsservctl)
+ #define __NR_nfsctl	__NR_nfsservctl
+ #endif
+ 
+ int
+ nfsctl (int cmd, struct nfsctl_arg * argp, union nfsctl_res * resp)
+ {
++#ifdef __NR_nfsctl
+   return syscall (__NR_nfsctl, cmd, argp, resp);
++#else
++  errno = ENOSYS;
++  return -1;
++#endif
+ }
+diff --git a/tools/rpcdebug/rpcdebug.c b/tools/rpcdebug/rpcdebug.c
+index 275a491..444616d 100644
+--- a/tools/rpcdebug/rpcdebug.c
++++ b/tools/rpcdebug/rpcdebug.c
+@@ -167,6 +167,9 @@ static struct flagmap {
+ 	FLAG(NFS,       CALLBACK),
+ 	FLAG(NFS,       CLIENT),
+ 	FLAG(NFS,       MOUNT),
++	FLAG(NFS,       FSCACHE),
++	FLAG(NFS,       PNFS),
++	FLAG(NFS,       PNFS_LD),
+ 	FLAG(NFS,	ALL),
+ 
+ 	/* nfsd */
+diff --git a/tools/rpcgen/Makefile.am b/tools/rpcgen/Makefile.am
+index 51a2bfa..8a9ec89 100644
+--- a/tools/rpcgen/Makefile.am
++++ b/tools/rpcgen/Makefile.am
+@@ -12,6 +12,7 @@ rpcgen_SOURCES = rpc_clntout.c rpc_cout.c rpc_hout.c rpc_main.c \
+ rpcgen_CFLAGS=$(CFLAGS_FOR_BUILD)
+ rpcgen_CPPLAGS=$(CPPFLAGS_FOR_BUILD)
+ rpcgen_LDFLAGS=$(LDFLAGS_FOR_BUILD)
++rpcgen_LDADD=$(LIBTIRPC)
+ 
+ MAINTAINERCLEANFILES = Makefile.in
+ 
+diff --git a/utils/Makefile.am b/utils/Makefile.am
+index d074b85..09045dd 100644
+--- a/utils/Makefile.am
++++ b/utils/Makefile.am
+@@ -21,6 +21,10 @@ if CONFIG_MOUNT
+ OPTDIRS += mount
+ endif
+ 
++if CONFIG_NFSDCLD
++OPTDIRS += nfsdcld
++endif
++
+ SUBDIRS = \
+ 	exportfs \
+ 	mountd \
+@@ -28,6 +32,7 @@ SUBDIRS = \
+ 	nfsstat \
+ 	showmount \
+ 	statd \
++	osd_login \
+ 	$(OPTDIRS)
+ 
+ MAINTAINERCLEANFILES = Makefile.in
+diff --git a/utils/blkmapd/device-process.c b/utils/blkmapd/device-process.c
+index 27ff374..652a7a8 100644
+--- a/utils/blkmapd/device-process.c
++++ b/utils/blkmapd/device-process.c
+@@ -296,7 +296,7 @@ decode_blk_volume(uint32_t **pp, uint32_t *end, struct bl_volume *vols, int voln
+ 		off_t stripe_unit = vol->param.bv_stripe_unit;
+ 		/* Check limitations imposed by device-mapper */
+ 		if ((stripe_unit & (stripe_unit - 1)) != 0
+-		    || stripe_unit < (off_t) (PAGE_SIZE >> 9))
++		    || stripe_unit < (off_t) (sysconf(_SC_PAGE_SIZE) >> 9))
+ 			return -EIO;
+ 		BLK_READBUF(p, end, 4);
+ 		READ32(vol->bv_vol_n);
+diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
+index 7432a65..a3323d7 100644
+--- a/utils/exportfs/exportfs.c
++++ b/utils/exportfs/exportfs.c
+@@ -16,6 +16,7 @@
+ #include <sys/stat.h>
+ #include <sys/vfs.h>
+ #include <sys/stat.h>
++#include <sys/file.h>
+ #include <unistd.h>
+ #include <stdbool.h>
+ #include <stdlib.h>
+@@ -43,6 +44,41 @@ static void	usage(const char *progname);
+ static void	validate_export(nfs_export *exp);
+ static int	matchhostname(const char *hostname1, const char *hostname2);
+ static void	export_d_read(const char *dname);
++static void grab_lockfile(void);
++static void release_lockfile(void);
++
++static const char *lockfile = EXP_LOCKFILE;
++static int _lockfd = -1;
++
++/*
++ * If we aren't careful, changes made by exportfs can be lost
++ * when multiple exports process run at once:
++ *
++ *	exportfs process 1	exportfs process 2
++ *	------------------------------------------
++ *	reads etab version A	reads etab version A
++ *	adds new export B	adds new export C
++ *	writes A+B		writes A+C
++ *
++ * The locking in support/export/xtab.c will prevent mountd from
++ * seeing a partially written version of etab, and will prevent 
++ * the two writers above from writing simultaneously and
++ * corrupting etab, but to prevent problems like the above we
++ * need these additional lockfile() routines.
++ */
++static void 
++grab_lockfile()
++{
++	_lockfd = open(lockfile, O_CREAT|O_RDWR, 0666);
++	if (_lockfd != -1) 
++		lockf(_lockfd, F_LOCK, 0);
++}
++static void 
++release_lockfile()
++{
++	if (_lockfd != -1)
++		lockf(_lockfd, F_ULOCK, 0);
++}
+ 
+ int
+ main(int argc, char **argv)
+@@ -129,6 +165,13 @@ main(int argc, char **argv)
+ 			return 0;
+ 		}
+ 	}
++
++	/*
++	 * Serialize things as best we can
++	 */
++	grab_lockfile();
++	atexit(release_lockfile);
++
+ 	if (f_export && ! f_ignore) {
+ 		export_read(_PATH_EXPORTS);
+ 		export_d_read(_PATH_EXPORTS_D);
+diff --git a/utils/exportfs/exportfs.man b/utils/exportfs/exportfs.man
+index 364f247..8853486 100644
+--- a/utils/exportfs/exportfs.man
++++ b/utils/exportfs/exportfs.man
+@@ -177,7 +177,7 @@ In this way
+ .B exportfs
+ can be used to modify the export options of an already exported directory.
+ .SS Unexporting Directories
+-The third synopsis shows how to unexported a currently exported directory.
++The third synopsis shows how to unexport a currently exported directory.
+ When using
+ .BR "exportfs -ua" ,
+ all entries listed in
+diff --git a/utils/exportfs/exports.man b/utils/exportfs/exports.man
+index 54adfeb..bc1de73 100644
+--- a/utils/exportfs/exports.man
++++ b/utils/exportfs/exports.man
+@@ -293,24 +293,6 @@ be explicitly requested with either of the synonymous
+ .IR auth_nlm ,
+ or
+ .IR secure_locks .
+-.TP
+-.IR no_acl
+-On some specially patched kernels, and when exporting filesystems that
+-support ACLs, this option tells
+-.B nfsd
+-not to reveal ACLs to clients, so
+-they will see only a subset of actual permissions on the given file
+-system.  This option is safe for filesystems used by NFSv2 clients and
+-old NFSv3 clients that perform access decisions locally.  Current
+-NFSv3 clients use the ACCESS RPC to perform all access decisions on
+-the server.  Note that the
+-.I no_acl
+-option only has effect on kernels specially patched to support it, and
+-when exporting filesystems with ACL support.  The default is to export
+-with ACL support (i.e. by default,
+-.I no_acl
+-is off).
+-
+ .\".TP
+ .\".I noaccess
+ .\"This makes everything below the directory inaccessible for the named
+diff --git a/utils/exportfs/nfsd.man b/utils/exportfs/nfsd.man
+index 7365a1b..47b73be 100644
+--- a/utils/exportfs/nfsd.man
++++ b/utils/exportfs/nfsd.man
+@@ -12,7 +12,7 @@ nfsd \- special filesystem for controlling Linux NFS server
+ .SH DESCRIPTION
+ The
+ .B nfsd
+-filesytem is a special filesystem which provides access to the Linux
++filesystem is a special filesystem which provides access to the Linux
+ NFS server.  The filesystem consists of a single directory which
+ contains a number of files.  These files are actually gateways into
+ the NFS server.  Writing to them can affect the server.  Reading from
+@@ -86,7 +86,7 @@ should be followed by a newline, with white-space separating the
+ fields, and octal quoting of special characters.
+ 
+ On writing this, the program will be able to read back a filehandle
+-for that path as exported to the given client.  The filehandles length
++for that path as exported to the given client.  The filehandle's length
+ will be at most the number of bytes given.
+ 
+ The filehandle will be represented in hex with a leading '\ex'.
+@@ -165,7 +165,7 @@ file.  The user-space program might then write
+ .ti +5
+ nfsd 127.0.0.1 1057206953 localhost
+ .br
+-to indicate that 127.0.0.1 should map to localhost, atleast for now.
++to indicate that 127.0.0.1 should map to localhost, at least for now.
+ 
+ If the program uses select(2) or poll(2) to discover if it can read
+ from the
+diff --git a/utils/gssd/Makefile.am b/utils/gssd/Makefile.am
+index d7888ad..2365704 100644
+--- a/utils/gssd/Makefile.am
++++ b/utils/gssd/Makefile.am
+@@ -17,7 +17,6 @@ COMMON_SRCS = \
+ 	context_mit.c \
+ 	context_heimdal.c \
+ 	context_lucid.c \
+-	context_spkm3.c \
+ 	gss_util.c \
+ 	gss_oids.c \
+ 	err_util.c \
+@@ -40,7 +39,7 @@ gssd_SOURCES = \
+ 
+ gssd_LDADD =	../../support/nfs/libnfs.a \
+ 		$(RPCSECGSS_LIBS) $(GSSGLUE_LIBS) $(KRBLIBS)
+-gssd_LDFLAGS = $(KRBLDFLAGS)
++gssd_LDFLAGS = $(KRBLDFLAGS) $(LIBTIRPC)
+ 
+ gssd_CFLAGS = $(AM_CFLAGS) $(CFLAGS) \
+ 	      $(RPCSECGSS_CFLAGS) $(GSSGLUE_CFLAGS) $(KRBCFLAGS)
+@@ -58,8 +57,8 @@ svcgssd_SOURCES = \
+ 
+ svcgssd_LDADD = \
+ 	../../support/nfs/libnfs.a \
+-	$(RPCSECGSS_LIBS) $(GSSGLUE_LIBS) -lnfsidmap \
+-	$(KRBLIBS)
++	$(RPCSECGSS_LIBS) $(GSSGLUE_LIBS) $(LIBNFSIDMAP) \
++	$(KRBLIBS) $(LIBTIRPC)
+ 
+ svcgssd_LDFLAGS = $(KRBLDFLAGS)
+ 
+diff --git a/utils/gssd/context.c b/utils/gssd/context.c
+index 1e50bbf..fee7da2 100644
+--- a/utils/gssd/context.c
++++ b/utils/gssd/context.c
+@@ -51,10 +51,6 @@ serialize_context_for_kernel(gss_ctx_id_t ctx,
+ {
+ 	if (g_OID_equal(&krb5oid, mech))
+ 		return serialize_krb5_ctx(ctx, buf, endtime);
+-#ifdef HAVE_SPKM3_H
+-	else if (g_OID_equal(&spkm3oid, mech))
+-		return serialize_spkm3_ctx(ctx, buf, endtime);
+-#endif
+ 	else {
+ 		printerr(0, "ERROR: attempting to serialize context with "
+ 				"unknown/unsupported mechanism oid\n");
+diff --git a/utils/gssd/context.h b/utils/gssd/context.h
+index c9cb0bd..0e437f4 100644
+--- a/utils/gssd/context.h
++++ b/utils/gssd/context.h
+@@ -43,8 +43,6 @@
+ 
+ int serialize_context_for_kernel(gss_ctx_id_t ctx, gss_buffer_desc *buf,
+ 				 gss_OID mech, int32_t *endtime);
+-int serialize_spkm3_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf,
+-			int32_t *endtime);
+ int serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf,
+ 		       int32_t *endtime);
+ 
+diff --git a/utils/gssd/context_lucid.c b/utils/gssd/context_lucid.c
+index 3e695ab..64146d7 100644
+--- a/utils/gssd/context_lucid.c
++++ b/utils/gssd/context_lucid.c
+@@ -80,6 +80,7 @@ prepare_krb5_rfc1964_buffer(gss_krb5_lucid_context_v1_t *lctx,
+ 	uint32_t i;
+ 	char *skd, *dkd;
+ 	gss_buffer_desc fakeoid;
++	int err;
+ 
+ 	/*
+ 	 * The new Kerberos interface to get the gss context
+@@ -138,11 +139,10 @@ prepare_krb5_rfc1964_buffer(gss_krb5_lucid_context_v1_t *lctx,
+ 	dkd = (char *) enc_key.data;
+ 	for (i = 0; i < enc_key.length; i++)
+ 		dkd[i] = skd[i] ^ 0xf0;
+-	if (write_lucid_keyblock(&p, end, &enc_key)) {
+-		free(enc_key.data);
+-		goto out_err;
+-	}
++	err = write_lucid_keyblock(&p, end, &enc_key);
+ 	free(enc_key.data);
++	if (err)
++		goto out_err;
+ 
+ 	if (write_lucid_keyblock(&p, end, &lctx->rfc1964_kd.ctx_key))
+ 		goto out_err;
+@@ -153,7 +153,6 @@ out_err:
+ 	printerr(0, "ERROR: failed serializing krb5 context for kernel\n");
+ 	if (buf->value) free(buf->value);
+ 	buf->length = 0;
+-	if (enc_key.data) free(enc_key.data);
+ 	return -1;
+ }
+ 
+diff --git a/utils/gssd/context_spkm3.c b/utils/gssd/context_spkm3.c
+deleted file mode 100644
+index b927475..0000000
+--- a/utils/gssd/context_spkm3.c
++++ /dev/null
+@@ -1,184 +0,0 @@
+-/*
+-  Copyright (c) 2004 The Regents of the University of Michigan.
+-  All rights reserved.
+-
+-  Redistribution and use in source and binary forms, with or without
+-  modification, are permitted provided that the following conditions
+-  are met:
+-
+-  1. Redistributions of source code must retain the above copyright
+-     notice, this list of conditions and the following disclaimer.
+-  2. Redistributions in binary form must reproduce the above copyright
+-     notice, this list of conditions and the following disclaimer in the
+-     documentation and/or other materials provided with the distribution.
+-  3. Neither the name of the University nor the names of its
+-     contributors may be used to endorse or promote products derived
+-     from this software without specific prior written permission.
+-
+-  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+-  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+-  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+-  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+-  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+-  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+-  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+-  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+-  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+-  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-*/
+-
+-#ifdef HAVE_CONFIG_H
+-#include <config.h>
+-#endif	/* HAVE_CONFIG_H */
+-
+-#include <stdio.h>
+-#include <syslog.h>
+-#include <string.h>
+-#include <gssapi/gssapi.h>
+-#include <rpc/rpc.h>
+-#include <rpc/auth_gss.h>
+-#include "gss_util.h"
+-#include "gss_oids.h"
+-#include "err_util.h"
+-#include "context.h"
+-
+-#ifdef HAVE_SPKM3_H
+-
+-#include <spkm3.h>
+-
+-/*
+- * Function: prepare_spkm3_ctx_buffer()
+- *
+- * Prepare spkm3 lucid context for the kernel
+- *
+- *	buf->length should be:
+- *
+- *      version 4
+- *	ctx_id 4 + 12
+- *	qop 4
+- *	mech_used 4 + 7
+- *	ret_fl  4
+- *	req_fl  4
+- *      share   4 + key_len
+- *      conf_alg 4 + oid_len
+- *      d_conf_key 4 + key_len
+- *      intg_alg 4 + oid_len
+- *      d_intg_key 4 + key_len
+- *      kyestb 4 + oid_len
+- *      owl alg 4 + oid_len
+-*/
+-static int
+-prepare_spkm3_ctx_buffer(gss_spkm3_lucid_ctx_t *lctx, gss_buffer_desc *buf)
+-{
+-	char *p, *end;
+-	unsigned int buf_size = 0;
+-
+-	buf_size = sizeof(lctx->version) +
+-		lctx->ctx_id.length + sizeof(lctx->ctx_id.length) +
+-		sizeof(lctx->endtime) +
+-		sizeof(lctx->mech_used.length) + lctx->mech_used.length +
+-		sizeof(lctx->ret_flags) +
+-		sizeof(lctx->conf_alg.length) + lctx->conf_alg.length +
+-		sizeof(lctx->derived_conf_key.length) +
+-		lctx->derived_conf_key.length +
+-		sizeof(lctx->intg_alg.length) + lctx->intg_alg.length +
+-		sizeof(lctx->derived_integ_key.length) +
+-		lctx->derived_integ_key.length;
+-
+-	if (!(buf->value = calloc(1, buf_size)))
+-		goto out_err;
+-	p = buf->value;
+-	end = buf->value + buf_size;
+-
+-	if (WRITE_BYTES(&p, end, lctx->version))
+-		goto out_err;
+-	printerr(2, "DEBUG: exporting version = %d\n", lctx->version);
+-
+-	if (write_buffer(&p, end, &lctx->ctx_id))
+-		goto out_err;
+-	printerr(2, "DEBUG: exporting ctx_id(%d)\n", lctx->ctx_id.length);
+-
+-	if (WRITE_BYTES(&p, end, lctx->endtime))
+-		goto out_err;
+-	printerr(2, "DEBUG: exporting endtime = %d\n", lctx->endtime);
+-
+-	if (write_buffer(&p, end, &lctx->mech_used))
+-		goto out_err;
+-	printerr(2, "DEBUG: exporting mech oid (%d)\n", lctx->mech_used.length);
+-
+-	if (WRITE_BYTES(&p, end, lctx->ret_flags))
+-		goto out_err;
+-	printerr(2, "DEBUG: exporting ret_flags = %d\n", lctx->ret_flags);
+-
+-	if (write_buffer(&p, end, &lctx->conf_alg))
+-		goto out_err;
+-	printerr(2, "DEBUG: exporting conf_alg oid (%d)\n", lctx->conf_alg.length);
+-
+-	if (write_buffer(&p, end, &lctx->derived_conf_key))
+-		goto out_err;
+-	printerr(2, "DEBUG: exporting conf key (%d)\n", lctx->derived_conf_key.length);
+-
+-	if (write_buffer(&p, end, &lctx->intg_alg))
+-		goto out_err;
+-	printerr(2, "DEBUG: exporting intg_alg oid (%d)\n", lctx->intg_alg.length);
+-
+-	if (write_buffer(&p, end, &lctx->derived_integ_key))
+-		goto out_err;
+-	printerr(2, "DEBUG: exporting intg key (%d)\n", lctx->derived_integ_key.length);
+-
+-	buf->length = p - (char *)buf->value;
+-	return 0;
+-out_err:
+-	printerr(0, "ERROR: failed serializing spkm3 context for kernel\n");
+-	if (buf->value) free(buf->value);
+-	buf->length = 0;
+-
+-	return -1;
+-}
+-
+-/* ANDROS: need to determine which fields of the spkm3_gss_ctx_id_desc_t
+- * are needed in the kernel for get_mic, validate, wrap, unwrap, and destroy
+- * and only export those fields to the kernel.
+- */
+-int
+-serialize_spkm3_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf, int32_t *endtime)
+-{
+-	OM_uint32 vers, ret, maj_stat, min_stat;
+-	void *ret_ctx = 0;
+-	gss_spkm3_lucid_ctx_t     *lctx;
+-
+-	printerr(1, "serialize_spkm3_ctx called\n");
+-
+-	printerr(2, "DEBUG: serialize_spkm3_ctx: lucid version!\n");
+-	maj_stat = gss_export_lucid_sec_context(&min_stat, &ctx, 1, &ret_ctx);
+-	if (maj_stat != GSS_S_COMPLETE)
+-		goto out_err;
+-
+-	lctx = (gss_spkm3_lucid_ctx_t *)ret_ctx;
+-
+-	vers = lctx->version;
+-	if (vers != 1) {
+-		printerr(0, "ERROR: unsupported spkm3 context version %d\n",
+-			vers);
+-		goto out_err;
+-	}
+-	ret = prepare_spkm3_ctx_buffer(lctx, buf);
+-
+-	if (endtime)
+-		*endtime = lctx->endtime;
+-
+-	maj_stat = gss_free_lucid_sec_context(&min_stat, ctx, ret_ctx);
+-
+-	if (maj_stat != GSS_S_COMPLETE)
+-		printerr(0, "WARN: failed to free lucid sec context\n");
+-	if (ret)
+-		goto out_err;
+-	printerr(2, "DEBUG: serialize_spkm3_ctx: success\n");
+-	return 0;
+-
+-out_err:
+-	printerr(2, "DEBUG: serialize_spkm3_ctx: failed\n");
+-	return -1;
+-}
+-#endif /* HAVE_SPKM3_H */
+diff --git a/utils/gssd/gss_oids.c b/utils/gssd/gss_oids.c
+index a59c4a6..4362de2 100644
+--- a/utils/gssd/gss_oids.c
++++ b/utils/gssd/gss_oids.c
+@@ -38,6 +38,3 @@
+ /* from kerberos source, gssapi_krb5.c */
+ gss_OID_desc krb5oid =
+    {9, "\052\206\110\206\367\022\001\002\002"};
+-
+-gss_OID_desc spkm3oid =
+-   {7, "\053\006\001\005\005\001\003"};
+diff --git a/utils/gssd/gss_oids.h b/utils/gssd/gss_oids.h
+index 8b0a352..fde8532 100644
+--- a/utils/gssd/gss_oids.h
++++ b/utils/gssd/gss_oids.h
+@@ -34,7 +34,6 @@
+ #include <sys/types.h>
+ 
+ extern gss_OID_desc krb5oid;
+-extern gss_OID_desc spkm3oid;
+ 
+ #ifndef g_OID_equal
+ #define g_OID_equal(o1,o2) \
+diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c
+index ccadb07..7825255 100644
+--- a/utils/gssd/gssd.c
++++ b/utils/gssd/gssd.c
+@@ -57,7 +57,7 @@
+ 
+ char pipefs_dir[PATH_MAX] = GSSD_PIPEFS_DIR;
+ char keytabfile[PATH_MAX] = GSSD_DEFAULT_KEYTAB_FILE;
+-char ccachedir[PATH_MAX] = GSSD_DEFAULT_CRED_DIR;
++char ccachedir[PATH_MAX] = GSSD_DEFAULT_CRED_DIR ":" GSSD_USER_CRED_DIR;
+ char *ccachesearch[GSSD_MAX_CCACHE_SEARCH + 1];
+ int  use_memcache = 0;
+ int  root_uses_machine_creds = 1;
+@@ -85,7 +85,7 @@ sig_hup(int signal)
+ static void
+ usage(char *progname)
+ {
+-	fprintf(stderr, "usage: %s [-f] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout] [-R preferred realm]\n",
++	fprintf(stderr, "usage: %s [-f] [-l] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout] [-R preferred realm]\n",
+ 		progname);
+ 	exit(1);
+ }
+@@ -102,7 +102,7 @@ main(int argc, char *argv[])
+ 	char *progname;
+ 
+ 	memset(ccachesearch, 0, sizeof(ccachesearch));
+-	while ((opt = getopt(argc, argv, "fvrmnMp:k:d:t:R:")) != -1) {
++	while ((opt = getopt(argc, argv, "fvrlmnMp:k:d:t:R")) != -1) {
+ 		switch (opt) {
+ 			case 'f':
+ 				fg = 1;
+@@ -143,6 +143,13 @@ main(int argc, char *argv[])
+ 			case 'R':
+ 				preferred_realm = strdup(optarg);
+ 				break;
++			case 'l':
++#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
++				limit_to_legacy_enctypes = 1;
++#else 
++				errx(1, "Setting encryption type not support by Kerberos libraries.");
++#endif
++				break;
+ 			default:
+ 				usage(argv[0]);
+ 				break;
+diff --git a/utils/gssd/gssd.h b/utils/gssd/gssd.h
+index b1b5793..28a8206 100644
+--- a/utils/gssd/gssd.h
++++ b/utils/gssd/gssd.h
+@@ -45,6 +45,7 @@
+ #define DNOTIFY_SIGNAL		(SIGRTMIN + 3)
+ 
+ #define GSSD_DEFAULT_CRED_DIR			"/tmp"
++#define GSSD_USER_CRED_DIR			"/run/user"
+ #define GSSD_DEFAULT_CRED_PREFIX		"krb5cc_"
+ #define GSSD_DEFAULT_MACHINE_CRED_SUFFIX	"machine"
+ #define GSSD_DEFAULT_KEYTAB_FILE		"/etc/krb5.keytab"
+@@ -55,7 +56,7 @@
+ /*
+  * The gss mechanisms that we can handle
+  */
+-enum {AUTHTYPE_KRB5, AUTHTYPE_SPKM3, AUTHTYPE_LIPKEY};
++enum {AUTHTYPE_KRB5, AUTHTYPE_LIPKEY};
+ 
+ 
+ 
+@@ -80,8 +81,6 @@ struct clnt_info {
+ 	char			*protocol;
+ 	int			krb5_fd;
+ 	int			krb5_poll_index;
+-	int			spkm3_fd;
+-	int			spkm3_poll_index;
+ 	int                     gssd_fd;
+ 	int                     gssd_poll_index;
+ 	struct sockaddr_storage addr;
+@@ -98,7 +97,6 @@ struct topdirs_info {
+ 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);
+ void gssd_run(void);
+ 
+diff --git a/utils/gssd/gssd.man b/utils/gssd/gssd.man
+index 073379d..d8138fa 100644
+--- a/utils/gssd/gssd.man
++++ b/utils/gssd/gssd.man
+@@ -6,7 +6,7 @@
+ .SH NAME
+ rpc.gssd \- rpcsec_gss daemon
+ .SH SYNOPSIS
+-.B "rpc.gssd [-f] [-n] [-k keytab] [-p pipefsdir] [-v] [-r] [-d ccachedir]"
++.B "rpc.gssd [-f] [-n] [-k keytab] [-l] [-p pipefsdir] [-v] [-r] [-d ccachedir]"
+ .SH DESCRIPTION
+ The rpcsec_gss protocol gives a means of using the gss-api generic security
+ api to provide security for protocols using rpc (in particular, nfs).  Before
+@@ -70,6 +70,30 @@ for "machine credentials" is now:
+ If this search order does not use the correct key then provide a
+ keytab file that contains only correct keys.
+ .TP
++.B -l
++Tells
++.B rpc.gssd
++to limit session keys to Single DES even if the kernel supports stronger
++encryption types. Service ticket encryption is still governed by what
++the KDC believes the target server supports. This way the client can
++access a server that has strong keys in its keytab for ticket decryption
++but whose kernel only supports Single DES.
++.IP
++The alternative is to put only Single DES keys in the server's keytab
++and limit encryption types for its principal to Single DES on the KDC
++which will cause service tickets for this server to be encrypted using
++only Single DES and (as a side-effect) contain only Single DES session
++keys.
++.IP
++This legacy behaviour is only required for older servers
++(pre nfs-utils-1.2.4). If the server has a recent kernel, Kerberos
++implementation and nfs-utils it will work just fine with stronger
++encryption.
++.IP
++.B Note:
++This option is only available with Kerberos libraries that 
++support setable encryption types.
++.TP
+ .B -p path
+ Tells
+ .B rpc.gssd
+diff --git a/utils/gssd/gssd_main_loop.c b/utils/gssd/gssd_main_loop.c
+index b06c223..cec09ea 100644
+--- a/utils/gssd/gssd_main_loop.c
++++ b/utils/gssd/gssd_main_loop.c
+@@ -98,17 +98,6 @@ scan_poll_results(int ret)
+ 			if (!ret)
+ 				break;
+ 		}
+-		i = clp->spkm3_poll_index;
+-		if (i >= 0 && pollarray[i].revents) {
+-			if (pollarray[i].revents & POLLHUP)
+-				dir_changed = 1;
+-			if (pollarray[i].revents & POLLIN)
+-				handle_spkm3_upcall(clp);
+-			pollarray[clp->spkm3_poll_index].revents = 0;
+-			ret--;
+-			if (!ret)
+-				break;
+-		}
+ 	}
+ };
+ 
+diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c
+index 41328c9..aa39435 100644
+--- a/utils/gssd/gssd_proc.c
++++ b/utils/gssd/gssd_proc.c
+@@ -299,15 +299,11 @@ destroy_client(struct clnt_info *clp)
+ 	if (clp->krb5_poll_index != -1)
+ 		memset(&pollarray[clp->krb5_poll_index], 0,
+ 					sizeof(struct pollfd));
+-	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);
+@@ -327,10 +323,8 @@ insert_new_clnt(void)
+ 		goto out;
+ 	}
+ 	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;
+ 
+@@ -355,30 +349,22 @@ process_clnt_dir_files(struct clnt_info * clp)
+ 			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) {
++		if (clp->krb5_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) &&
+-			(clp->gssd_fd == -1))
++	if ((clp->krb5_fd == -1) && (clp->gssd_fd == -1))
+ 		return -1;
+ 	snprintf(info_file_name, sizeof(info_file_name), "%s/info",
+ 			clp->dirname);
+@@ -431,15 +417,6 @@ insert_clnt_poll(struct clnt_info *clp)
+ 		pollarray[clp->krb5_poll_index].events |= POLLIN;
+ 	}
+ 
+-	if ((clp->spkm3_fd != -1) && (clp->spkm3_poll_index == -1)) {
+-		if (get_poll_index(&clp->spkm3_poll_index)) {
+-			printerr(0, "ERROR: Too many spkm3 clients\n");
+-			return -1;
+-		}
+-		pollarray[clp->spkm3_poll_index].fd = clp->spkm3_fd;
+-		pollarray[clp->spkm3_poll_index].events |= POLLIN;
+-	}
+-
+ 	return 0;
+ }
+ 
+@@ -839,13 +816,6 @@ int create_auth_rpc_client(struct clnt_info *clp,
+ 		sec.mech = (gss_OID)&krb5oid;
+ 		sec.req_flags = GSS_C_MUTUAL_FLAG;
+ 	}
+-	else if (authtype == AUTHTYPE_SPKM3) {
+-		sec.mech = (gss_OID)&spkm3oid;
+-		/* XXX sec.req_flags = GSS_C_ANON_FLAG;
+-		 * Need a way to switch....
+-		 */
+-		sec.req_flags = GSS_C_MUTUAL_FLAG;
+-	}
+ 	else {
+ 		printerr(0, "ERROR: Invalid authentication type (%d) "
+ 			"in create_auth_rpc_client\n", authtype);
+@@ -919,9 +889,8 @@ int create_auth_rpc_client(struct clnt_info *clp,
+ 	auth = authgss_create_default(rpc_clnt, clp->servicename, &sec);
+ 	if (!auth) {
+ 		/* Our caller should print appropriate message */
+-		printerr(2, "WARNING: Failed to create %s context for "
++		printerr(2, "WARNING: Failed to create krb5 context for "
+ 			    "user with uid %d for server %s\n",
+-			(authtype == AUTHTYPE_KRB5 ? "krb5":"spkm3"),
+ 			 uid, clp->servername);
+ 		goto out_fail;
+ 	}
+@@ -949,6 +918,23 @@ int create_auth_rpc_client(struct clnt_info *clp,
+ 	goto out;
+ }
+ 
++static char *
++user_cachedir(char *dirname, uid_t uid)
++{
++	struct passwd *pw;
++	char *ptr;
++
++	if ((pw = getpwuid(uid)) == NULL) {
++		printerr(0, "user_cachedir: Failed to find '%d' uid"
++			    " for cache directory\n");
++		return NULL;
++	}
++	ptr = malloc(strlen(dirname)+strlen(pw->pw_name)+2);
++	if (ptr)
++		sprintf(ptr, "%s/%s", dirname, pw->pw_name);
++
++	return ptr;
++}
+ /*
+  * this code uses the userland rpcsec gss library to create a krb5
+  * context on behalf of the kernel
+@@ -963,7 +949,7 @@ process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname,
+ 	gss_buffer_desc		token;
+ 	char			**credlist = NULL;
+ 	char			**ccname;
+-	char			**dirname;
++	char			**dirname, *dir, *userdir;
+ 	int			create_resp = -1;
+ 	int			err, downcall_err = -EACCES;
+ 
+@@ -1006,7 +992,22 @@ process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname,
+ 				service == NULL)) {
+ 		/* Tell krb5 gss which credentials cache to use */
+ 		for (dirname = ccachesearch; *dirname != NULL; dirname++) {
+-			err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname);
++			/* See if the user name is needed */
++			if (strncmp(*dirname, GSSD_USER_CRED_DIR, 
++					strlen(GSSD_USER_CRED_DIR)) == 0) {
++				userdir = user_cachedir(*dirname, uid);
++				if (userdir == NULL) 
++					continue;
++				dir = userdir;
++			} else
++				dir = *dirname;
++
++			err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, dir);
++
++			if (userdir) {
++				free(userdir);
++				userdir = NULL;
++			}
+ 			if (err == -EKEYEXPIRED)
+ 				downcall_err = -EKEYEXPIRED;
+ 			else if (!err)
+@@ -1103,59 +1104,6 @@ out_return_error:
+ 	goto out;
+ }
+ 
+-/*
+- * this code uses the userland rpcsec gss library to create an spkm3
+- * context on behalf of the kernel
+- */
+-static void
+-process_spkm3_upcall(struct clnt_info *clp, uid_t uid, int fd)
+-{
+-	CLIENT			*rpc_clnt = NULL;
+-	AUTH			*auth = NULL;
+-	struct authgss_private_data pd;
+-	gss_buffer_desc		token;
+-
+-	printerr(2, "handling spkm3 upcall (%s)\n", clp->dirname);
+-
+-	token.length = 0;
+-	token.value = NULL;
+-
+-	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);
+-		goto out_return_error;
+-	}
+-
+-	if (!authgss_get_private_data(auth, &pd)) {
+-		printerr(0, "WARNING: Failed to obtain authentication "
+-			    "data for user with uid %d for server %s\n",
+-			 uid, clp->servername);
+-		goto out_return_error;
+-	}
+-
+-	if (serialize_context_for_kernel(pd.pd_ctx, &token, &spkm3oid, NULL)) {
+-		printerr(0, "WARNING: Failed to serialize spkm3 context for "
+-			    "user with uid %d for server\n",
+-			 uid, clp->servername);
+-		goto out_return_error;
+-	}
+-
+-	do_downcall(fd, uid, &pd, &token);
+-
+-out:
+-	if (token.value)
+-		free(token.value);
+-	if (auth)
+-		AUTH_DESTROY(auth);
+-	if (rpc_clnt)
+-		clnt_destroy(rpc_clnt);
+-	return;
+-
+-out_return_error:
+-	do_error_downcall(fd, uid, -1);
+-	goto out;
+-}
+-
+ void
+ handle_krb5_upcall(struct clnt_info *clp)
+ {
+@@ -1171,20 +1119,6 @@ handle_krb5_upcall(struct clnt_info *clp)
+ }
+ 
+ void
+-handle_spkm3_upcall(struct clnt_info *clp)
+-{
+-	uid_t			uid;
+-
+-	if (read(clp->spkm3_fd, &uid, sizeof(uid)) < (ssize_t)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;
+@@ -1292,8 +1226,6 @@ handle_gssd_upcall(struct clnt_info *clp)
+ 
+ 	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);
+diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
+index 4b13fa1..887d118 100644
+--- a/utils/gssd/krb5_util.c
++++ b/utils/gssd/krb5_util.c
+@@ -129,6 +129,10 @@
+ /* Global list of principals/cache file names for machine credentials */
+ struct gssd_k5_kt_princ *gssd_k5_kt_princ_list = NULL;
+ 
++#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
++int limit_to_legacy_enctypes = 0;
++#endif
++
+ /*==========================*/
+ /*===  Internal routines ===*/
+ /*==========================*/
+@@ -1342,7 +1346,7 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
+ 	 * If we failed for any reason to produce global
+ 	 * list of supported enctypes, use local default here.
+ 	 */
+-	if (krb5_enctypes == NULL)
++	if (krb5_enctypes == NULL || limit_to_legacy_enctypes)
+ 		maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
+ 					&krb5oid, num_enctypes, enctypes);
+ 	else
+diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h
+index b42b91e..cd6e107 100644
+--- a/utils/gssd/krb5_util.h
++++ b/utils/gssd/krb5_util.h
+@@ -36,6 +36,7 @@ char *gssd_k5_err_msg(krb5_context context, krb5_error_code code);
+ void gssd_k5_get_default_realm(char **def_realm);
+ 
+ #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
++extern int limit_to_legacy_enctypes;
+ int limit_krb5_enctypes(struct rpc_gss_sec *sec);
+ #endif
+ 
+diff --git a/utils/gssd/svcgssd_mech2file.c b/utils/gssd/svcgssd_mech2file.c
+index 65de8d0..ecd908b 100644
+--- a/utils/gssd/svcgssd_mech2file.c
++++ b/utils/gssd/svcgssd_mech2file.c
+@@ -53,8 +53,6 @@ struct mech2file {
+ 
+ struct mech2file m2f[] = {
+ 	{{9, "\052\206\110\206\367\022\001\002\002"}, "krb5"},
+-	{{7, "\053\006\001\005\005\001\003"}, "spkm3"},
+-	{{7, "\053\006\001\005\005\001\009"}, "lipkey"},
+ 	{{0,0},""},
+ };
+ 
+diff --git a/utils/gssd/svcgssd_proc.c b/utils/gssd/svcgssd_proc.c
+index c714d99..0d4f78d 100644
+--- a/utils/gssd/svcgssd_proc.c
++++ b/utils/gssd/svcgssd_proc.c
+@@ -369,12 +369,8 @@ get_hostbased_client_name(gss_name_t client_name, gss_OID mech,
+ 	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");
++	} else {
++		printerr(1, "WARNING: unknown/unsupport mech OID\n");
+ 	}
+ 
+ 	res = 0;
+diff --git a/utils/idmapd/Makefile.am b/utils/idmapd/Makefile.am
+index 4328e41..58b33ec 100644
+--- a/utils/idmapd/Makefile.am
++++ b/utils/idmapd/Makefile.am
+@@ -16,7 +16,7 @@ idmapd_SOURCES = \
+ 	nfs_idmap.h \
+ 	queue.h
+ 
+-idmapd_LDADD = -levent -lnfsidmap ../../support/nfs/libnfs.a
++idmapd_LDADD = $(LIBEVENT) $(LIBNFSIDMAP) ../../support/nfs/libnfs.a
+ 
+ MAINTAINERCLEANFILES = Makefile.in
+ 
+diff --git a/utils/idmapd/idmapd.c b/utils/idmapd/idmapd.c
+index 19d9114..e80efb4 100644
+--- a/utils/idmapd/idmapd.c
++++ b/utils/idmapd/idmapd.c
+@@ -778,8 +778,8 @@ nfsopen(struct idmap_client *ic)
+ 	} else {
+ 		event_set(&ic->ic_event, ic->ic_fd, EV_READ, nfscb, ic);
+ 		event_add(&ic->ic_event, NULL);
+-		fcntl(ic->ic_dirfd, F_SETSIG, 0);
+ 		fcntl(ic->ic_dirfd, F_NOTIFY, 0);
++		fcntl(ic->ic_dirfd, F_SETSIG, 0);
+ 		if (verbose > 0)
+ 			xlog_warn("Opened %s", ic->ic_path);
+ 	}
+diff --git a/utils/mount/Makefile.am b/utils/mount/Makefile.am
+index 7bc3e2b..7627854 100644
+--- a/utils/mount/Makefile.am
++++ b/utils/mount/Makefile.am
+@@ -24,7 +24,8 @@ EXTRA_DIST += nfsmount.conf
+ endif
+ 
+ mount_nfs_LDADD = ../../support/nfs/libnfs.a \
+-		  ../../support/export/libexport.a
++		  ../../support/export/libexport.a \
++		  $(LIBTIRPC)
+ 
+ mount_nfs_SOURCES = $(mount_common)
+ 
+diff --git a/utils/mount/mount_libmount.c b/utils/mount/mount_libmount.c
+index e450d79..e8f17a9 100644
+--- a/utils/mount/mount_libmount.c
++++ b/utils/mount/mount_libmount.c
+@@ -346,6 +346,21 @@ static int mount_main(struct libmnt_context *cxt, int argc, char **argv)
+ 
+ 	if (chk_mountpoint(mount_point))
+ 		goto err;
++
++	/*
++	 * The libmount strictly uses only options from fstab if running in
++	 * restricted mode (suid, non-root user). This is done in
++	 * mnt_context_prepare_mount() by default.
++	 *
++	 * We have to read fstab before nfsmount.conf, otherwise the options
++	 * from nfsmount.conf will be ignored (overwrited).
++	 */
++	rc = mnt_context_apply_fstab(cxt);
++	if (rc) {
++		nfs_error(_("%s: failed to apply fstab options\n"), progname);
++		goto err;
++	}
++
+ 	/*
+ 	 * Concatenate mount options from the configuration file
+ 	 */
+diff --git a/utils/mount/nfs.man b/utils/mount/nfs.man
+index ce40933..0d20cf0 100644
+--- a/utils/mount/nfs.man
++++ b/utils/mount/nfs.man
+@@ -372,14 +372,8 @@ Valid security flavors are
+ .BR sys ,
+ .BR krb5 ,
+ .BR krb5i ,
+-.BR krb5p ,
+-.BR lkey ,
+-.BR lkeyi ,
+-.BR lkeyp ,
+-.BR spkm ,
+-.BR spkmi ,
+ and
+-.BR spkmp .
++.BR krb5p ,
+ Refer to the SECURITY CONSIDERATIONS section for details.
+ .TP 1.5i
+ .BR sharecache " / " nosharecache
+@@ -1416,7 +1410,7 @@ security flavor encrypts every RPC request
+ to prevent data exposure during network transit; however,
+ expect some performance impact
+ when using integrity checking or encryption.
+-Similar support for other forms of cryptographic security (such as lipkey and SPKM3)
++Similar support for other forms of cryptographic security
+ is also available.
+ .P
+ The NFS version 4 protocol allows
+@@ -1561,10 +1555,10 @@ To ensure that the saved mount options are not erased during a remount,
+ specify either the local mount directory, or the server hostname and
+ export pathname, but not both, during a remount.  For example,
+ .P
+-.NF
+-.TA 2.5i
++.nf
++.ta 8n
+ 	mount -o remount,ro /mnt
+-.FI
++.fi
+ .P
+ merges the mount option
+ .B ro
+diff --git a/utils/mount/nfs_mount.h b/utils/mount/nfs_mount.h
+index 2becfb1..ec30c9b 100644
+--- a/utils/mount/nfs_mount.h
++++ b/utils/mount/nfs_mount.h
+@@ -75,9 +75,6 @@ struct nfs_mount_data {
+ #define AUTH_GSS_LKEY		390006
+ #define AUTH_GSS_LKEYI		390007
+ #define AUTH_GSS_LKEYP		390008
+-#define AUTH_GSS_SPKM		390009
+-#define AUTH_GSS_SPKMI		390010
+-#define AUTH_GSS_SPKMP		390011
+ #endif
+ 
+ int	nfsmount(const char *, const char *, int , char **, int, int);
+diff --git a/utils/mount/nfsmount.c b/utils/mount/nfsmount.c
+index 1298fe4..930622d 100644
+--- a/utils/mount/nfsmount.c
++++ b/utils/mount/nfsmount.c
+@@ -294,18 +294,6 @@ parse_options(char *old_opts, struct nfs_mount_data *data,
+ 					data->pseudoflavor = AUTH_GSS_KRB5I;
+ 				else if (!strcmp(secflavor, "krb5p"))
+ 					data->pseudoflavor = AUTH_GSS_KRB5P;
+-				else if (!strcmp(secflavor, "lipkey"))
+-					data->pseudoflavor = AUTH_GSS_LKEY;
+-				else if (!strcmp(secflavor, "lipkey-i"))
+-					data->pseudoflavor = AUTH_GSS_LKEYI;
+-				else if (!strcmp(secflavor, "lipkey-p"))
+-					data->pseudoflavor = AUTH_GSS_LKEYP;
+-				else if (!strcmp(secflavor, "spkm3"))
+-					data->pseudoflavor = AUTH_GSS_SPKM;
+-				else if (!strcmp(secflavor, "spkm3i"))
+-					data->pseudoflavor = AUTH_GSS_SPKMI;
+-				else if (!strcmp(secflavor, "spkm3p"))
+-					data->pseudoflavor = AUTH_GSS_SPKMP;
+ 				else if (sloppy)
+ 					continue;
+ 				else {
+diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c
+index 314a806..e09aa7c 100644
+--- a/utils/mount/stropts.c
++++ b/utils/mount/stropts.c
+@@ -540,6 +540,8 @@ nfs_rewrite_pmap_mount_options(struct mount_options *options)
+ 			errno = EOPNOTSUPP;
+ 		else if (rpc_createerr.cf_stat == RPC_AUTHERROR)
+ 			errno = EACCES;
++		else if (rpc_createerr.cf_stat == RPC_TIMEDOUT)
++			errno = ETIMEDOUT;
+ 		else if (rpc_createerr.cf_error.re_errno != 0)
+ 			errno = rpc_createerr.cf_error.re_errno;
+ 		return 0;
+@@ -665,9 +667,10 @@ static int nfs_try_mount_v3v2(struct nfsmount_info *mi)
+ 		case EHOSTUNREACH:
+ 			continue;
+ 		default:
+-			break;
++			goto out;
+ 		}
+ 	}
++out:
+ 	return ret;
+ }
+ 
+@@ -751,9 +754,10 @@ static int nfs_try_mount_v4(struct nfsmount_info *mi)
+ 		case EHOSTUNREACH:
+ 			continue;
+ 		default:
+-			break;
++			goto out;
+ 		}
+ 	}
++out:
+ 	return ret;
+ }
+ 
+@@ -907,7 +911,8 @@ static int nfsmount_parent(struct nfsmount_info *mi)
+ 	if (nfs_try_mount(mi))
+ 		return EX_SUCCESS;
+ 
+-	if (nfs_is_permanent_error(errno)) {
++	/* retry background mounts when the server is not up */
++	if (nfs_is_permanent_error(errno) && errno != EOPNOTSUPP) {
+ 		mount_error(mi->spec, mi->node, errno);
+ 		return EX_FAIL;
+ 	}
+@@ -942,7 +947,8 @@ static int nfsmount_child(struct nfsmount_info *mi)
+ 		if (nfs_try_mount(mi))
+ 			return EX_SUCCESS;
+ 
+-		if (nfs_is_permanent_error(errno))
++		/* retry background mounts when the server is not up */
++		if (nfs_is_permanent_error(errno) && errno != EOPNOTSUPP)
+ 			break;
+ 
+ 		if (time(NULL) > timeout)
+diff --git a/utils/mountd/Makefile.am b/utils/mountd/Makefile.am
+index eba81fc..7db968b 100644
+--- a/utils/mountd/Makefile.am
++++ b/utils/mountd/Makefile.am
+@@ -12,7 +12,7 @@ mountd_SOURCES = mountd.c mount_dispatch.c auth.c rmtab.c cache.c \
+ mountd_LDADD = ../../support/export/libexport.a \
+ 	       ../../support/nfs/libnfs.a \
+ 	       ../../support/misc/libmisc.a \
+-	       $(LIBBSD) $(LIBWRAP) $(LIBNSL) $(LIBBLKID)
++	       $(LIBBSD) $(LIBWRAP) $(LIBNSL) $(LIBBLKID) $(LIBDL) $(LIBTIRPC)
+ mountd_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) \
+ 		  -I$(top_builddir)/support/include \
+ 		  -I$(top_srcdir)/support/export
+diff --git a/utils/mountd/auth.c b/utils/mountd/auth.c
+index ccc849a..508040a 100644
+--- a/utils/mountd/auth.c
++++ b/utils/mountd/auth.c
+@@ -112,15 +112,23 @@ auth_reload()
+ 	return counter;
+ }
+ 
++static char *get_client_ipaddr_name(const struct sockaddr *caller)
++{
++	char buf[INET6_ADDRSTRLEN + 1];
++
++	buf[0] = '$';
++	host_ntop(caller, buf + 1, sizeof(buf) - 1);
++	return strdup(buf);
++}
++
+ static char *
+ get_client_hostname(const struct sockaddr *caller, struct addrinfo *ai,
+ 		enum auth_error *error)
+ {
+-	char buf[INET6_ADDRSTRLEN];
+ 	char *n;
+ 
+ 	if (use_ipaddr)
+-		return strdup(host_ntop(caller, buf, sizeof(buf)));
++		return get_client_ipaddr_name(caller);
+ 	n = client_compose(ai);
+ 	*error = unknown_host;
+ 	if (!n)
+@@ -131,6 +139,23 @@ get_client_hostname(const struct sockaddr *caller, struct addrinfo *ai,
+ 	return strdup("DEFAULT");
+ }
+ 
++bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai)
++{
++	return client_check(exp->m_client, ai);
++}
++
++bool namelist_client_matches(nfs_export *exp, char *dom)
++{
++	return client_member(dom, exp->m_client->m_hostname);
++}
++
++bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai)
++{
++	if (is_ipaddr_client(dom))
++		return ipaddr_client_matches(exp, ai);
++	return namelist_client_matches(exp, dom);
++}
++
+ /* return static nfs_export with details filled in */
+ static nfs_export *
+ auth_authenticate_newcache(const struct sockaddr *caller,
+@@ -155,9 +180,10 @@ auth_authenticate_newcache(const struct sockaddr *caller,
+ 		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))
++			if (!client_matches(exp, my_client.m_hostname, ai))
+ 				continue;
+-			if (use_ipaddr && !client_check(exp->m_client, ai))
++			if (exp->m_export.e_flags & NFSEXP_V4ROOT)
++				/* not acceptable for v[23] export */
+ 				continue;
+ 			break;
+ 		}
+@@ -187,10 +213,6 @@ auth_authenticate_internal(const struct sockaddr *caller, const char *path,
+ 			return NULL;
+ 		}
+ 	}
+-	if (exp->m_export.e_flags & NFSEXP_V4ROOT) {
+-		*error = no_entry;
+-		return NULL;
+-	}
+ 	if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) &&
+ 		     nfs_get_port(caller) >= IPPORT_RESERVED) {
+ 		*error = illegal_port;
+diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c
+index d2ae456..7d80432 100644
+--- a/utils/mountd/cache.c
++++ b/utils/mountd/cache.c
+@@ -84,7 +84,6 @@ static void auth_unix_ip(FILE *f)
+ 	char ipaddr[INET6_ADDRSTRLEN];
+ 	char *client = NULL;
+ 	struct addrinfo *tmp = NULL;
+-	struct addrinfo *ai = NULL;
+ 	if (readline(fileno(f), &lbuf, &lbuflen) != 1)
+ 		return;
+ 
+@@ -107,12 +106,16 @@ static void auth_unix_ip(FILE *f)
+ 
+ 	/* addr is a valid, interesting address, find the domain name... */
+ 	if (!use_ipaddr) {
++		struct addrinfo *ai = NULL;
++
+ 		ai = client_resolve(tmp->ai_addr);
++		if (ai == NULL)
++			goto out;
+ 		client = client_compose(ai);
+ 		freeaddrinfo(ai);
++		if (!client)
++			goto out;
+ 	}
+-	freeaddrinfo(tmp);
+-
+ 	qword_print(f, "nfsd");
+ 	qword_print(f, ipaddr);
+ 	qword_printuint(f, time(0) + DEFAULT_TTL);
+@@ -124,6 +127,9 @@ static void auth_unix_ip(FILE *f)
+ 	xlog(D_CALL, "auth_unix_ip: client %p '%s'", client, client?client: "DEFAULT");
+ 
+ 	free(client);
++out:
++	freeaddrinfo(tmp);
++
+ }
+ 
+ static void auth_unix_gid(FILE *f)
+@@ -495,6 +501,21 @@ static bool match_fsid(struct parsed_fsid *parsed, nfs_export *exp, char *path)
+ 	return false;
+ }
+ 
++struct addrinfo *lookup_client_addr(char *dom)
++{
++	struct addrinfo *ret;
++	struct addrinfo *tmp;
++
++	dom++; /* skip initial "$" */
++
++	tmp = host_pton(dom);
++	if (tmp == NULL)
++		return NULL;
++	ret = client_resolve(tmp->ai_addr);
++	freeaddrinfo(tmp);
++	return ret;
++}
++
+ static void nfsd_fh(FILE *f)
+ {
+ 	/* request are:
+@@ -538,6 +559,12 @@ static void nfsd_fh(FILE *f)
+ 
+ 	auth_reload();
+ 
++	if (is_ipaddr_client(dom)) {
++		ai = lookup_client_addr(dom);
++		if (!ai)
++			goto out;
++	}
++
+ 	/* Now determine export point for this fsid/domain */
+ 	for (i=0 ; i < MCL_MAXTYPES; i++) {
+ 		nfs_export *next_exp;
+@@ -568,7 +595,8 @@ static void nfsd_fh(FILE *f)
+ 				next_exp = exp->m_next;
+ 			}
+ 
+-			if (!use_ipaddr && !client_member(dom, exp->m_client->m_hostname))
++			if (!is_ipaddr_client(dom)
++					&& !namelist_client_matches(exp, dom))
+ 				continue;
+ 			if (exp->m_export.e_mountpoint &&
+ 			    !is_mountpoint(exp->m_export.e_mountpoint[0]?
+@@ -578,29 +606,29 @@ static void nfsd_fh(FILE *f)
+ 
+ 			if (!match_fsid(&parsed, exp, path))
+ 				continue;
+-			if (use_ipaddr) {
+-				if (ai == NULL) {
+-					struct addrinfo *tmp;
+-					tmp = host_pton(dom);
+-					if (tmp == NULL)
+-						goto out;
+-					ai = client_resolve(tmp->ai_addr);
+-					freeaddrinfo(tmp);
+-				}
+-				if (!client_check(exp->m_client, ai))
+-					continue;
+-			}
++			if (is_ipaddr_client(dom)
++					&& !ipaddr_client_matches(exp, ai))
++				continue;
+ 			if (!found || subexport(&exp->m_export, found)) {
+ 				found = &exp->m_export;
+ 				free(found_path);
+ 				found_path = strdup(path);
+ 				if (found_path == NULL)
+ 					goto out;
+-			} else if (strcmp(found->e_path, exp->m_export.e_path)
++			} else if (strcmp(found->e_path, exp->m_export.e_path) != 0
+ 				   && !subexport(found, &exp->m_export))
+ 			{
+ 				xlog(L_WARNING, "%s and %s have same filehandle for %s, using first",
+ 				     found_path, path, dom);
++			} else {
++				/* same path, if one is V4ROOT, choose the other */
++				if (found->e_flags & NFSEXP_V4ROOT) {
++					found = &exp->m_export;
++					free(found_path);
++					found_path = strdup(path);
++					if (found_path == NULL)
++						goto out;
++				}
+ 			}
+ 		}
+ 	}
+@@ -742,14 +770,6 @@ static int path_matches(nfs_export *exp, char *path)
+ }
+ 
+ static int
+-client_matches(nfs_export *exp, char *dom, struct addrinfo *ai)
+-{
+-	if (use_ipaddr)
+-		return client_check(exp->m_client, ai);
+-	return client_member(dom, exp->m_client->m_hostname);
+-}
+-
+-static int
+ export_matches(nfs_export *exp, char *dom, char *path, struct addrinfo *ai)
+ {
+ 	return path_matches(exp, path) && client_matches(exp, dom, ai);
+@@ -772,10 +792,14 @@ lookup_export(char *dom, char *path, struct addrinfo *ai)
+ 				found_type = i;
+ 				continue;
+ 			}
+-
+-			/* Always prefer non-V4ROOT mounts */
+-			if (found->m_export.e_flags & NFSEXP_V4ROOT)
++			/* Always prefer non-V4ROOT exports */
++			if (exp->m_export.e_flags & NFSEXP_V4ROOT)
++				continue;
++			if (found->m_export.e_flags & NFSEXP_V4ROOT) {
++				found = exp;
++				found_type = i;
+ 				continue;
++			}
+ 
+ 			/* If one is a CROSSMOUNT, then prefer the longest path */
+ 			if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) ||
+@@ -802,6 +826,229 @@ lookup_export(char *dom, char *path, struct addrinfo *ai)
+ 	return found;
+ }
+ 
++#ifdef HAVE_NFS_PLUGIN_H
++#include <dlfcn.h>
++#include <nfs-plugin.h>
++
++/*
++ * Walk through a set of FS locations and build a set of export options.
++ * Returns true if all went to plan; otherwise, false.
++ */
++static _Bool
++locations_to_options(struct jp_ops *ops, nfs_fsloc_set_t locations,
++		char *options, size_t remaining, int *ttl)
++{
++	char *server, *last_path, *rootpath, *ptr;
++	_Bool seen = false;
++
++	last_path = NULL;
++	rootpath = NULL;
++	server = NULL;
++	ptr = options;
++	*ttl = 0;
++
++	for (;;) {
++		enum jp_status status;
++		int len;
++
++		status = ops->jp_get_next_location(locations, &server,
++							&rootpath, ttl);
++		if (status == JP_EMPTY)
++			break;
++		if (status != JP_OK) {
++			xlog(D_GENERAL, "%s: failed to parse location: %s",
++				__func__, ops->jp_error(status));
++			goto out_false;
++		}
++		xlog(D_GENERAL, "%s: Location: %s:%s",
++			__func__, server, rootpath);
++
++		if (last_path && strcmp(rootpath, last_path) == 0) {
++			len = snprintf(ptr, remaining, "+%s", server);
++			if (len < 0) {
++				xlog(D_GENERAL, "%s: snprintf: %m", __func__);
++				goto out_false;
++			}
++			if ((size_t)len >= remaining) {
++				xlog(D_GENERAL, "%s: options buffer overflow", __func__);
++				goto out_false;
++			}
++			remaining -= (size_t)len;
++			ptr += len;
++		} else {
++			if (last_path == NULL)
++				len = snprintf(ptr, remaining, "refer=%s@%s",
++							rootpath, server);
++			else
++				len = snprintf(ptr, remaining, ":%s@%s",
++							rootpath, server);
++			if (len < 0) {
++				xlog(D_GENERAL, "%s: snprintf: %m", __func__);
++				goto out_false;
++			}
++			if ((size_t)len >= remaining) {
++				xlog(D_GENERAL, "%s: options buffer overflow",
++					__func__);
++				goto out_false;
++			}
++			remaining -= (size_t)len;
++			ptr += len;
++			last_path = rootpath;
++		}
++
++		seen = true;
++		free(rootpath);
++		free(server);
++	}
++
++	xlog(D_CALL, "%s: options='%s', ttl=%d",
++		__func__, options, *ttl);
++	return seen;
++
++out_false:
++	free(rootpath);
++	free(server);
++	return false;
++}
++
++/*
++ * Walk through the set of FS locations and build an exportent.
++ * Returns pointer to an exportent if "junction" refers to a junction.
++ *
++ * Returned exportent points to static memory.
++ */
++static struct exportent *do_locations_to_export(struct jp_ops *ops,
++		nfs_fsloc_set_t locations, const char *junction,
++		char *options, size_t options_len)
++{
++	struct exportent *exp;
++	int ttl;
++
++	if (!locations_to_options(ops, locations, options, options_len, &ttl))
++		return NULL;
++
++	exp = mkexportent("*", (char *)junction, options);
++	if (exp == NULL) {
++		xlog(L_ERROR, "%s: Failed to construct exportent", __func__);
++		return NULL;
++	}
++
++	exp->e_uuid = NULL;
++	exp->e_ttl = ttl;
++	return exp;
++}
++
++/*
++ * Convert set of FS locations to an exportent.  Returns pointer to
++ * an exportent if "junction" refers to a junction.
++ *
++ * Returned exportent points to static memory.
++ */
++static struct exportent *locations_to_export(struct jp_ops *ops,
++		nfs_fsloc_set_t locations, const char *junction)
++{
++	struct exportent *exp;
++	char *options;
++
++	options = malloc(BUFSIZ);
++	if (options == NULL) {
++		xlog(D_GENERAL, "%s: failed to allocate options buffer",
++			__func__);
++		return NULL;
++	}
++	options[0] = '\0';
++
++	exp = do_locations_to_export(ops, locations, junction,
++						options, BUFSIZ);
++
++	free(options);
++	return exp;
++}
++
++/*
++ * Retrieve locations information in "junction" and dump it to the
++ * kernel.  Returns pointer to an exportent if "junction" refers
++ * to a junction.
++ *
++ * Returned exportent points to static memory.
++ */
++static struct exportent *invoke_junction_ops(void *handle,
++		const char *junction)
++{
++	nfs_fsloc_set_t locations;
++	struct exportent *exp;
++	enum jp_status status;
++	struct jp_ops *ops;
++	char *error;
++
++	ops = (struct jp_ops *)dlsym(handle, "nfs_junction_ops");
++	error = dlerror();
++	if (error != NULL) {
++		xlog(D_GENERAL, "%s: dlsym(jp_junction_ops): %s",
++			__func__, error);
++		return NULL;
++	}
++	if (ops->jp_api_version != JP_API_VERSION) {
++		xlog(D_GENERAL, "%s: unrecognized junction API version: %u",
++			__func__, ops->jp_api_version);
++		return NULL;
++	}
++
++	status = ops->jp_init(false);
++	if (status != JP_OK) {
++		xlog(D_GENERAL, "%s: failed to resolve %s: %s",
++			__func__, junction, ops->jp_error(status));
++		return NULL;
++	}
++
++	status = ops->jp_get_locations(junction, &locations);
++	if (status != JP_OK) {
++		xlog(D_GENERAL, "%s: failed to resolve %s: %s",
++			__func__, junction, ops->jp_error(status));
++		return NULL;
++	}
++
++	exp = locations_to_export(ops, locations, junction);
++
++	ops->jp_put_locations(locations);
++	ops->jp_done();
++	return exp;
++}
++
++/*
++ * Load the junction plug-in, then try to resolve "pathname".
++ * Returns pointer to an initialized exportent if "junction"
++ * refers to a junction, or NULL if not.
++ *
++ * Returned exportent points to static memory.
++ */
++static struct exportent *lookup_junction(const char *pathname)
++{
++	struct exportent *exp;
++	void *handle;
++
++	handle = dlopen("libnfsjunct.so", RTLD_NOW);
++	if (handle == NULL) {
++		xlog(D_GENERAL, "%s: dlopen: %s", __func__, dlerror());
++		return NULL;
++	}
++	(void)dlerror();	/* Clear any error */
++
++	exp = invoke_junction_ops(handle, pathname);
++
++	/* We could leave it loaded to make junction resolution
++	 * faster next time.  However, if we want to replace the
++	 * library, that would require restarting mountd. */
++	(void)dlclose(handle);
++	return exp;
++}
++#else	/* !HAVE_NFS_PLUGIN_H */
++static inline struct exportent *lookup_junction(const char *UNUSED(pathname))
++{
++	return NULL;
++}
++#endif	/* !HAVE_NFS_PLUGIN_H */
++
+ static void nfsd_export(FILE *f)
+ {
+ 	/* requests are:
+@@ -834,13 +1081,9 @@ static void nfsd_export(FILE *f)
+ 
+ 	auth_reload();
+ 
+-	if (use_ipaddr) {
+-		struct addrinfo *tmp;
+-		tmp = host_pton(dom);
+-		if (tmp == NULL)
+-			goto out;
+-		ai = client_resolve(tmp->ai_addr);
+-		freeaddrinfo(tmp);
++	if (is_ipaddr_client(dom)) {
++		ai = lookup_client_addr(dom);
++		if (!ai)
+ 			goto out;
+ 	}
+ 
+@@ -854,7 +1097,7 @@ static void nfsd_export(FILE *f)
+ 			dump_to_cache(f, dom, path, NULL);
+ 		}
+ 	} else {
+-		dump_to_cache(f, dom, path, NULL);
++		dump_to_cache(f, dom, path, lookup_junction(path));
+ 	}
+  out:
+ 	xlog(D_CALL, "nfsd_export: found %p path %s", found, path ? path : NULL);
+diff --git a/utils/mountd/fsloc.c b/utils/mountd/fsloc.c
+index e2add2d..bc737d1 100644
+--- a/utils/mountd/fsloc.c
++++ b/utils/mountd/fsloc.c
+@@ -40,12 +40,12 @@ static void replicas_print(struct servers *sp)
+ {
+ 	int i;
+ 	if (!sp) {
+-		xlog(L_NOTICE, "NULL replicas pointer\n");
++		xlog(L_NOTICE, "NULL replicas pointer");
+ 		return;
+ 	}
+-	xlog(L_NOTICE, "replicas listsize=%i\n", sp->h_num);
++	xlog(L_NOTICE, "replicas listsize=%i", sp->h_num);
+ 	for (i=0; i<sp->h_num; i++) {
+-		xlog(L_NOTICE, "    %s:%s\n",
++		xlog(L_NOTICE, "    %s:%s",
+ 		       sp->h_mp[i]->h_host, sp->h_mp[i]->h_path);
+ 	}
+ }
+@@ -120,23 +120,37 @@ static struct servers *parse_list(char **list)
+  */
+ static struct servers *method_list(char *data)
+ {
+-	char *copy, *ptr=data;
++	char *copy, *ptr=data, *p;
+ 	char **list;
+ 	int i, listsize;
+ 	struct servers *rv=NULL;
++	bool v6esc = false;
+ 
+-	xlog(L_NOTICE, "method_list(%s)\n", data);
++	xlog(L_NOTICE, "method_list(%s)", data);
+ 	for (ptr--, listsize=1; ptr; ptr=index(ptr, ':'), listsize++)
+ 		ptr++;
+ 	list = malloc(listsize * sizeof(char *));
+ 	copy = strdup(data);
+ 	if (copy)
+-		xlog(L_NOTICE, "converted to %s\n", copy);
++		xlog(L_NOTICE, "converted to %s", copy);
+ 	if (list && copy) {
+ 		ptr = copy;
+-		for (i=0; i<listsize; i++) {
+-			list[i] = strsep(&ptr, ":");
++		for (p = ptr, i = 0; *p && i < listsize; p++) {
++			if (*p == '[')
++				v6esc = true;
++			else if (*p == ']')
++				v6esc = false;
++
++			if (!v6esc && *p == ':') {
++				*p = '\0';
++				if (*ptr)
++					list[i++] = ptr;
++				ptr = p + 1;
++			}
+ 		}
++		if (*ptr)
++			list[i++] = ptr;
++		list[i] = NULL;
+ 		rv = parse_list(list);
+ 	}
+ 	free(copy);
+diff --git a/utils/mountd/mountd.h b/utils/mountd/mountd.h
+index 4c184d2..6d358a7 100644
+--- a/utils/mountd/mountd.h
++++ b/utils/mountd/mountd.h
+@@ -56,4 +56,13 @@ struct nfs_fh_len *
+ 		cache_get_filehandle(nfs_export *exp, int len, char *p);
+ int		cache_export(nfs_export *exp, char *path);
+ 
++bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai);
++bool namelist_client_matches(nfs_export *exp, char *dom);
++bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai);
++
++static inline bool is_ipaddr_client(char *dom)
++{
++	return dom[0] == '$';
++}
++
+ #endif /* MOUNTD_H */
+diff --git a/utils/mountd/v4root.c b/utils/mountd/v4root.c
+index c33a5a9..708eb61 100644
+--- a/utils/mountd/v4root.c
++++ b/utils/mountd/v4root.c
+@@ -46,6 +46,7 @@ static nfs_export pseudo_root = {
+ 		.e_nsqgids = 0,
+ 		.e_fsid = 0,
+ 		.e_mountpoint = NULL,
++		.e_ttl = DEFAULT_TTL,
+ 	},
+ 	.m_exported = 0,
+ 	.m_xtabent = 1,
+@@ -83,7 +84,7 @@ v4root_create(char *path, nfs_export *export)
+ 	struct exportent *curexp = &export->m_export;
+ 
+ 	dupexportent(&eep, &pseudo_root.m_export);
+-	eep.e_hostname = strdup(curexp->e_hostname);
++	eep.e_hostname = curexp->e_hostname;
+ 	strncpy(eep.e_path, path, sizeof(eep.e_path));
+ 	if (strcmp(path, "/") != 0)
+ 		eep.e_flags &= ~NFSEXP_FSID;
+@@ -149,13 +150,13 @@ static int v4root_add_parents(nfs_export *exp)
+ 				"pseudo export for '%s'", exp->m_export.e_path);
+ 		return -ENOMEM;
+ 	}
+-	for (ptr = path + 1; ptr; ptr = strchr(ptr, '/')) {
++	for (ptr = path; ptr; ptr = strchr(ptr, '/')) {
+ 		int ret;
+ 		char saved;
+ 
+ 		saved = *ptr;
+ 		*ptr = '\0';
+-		ret = pseudofs_update(hostname, path, exp);
++		ret = pseudofs_update(hostname, *path ? path : "/", exp);
+ 		if (ret)
+ 			return ret;
+ 		*ptr = saved;
+@@ -192,6 +193,13 @@ v4root_set()
+ 				 */
+ 				continue;
+ 
++			if (strcmp(exp->m_export.e_path, "/") == 0 &&
++			    !(exp->m_export.e_flags & NFSEXP_FSID)) {
++				/* Force '/' to be exported as fsid == 0*/
++				exp->m_export.e_flags |= NFSEXP_FSID;
++				exp->m_export.e_fsid = 0;
++			}
++
+ 			v4root_add_parents(exp);
+ 			/* XXX: error handling! */
+ 		}
+diff --git a/utils/nfsd/Makefile.am b/utils/nfsd/Makefile.am
+index c4c6fb0..1536065 100644
+--- a/utils/nfsd/Makefile.am
++++ b/utils/nfsd/Makefile.am
+@@ -8,7 +8,7 @@ KPREFIX		= @kprefix@
+ sbin_PROGRAMS	= nfsd
+ 
+ nfsd_SOURCES = nfsd.c nfssvc.c
+-nfsd_LDADD = ../../support/nfs/libnfs.a
++nfsd_LDADD = ../../support/nfs/libnfs.a $(LIBTIRPC)
+ 
+ MAINTAINERCLEANFILES = Makefile.in
+ 
+diff --git a/utils/nfsd/nfsd.c b/utils/nfsd/nfsd.c
+index 8bc5d3a..2a3f5cc 100644
+--- a/utils/nfsd/nfsd.c
++++ b/utils/nfsd/nfsd.c
+@@ -27,6 +27,10 @@
+ #include "nfssvc.h"
+ #include "xlog.h"
+ 
++#ifndef NFSD_NPROC
++#define NFSD_NPROC 8
++#endif
++
+ static void	usage(const char *);
+ 
+ static struct option longopts[] =
+@@ -90,7 +94,7 @@ nfsd_enable_protos(unsigned int *proto4, unsigned int *proto6)
+ int
+ main(int argc, char **argv)
+ {
+-	int	count = 1, c, error = 0, portnum = 0, fd, found_one;
++	int	count = NFSD_NPROC, c, error = 0, portnum = 0, fd, found_one;
+ 	char *p, *progname, *port;
+ 	char *haddr = NULL;
+ 	int	socket_up = 0;
+diff --git a/utils/nfsd/nfsd.man b/utils/nfsd/nfsd.man
+index d8988d2..1cf9296 100644
+--- a/utils/nfsd/nfsd.man
++++ b/utils/nfsd/nfsd.man
+@@ -38,7 +38,7 @@ request on all known network addresses.  This may change in future
+ releases of the Linux Kernel.
+ .TP
+ .B \-p " or " \-\-port  port
+-specify a diferent port to listen on for NFS requests. By default,
++specify a different port to listen on for NFS requests. By default,
+ .B rpc.nfsd
+ will listen on port 2049.
+ .TP
+diff --git a/utils/nfsdcld/Makefile.am b/utils/nfsdcld/Makefile.am
+new file mode 100644
+index 0000000..f320dff
+--- /dev/null
++++ b/utils/nfsdcld/Makefile.am
+@@ -0,0 +1,14 @@
++## Process this file with automake to produce Makefile.in
++
++man8_MANS	= nfsdcld.man
++EXTRA_DIST	= $(man8_MANS)
++
++AM_CFLAGS	+= -D_LARGEFILE64_SOURCE
++sbin_PROGRAMS	= nfsdcld
++
++nfsdcld_SOURCES = nfsdcld.c sqlite.c
++
++nfsdcld_LDADD = ../../support/nfs/libnfs.a $(LIBEVENT) $(LIBSQLITE)
++
++MAINTAINERCLEANFILES = Makefile.in
++
+diff --git a/utils/nfsdcld/nfsdcld.c b/utils/nfsdcld/nfsdcld.c
+new file mode 100644
+index 0000000..2f0b004
+--- /dev/null
++++ b/utils/nfsdcld/nfsdcld.c
+@@ -0,0 +1,527 @@
++/*
++ * nfsdcld.c -- NFSv4 client name tracking daemon
++ *
++ * Copyright (C) 2011  Red Hat, Jeff Layton <jlayton at 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 /* HAVE_CONFIG_H */
++
++#include <errno.h>
++#include <event.h>
++#include <stdbool.h>
++#include <getopt.h>
++#include <string.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++#include <fcntl.h>
++#include <unistd.h>
++#include <libgen.h>
++#include <sys/inotify.h>
++
++#include "xlog.h"
++#include "nfslib.h"
++#include "cld.h"
++#include "sqlite.h"
++
++#ifndef PIPEFS_DIR
++#define PIPEFS_DIR NFS_STATEDIR "/rpc_pipefs"
++#endif
++
++#define DEFAULT_CLD_PATH	PIPEFS_DIR "/nfsd/cld"
++
++#define UPCALL_VERSION		1
++
++/* private data structures */
++struct cld_client {
++	int			cl_fd;
++	struct event		cl_event;
++	struct cld_msg	cl_msg;
++};
++
++/* global variables */
++static char *pipepath = DEFAULT_CLD_PATH;
++static int 		inotify_fd = -1;
++static struct event	pipedir_event;
++
++static struct option longopts[] =
++{
++	{ "help", 0, NULL, 'h' },
++	{ "foreground", 0, NULL, 'F' },
++	{ "debug", 0, NULL, 'd' },
++	{ "pipe", 1, NULL, 'p' },
++	{ "storagedir", 1, NULL, 's' },
++	{ NULL, 0, 0, 0 },
++};
++
++/* forward declarations */
++static void cldcb(int UNUSED(fd), short which, void *data);
++
++static void
++usage(char *progname)
++{
++	printf("%s [ -hFd ] [ -p pipe ] [ -s dir ]\n", progname);
++}
++
++#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX)
++
++static int
++cld_pipe_open(struct cld_client *clnt)
++{
++	int fd;
++
++	xlog(D_GENERAL, "%s: opening upcall pipe %s", __func__, pipepath);
++	fd = open(pipepath, O_RDWR, 0);
++	if (fd < 0) {
++		xlog(L_ERROR, "%s: open of %s failed: %m", __func__, pipepath);
++		return -errno;
++	}
++
++	if (clnt->cl_event.ev_flags & EVLIST_INIT)
++		event_del(&clnt->cl_event);
++	if (clnt->cl_fd >= 0)
++		close(clnt->cl_fd);
++
++	clnt->cl_fd = fd;
++	event_set(&clnt->cl_event, clnt->cl_fd, EV_READ, cldcb, clnt);
++	/* event_add is done by the caller */
++	return 0;
++}
++
++static void
++cld_inotify_cb(int UNUSED(fd), short which, void *data)
++{
++	int ret;
++	size_t elen;
++	ssize_t rret;
++	char evbuf[INOTIFY_EVENT_MAX];
++	char *dirc = NULL, *pname;
++	struct inotify_event *event = (struct inotify_event *)evbuf;
++	struct cld_client *clnt = data;
++
++	if (which != EV_READ)
++		return;
++
++	xlog(D_GENERAL, "%s: called for EV_READ", __func__);
++
++	dirc = strndup(pipepath, PATH_MAX);
++	if (!dirc) {
++		xlog(L_ERROR, "%s: unable to allocate memory", __func__);
++		goto out;
++	}
++
++	rret = read(inotify_fd, evbuf, INOTIFY_EVENT_MAX);
++	if (rret < 0) {
++		xlog(L_ERROR, "%s: read from inotify fd failed: %m", __func__);
++		goto out;
++	}
++
++	/* check to see if we have a filename in the evbuf */
++	if (!event->len) {
++		xlog(D_GENERAL, "%s: no filename in inotify event", __func__);
++		goto out;
++	}
++
++	pname = basename(dirc);
++	elen = strnlen(event->name, event->len);
++
++	/* does the filename match our pipe? */
++	if (strlen(pname) != elen || memcmp(pname, event->name, elen)) {
++		xlog(D_GENERAL, "%s: wrong filename (%s)", __func__,
++				event->name);
++		goto out;
++	}
++
++	ret = cld_pipe_open(clnt);
++	switch (ret) {
++	case 0:
++		/* readd the event for the cl_event pipe */
++		event_add(&clnt->cl_event, NULL);
++		break;
++	case -ENOENT:
++		/* pipe must have disappeared, wait for it to come back */
++		goto out;
++	default:
++		/* anything else is fatal */
++		xlog(L_FATAL, "%s: unable to open new pipe (%d). Aborting.",
++			ret, __func__);
++		exit(ret);
++	}
++
++out:
++	event_add(&pipedir_event, NULL);
++	free(dirc);
++}
++
++static int
++cld_inotify_setup(void)
++{
++	int ret;
++	char *dirc, *dname;
++
++	dirc = strndup(pipepath, PATH_MAX);
++	if (!dirc) {
++		xlog_err("%s: unable to allocate memory", __func__);
++		ret = -ENOMEM;
++		goto out_free;
++	}
++
++	dname = dirname(dirc);
++
++	inotify_fd = inotify_init();
++	if (inotify_fd < 0) {
++		xlog_err("%s: inotify_init failed: %m", __func__);
++		ret = -errno;
++		goto out_free;
++	}
++
++	ret = inotify_add_watch(inotify_fd, dname, IN_CREATE);
++	if (ret < 0) {
++		xlog_err("%s: inotify_add_watch failed: %m", __func__);
++		ret = -errno;
++		goto out_err;
++	}
++
++out_free:
++	free(dirc);
++	return 0;
++out_err:
++	close(inotify_fd);
++	goto out_free;
++}
++
++/*
++ * Set an inotify watch on the directory that should contain the pipe, and then
++ * try to open it. If it fails with anything but -ENOENT, return the error
++ * immediately.
++ *
++ * If it succeeds, then set up the pipe event handler. At that point, set up
++ * the inotify event handler and go ahead and return success.
++ */
++static int
++cld_pipe_init(struct cld_client *clnt)
++{
++	int ret;
++
++	xlog(D_GENERAL, "%s: init pipe handlers", __func__);
++
++	ret = cld_inotify_setup();
++	if (ret != 0)
++		goto out;
++
++	clnt->cl_fd = -1;
++	ret = cld_pipe_open(clnt);
++	switch (ret) {
++	case 0:
++		/* add the event and we're good to go */
++		event_add(&clnt->cl_event, NULL);
++		break;
++	case -ENOENT:
++		/* ignore this error -- cld_inotify_cb will handle it */
++		ret = 0;
++		break;
++	default:
++		/* anything else is fatal */
++		close(inotify_fd);
++		goto out;
++	}
++
++	/* set event for inotify read */
++	event_set(&pipedir_event, inotify_fd, EV_READ, cld_inotify_cb, clnt);
++	event_add(&pipedir_event, NULL);
++out:
++	return ret;
++}
++
++static void
++cld_not_implemented(struct cld_client *clnt)
++{
++	int ret;
++	ssize_t bsize, wsize;
++	struct cld_msg *cmsg = &clnt->cl_msg;
++
++	xlog(D_GENERAL, "%s: downcalling with not implemented error", __func__);
++
++	/* set up reply */
++	cmsg->cm_status = -EOPNOTSUPP;
++
++	bsize = sizeof(*cmsg);
++
++	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
++	if (wsize != bsize)
++		xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m",
++			 __func__, wsize);
++
++	/* reopen pipe, just to be sure */
++	ret = cld_pipe_open(clnt);
++	if (ret) {
++		xlog(L_FATAL, "%s: unable to reopen pipe: %d", __func__, ret);
++		exit(ret);
++	}
++}
++
++static void
++cld_create(struct cld_client *clnt)
++{
++	int ret;
++	ssize_t bsize, wsize;
++	struct cld_msg *cmsg = &clnt->cl_msg;
++
++	xlog(D_GENERAL, "%s: create client record.", __func__);
++
++	ret = sqlite_insert_client(cmsg->cm_u.cm_name.cn_id,
++				   cmsg->cm_u.cm_name.cn_len);
++
++	cmsg->cm_status = ret ? -EREMOTEIO : ret;
++
++	bsize = sizeof(*cmsg);
++
++	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
++	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
++	if (wsize != bsize) {
++		xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m",
++			 __func__, wsize);
++		ret = cld_pipe_open(clnt);
++		if (ret) {
++			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
++					__func__, ret);
++			exit(ret);
++		}
++	}
++}
++
++static void
++cld_remove(struct cld_client *clnt)
++{
++	int ret;
++	ssize_t bsize, wsize;
++	struct cld_msg *cmsg = &clnt->cl_msg;
++
++	xlog(D_GENERAL, "%s: remove client record.", __func__);
++
++	ret = sqlite_remove_client(cmsg->cm_u.cm_name.cn_id,
++				   cmsg->cm_u.cm_name.cn_len);
++
++	cmsg->cm_status = ret ? -EREMOTEIO : ret;
++
++	bsize = sizeof(*cmsg);
++
++	xlog(D_GENERAL, "%s: downcall with status %d", __func__,
++			cmsg->cm_status);
++	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
++	if (wsize != bsize) {
++		xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m",
++			 __func__, wsize);
++		ret = cld_pipe_open(clnt);
++		if (ret) {
++			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
++					__func__, ret);
++			exit(ret);
++		}
++	}
++}
++
++static void
++cld_check(struct cld_client *clnt)
++{
++	int ret;
++	ssize_t bsize, wsize;
++	struct cld_msg *cmsg = &clnt->cl_msg;
++
++	xlog(D_GENERAL, "%s: check client record", __func__);
++
++	ret = sqlite_check_client(cmsg->cm_u.cm_name.cn_id,
++				  cmsg->cm_u.cm_name.cn_len);
++
++	/* set up reply */
++	cmsg->cm_status = ret ? -EACCES : ret;
++
++	bsize = sizeof(*cmsg);
++
++	xlog(D_GENERAL, "%s: downcall with status %d", __func__,
++			cmsg->cm_status);
++	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
++	if (wsize != bsize) {
++		xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m",
++			 __func__, wsize);
++		ret = cld_pipe_open(clnt);
++		if (ret) {
++			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
++					__func__, ret);
++			exit(ret);
++		}
++	}
++}
++
++static void
++cld_gracedone(struct cld_client *clnt)
++{
++	int ret;
++	ssize_t bsize, wsize;
++	struct cld_msg *cmsg = &clnt->cl_msg;
++
++	xlog(D_GENERAL, "%s: grace done. cm_gracetime=%ld", __func__,
++			cmsg->cm_u.cm_gracetime);
++
++	ret = sqlite_remove_unreclaimed(cmsg->cm_u.cm_gracetime);
++
++	/* set up reply: downcall with 0 status */
++	cmsg->cm_status = ret ? -EREMOTEIO : ret;
++
++	bsize = sizeof(*cmsg);
++
++	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
++	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
++	if (wsize != bsize) {
++		xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m",
++			 __func__, wsize);
++		ret = cld_pipe_open(clnt);
++		if (ret) {
++			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
++					__func__, ret);
++			exit(ret);
++		}
++	}
++}
++
++static void
++cldcb(int UNUSED(fd), short which, void *data)
++{
++	ssize_t len;
++	struct cld_client *clnt = data;
++	struct cld_msg *cmsg = &clnt->cl_msg;
++
++	if (which != EV_READ)
++		goto out;
++
++	len = atomicio(read, clnt->cl_fd, cmsg, sizeof(*cmsg));
++	if (len <= 0) {
++		xlog(L_ERROR, "%s: pipe read failed: %m", __func__);
++		cld_pipe_open(clnt);
++		goto out;
++	}
++
++	if (cmsg->cm_vers != UPCALL_VERSION) {
++		xlog(L_ERROR, "%s: unsupported upcall version: %hu",
++				cmsg->cm_vers);
++		cld_pipe_open(clnt);
++		goto out;
++	}
++
++	switch(cmsg->cm_cmd) {
++	case Cld_Create:
++		cld_create(clnt);
++		break;
++	case Cld_Remove:
++		cld_remove(clnt);
++		break;
++	case Cld_Check:
++		cld_check(clnt);
++		break;
++	case Cld_GraceDone:
++		cld_gracedone(clnt);
++		break;
++	default:
++		xlog(L_WARNING, "%s: command %u is not yet implemented",
++				__func__, cmsg->cm_cmd);
++		cld_not_implemented(clnt);
++	}
++out:
++	event_add(&clnt->cl_event, NULL);
++}
++
++int
++main(int argc, char **argv)
++{
++	char arg;
++	int rc = 0;
++	bool foreground = false;
++	char *progname;
++	char *storagedir = NULL;
++	struct cld_client clnt;
++
++	memset(&clnt, 0, sizeof(clnt));
++
++	progname = strdup(basename(argv[0]));
++	if (!progname) {
++		fprintf(stderr, "%s: unable to allocate memory.\n", argv[0]);
++		return 1;
++	}
++
++	event_init();
++	xlog_syslog(0);
++	xlog_stderr(1);
++
++	/* process command-line options */
++	while ((arg = getopt_long(argc, argv, "hdFp:s:", longopts,
++				  NULL)) != EOF) {
++		switch (arg) {
++		case 'd':
++			xlog_config(D_ALL, 1);
++			break;
++		case 'F':
++			foreground = true;
++			break;
++		case 'p':
++			pipepath = optarg;
++			break;
++		case 's':
++			storagedir = optarg;
++			break;
++		default:
++			usage(progname);
++			return 0;
++		}
++	}
++
++
++	xlog_open(progname);
++	if (!foreground) {
++		xlog_syslog(1);
++		xlog_stderr(0);
++		rc = daemon(0, 0);
++		if (rc) {
++			xlog(L_ERROR, "Unable to daemonize: %m");
++			goto out;
++		}
++	}
++
++	/* set up storage db */
++	rc = sqlite_maindb_init(storagedir);
++	if (rc) {
++		xlog(L_ERROR, "Failed to open main database: %d", rc);
++		goto out;
++	}
++
++	/* set up event handler */
++	rc = cld_pipe_init(&clnt);
++	if (rc)
++		goto out;
++
++	xlog(D_GENERAL, "%s: Starting event dispatch handler.", __func__);
++	rc = event_dispatch();
++	if (rc < 0)
++		xlog(L_ERROR, "%s: event_dispatch failed: %m", __func__);
++
++	close(clnt.cl_fd);
++	close(inotify_fd);
++out:
++	free(progname);
++	return rc;
++}
+diff --git a/utils/nfsdcld/nfsdcld.man b/utils/nfsdcld/nfsdcld.man
+new file mode 100644
+index 0000000..bad5f34
+--- /dev/null
++++ b/utils/nfsdcld/nfsdcld.man
+@@ -0,0 +1,180 @@
++.\" Automatically generated by Pod::Man 2.22 (Pod::Simple 3.13)
++.\"
++.\" Standard preamble:
++.\" ========================================================================
++.de Sp \" Vertical space (when we can't use .PP)
++.if t .sp .5v
++.if n .sp
++..
++.de Vb \" Begin verbatim text
++.ft CW
++.nf
++.ne \\$1
++..
++.de Ve \" End verbatim text
++.ft R
++.fi
++..
++.\" Set up some character translations and predefined strings.  \*(-- will
++.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
++.\" double quote, and \*(R" will give a right double quote.  \*(C+ will
++.\" give a nicer C++.  Capital omega is used to do unbreakable dashes and
++.\" therefore won't be available.  \*(C` and \*(C' expand to `' in nroff,
++.\" nothing in troff, for use with C<>.
++.tr \(*W-
++.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
++.ie n \{\
++.    ds -- \(*W-
++.    ds PI pi
++.    if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
++.    if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\"  diablo 12 pitch
++.    ds L" ""
++.    ds R" ""
++.    ds C` ""
++.    ds C' ""
++'br\}
++.el\{\
++.    ds -- \|\(em\|
++.    ds PI \(*p
++.    ds L" ``
++.    ds R" ''
++'br\}
++.\"
++.\" Escape single quotes in literal strings from groff's Unicode transform.
++.ie \n(.g .ds Aq \(aq
++.el       .ds Aq '
++.\"
++.\" If the F register is turned on, we'll generate index entries on stderr for
++.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
++.\" entries marked with X<> in POD.  Of course, you'll have to process the
++.\" output yourself in some meaningful fashion.
++.ie \nF \{\
++.    de IX
++.    tm Index:\\$1\t\\n%\t"\\$2"
++..
++.    nr % 0
++.    rr F
++.\}
++.el \{\
++.    de IX
++..
++.\}
++.\"
++.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
++.\" Fear.  Run.  Save yourself.  No user-serviceable parts.
++.    \" fudge factors for nroff and troff
++.if n \{\
++.    ds #H 0
++.    ds #V .8m
++.    ds #F .3m
++.    ds #[ \f1
++.    ds #] \fP
++.\}
++.if t \{\
++.    ds #H ((1u-(\\\\n(.fu%2u))*.13m)
++.    ds #V .6m
++.    ds #F 0
++.    ds #[ \&
++.    ds #] \&
++.\}
++.    \" simple accents for nroff and troff
++.if n \{\
++.    ds ' \&
++.    ds ` \&
++.    ds ^ \&
++.    ds , \&
++.    ds ~ ~
++.    ds /
++.\}
++.if t \{\
++.    ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
++.    ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
++.    ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
++.    ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
++.    ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
++.    ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
++.\}
++.    \" troff and (daisy-wheel) nroff accents
++.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
++.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
++.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
++.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
++.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
++.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
++.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
++.ds ae a\h'-(\w'a'u*4/10)'e
++.ds Ae A\h'-(\w'A'u*4/10)'E
++.    \" corrections for vroff
++.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
++.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
++.    \" for low resolution devices (crt and lpr)
++.if \n(.H>23 .if \n(.V>19 \
++\{\
++.    ds : e
++.    ds 8 ss
++.    ds o a
++.    ds d- d\h'-1'\(ga
++.    ds D- D\h'-1'\(hy
++.    ds th \o'bp'
++.    ds Th \o'LP'
++.    ds ae ae
++.    ds Ae AE
++.\}
++.rm #[ #] #H #V #F C
++.\" ========================================================================
++.\"
++.IX Title "NFSDCLD 8"
++.TH NFSDCLD 8 "2011-12-21" "" ""
++.\" For nroff, turn off justification.  Always turn off hyphenation; it makes
++.\" way too many mistakes in technical documents.
++.if n .ad l
++.nh
++.SH "NAME"
++nfsdcld \- NFSv4 Client Tracking Daemon
++.SH "SYNOPSIS"
++.IX Header "SYNOPSIS"
++nfsdcld [\-d] [\-F] [\-p path] [\-s stable storage dir]
++.SH "DESCRIPTION"
++.IX Header "DESCRIPTION"
++nfsdcld is the NFSv4 client tracking daemon. It is not necessary to run
++this daemon on machines that are not acting as NFSv4 servers.
++.PP
++When a network partition is combined with a server reboot, there are
++edge conditions that can cause the server to grant lock reclaims when
++other clients have taken conflicting locks in the interim. A more detailed
++explanation of this issue is described in \s-1RFC\s0 3530, section 8.6.3.
++.PP
++In order to prevent these problems, the server must track a small amount
++of per-client information on stable storage. This daemon provides the
++userspace piece of that functionality.
++.SH "OPTIONS"
++.IX Header "OPTIONS"
++.IP "\fB\-d\fR, \fB\-\-debug\fR" 4
++.IX Item "-d, --debug"
++Enable debug level logging.
++.IP "\fB\-F\fR, \fB\-\-foreground\fR" 4
++.IX Item "-F, --foreground"
++Runs the daemon in the foreground and prints all output to stderr
++.IP "\fB\-p\fR \fIpipe\fR, \fB\-\-pipe\fR=\fIpipe\fR" 4
++.IX Item "-p pipe, --pipe=pipe"
++Location of the \*(L"cld\*(R" upcall pipe. The default value is
++\&\fI/var/lib/nfs/rpc_pipefs/nfsd/cld\fR. If the pipe does not exist when the
++daemon starts then it will wait for it to be created.
++.IP "\fB\-s\fR \fIstoragedir\fR, \fB\-\-storagedir\fR=\fIstorage_dir\fR" 4
++.IX Item "-s storagedir, --storagedir=storage_dir"
++Directory where stable storage information should be kept. The default
++value is \fI/var/lib/nfs/nfsdcld\fR.
++.SH "NOTES"
++.IX Header "NOTES"
++The Linux kernel NFSv4 server has historically tracked this information
++on stable storage by manipulating information on the filesystem
++directly, in the directory to which \fI/proc/fs/nfsd/nfsv4recoverydir\fR
++points.
++.PP
++This daemon requires a kernel that supports the nfsdcld upcall. If the
++kernel does not support the new upcall, or is using the legacy client
++name tracking code then it will not create the pipe that nfsdcld uses to
++talk to the kernel.
++.SH "AUTHORS"
++.IX Header "AUTHORS"
++The nfsdcld daemon was developed by Jeff Layton <jlayton at redhat.com>.
+diff --git a/utils/nfsdcld/sqlite.c b/utils/nfsdcld/sqlite.c
+new file mode 100644
+index 0000000..9e35774
+--- /dev/null
++++ b/utils/nfsdcld/sqlite.c
+@@ -0,0 +1,390 @@
++/*
++ * Copyright (C) 2011  Red Hat, Jeff Layton <jlayton at 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.
++ */
++
++/*
++ * Explanation:
++ *
++ * This file contains the code to manage the sqlite backend database for the
++ * clstated upcall daemon.
++ *
++ * The main database is called main.sqlite and contains the following tables:
++ *
++ * parameters: simple key/value pairs for storing database info
++ *
++ * clients: one column containing a BLOB with the as sent by the client
++ * 	    and a timestamp (in epoch seconds) of when the record was
++ * 	    established
++ *
++ * FIXME: should we also record the fsid being accessed?
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif /* HAVE_CONFIG_H */
++
++#include <dirent.h>
++#include <errno.h>
++#include <event.h>
++#include <stdbool.h>
++#include <string.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++#include <fcntl.h>
++#include <unistd.h>
++#include <sqlite3.h>
++#include <linux/limits.h>
++
++#include "xlog.h"
++
++#define CLD_SQLITE_SCHEMA_VERSION 1
++
++#ifndef CLD_SQLITE_TOPDIR
++#define CLD_SQLITE_TOPDIR NFS_STATEDIR "/nfsdcld"
++#endif
++
++/* in milliseconds */
++#define CLD_SQLITE_BUSY_TIMEOUT 10000
++
++/* private data structures */
++
++/* global variables */
++
++/* top level DB directory */
++static char *sqlite_topdir;
++
++/* reusable pathname and sql command buffer */
++static char buf[PATH_MAX];
++
++/* global database handle */
++static sqlite3 *dbh;
++
++/* forward declarations */
++
++/* make a directory, ignoring EEXIST errors unless it's not a directory */
++static int
++mkdir_if_not_exist(char *dirname)
++{
++	int ret;
++	struct stat statbuf;
++
++	ret = mkdir(dirname, S_IRWXU);
++	if (ret && errno != EEXIST)
++		return -errno;
++
++	ret = stat(dirname, &statbuf);
++	if (ret)
++		return -errno;
++
++	if (!S_ISDIR(statbuf.st_mode))
++		ret = -ENOTDIR;
++
++	return ret;
++}
++
++/*
++ * Open the "main" database, and attempt to initialize it by creating the
++ * parameters table and inserting the schema version into it. Ignore any errors
++ * from that, and then attempt to select the version out of it again. If the
++ * version appears wrong, then assume that the DB is corrupt or has been
++ * upgraded, and return an error. If all of that works, then attempt to create
++ * the "clients" table.
++ */
++int
++sqlite_maindb_init(char *topdir)
++{
++	int ret;
++	char *err = NULL;
++	sqlite3_stmt *stmt = NULL;
++
++	sqlite_topdir = topdir ? topdir : CLD_SQLITE_TOPDIR;
++
++	ret = mkdir_if_not_exist(sqlite_topdir);
++	if (ret)
++		return ret;
++
++	ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", sqlite_topdir);
++	if (ret < 0)
++		return ret;
++
++	buf[PATH_MAX - 1] = '\0';
++
++	ret = sqlite3_open(buf, &dbh);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "Unable to open main database: %d", ret);
++		return ret;
++	}
++
++	ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "Unable to set sqlite busy timeout: %d", ret);
++		goto out_err;
++	}
++
++	/* Try to create table */
++	ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS parameters "
++				"(key TEXT PRIMARY KEY, value TEXT);",
++				NULL, NULL, &err);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "Unable to create parameter table: %d", ret);
++		goto out_err;
++	}
++
++	/* insert version into table -- ignore error if it fails */
++	ret = snprintf(buf, sizeof(buf),
++		       "INSERT OR IGNORE INTO parameters values (\"version\", "
++		       "\"%d\");", CLD_SQLITE_SCHEMA_VERSION);
++	if (ret < 0) {
++		goto out_err;
++	} else if ((size_t)ret >= sizeof(buf)) {
++		ret = -EINVAL;
++		goto out_err;
++	}
++
++	ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "Unable to insert into parameter table: %d",
++				ret);
++		goto out_err;
++	}
++
++	ret = sqlite3_prepare_v2(dbh,
++		"SELECT value FROM parameters WHERE key == \"version\";",
++		 -1, &stmt, NULL);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "Unable to prepare select statement: %d", ret);
++		goto out_err;
++	}
++
++	/* check schema version */
++	ret = sqlite3_step(stmt);
++	if (ret != SQLITE_ROW) {
++		xlog(L_ERROR, "Select statement execution failed: %s",
++				sqlite3_errmsg(dbh));
++		goto out_err;
++	}
++
++	/* process SELECT result */
++	ret = sqlite3_column_int(stmt, 0);
++	if (ret != CLD_SQLITE_SCHEMA_VERSION) {
++		xlog(L_ERROR, "Unsupported database schema version! "
++			"Expected %d, got %d.",
++			CLD_SQLITE_SCHEMA_VERSION, ret);
++		ret = -EINVAL;
++		goto out_err;
++	}
++
++	/* now create the "clients" table */
++	ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS clients "
++				"(id BLOB PRIMARY KEY, time INTEGER);",
++				NULL, NULL, &err);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "Unable to create clients table: %s", err);
++		goto out_err;
++	}
++
++	sqlite3_free(err);
++	sqlite3_finalize(stmt);
++	return 0;
++
++out_err:
++	if (err) {
++		xlog(L_ERROR, "sqlite error: %s", err);
++		sqlite3_free(err);
++	}
++	sqlite3_finalize(stmt);
++	sqlite3_close(dbh);
++	return ret;
++}
++
++/*
++ * Create a client record
++ *
++ * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0)
++ */
++int
++sqlite_insert_client(const unsigned char *clname, const size_t namelen)
++{
++	int ret;
++	sqlite3_stmt *stmt = NULL;
++
++	ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients VALUES "
++				      "(?, strftime('%s', 'now'));", -1,
++					&stmt, NULL);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "%s: insert statement prepare failed: %s",
++			__func__, sqlite3_errmsg(dbh));
++		return ret;
++	}
++
++	ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
++				SQLITE_STATIC);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
++				sqlite3_errmsg(dbh));
++		goto out_err;
++	}
++
++	ret = sqlite3_step(stmt);
++	if (ret == SQLITE_DONE)
++		ret = SQLITE_OK;
++	else
++		xlog(L_ERROR, "%s: unexpected return code from insert: %s",
++				__func__, sqlite3_errmsg(dbh));
++
++out_err:
++	xlog(D_GENERAL, "%s: returning %d", __func__, ret);
++	sqlite3_finalize(stmt);
++	return ret;
++}
++
++/* Remove a client record */
++int
++sqlite_remove_client(const unsigned char *clname, const size_t namelen)
++{
++	int ret;
++	sqlite3_stmt *stmt = NULL;
++
++	ret = sqlite3_prepare_v2(dbh, "DELETE FROM clients WHERE id==?", -1,
++				 &stmt, NULL);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "%s: statement prepare failed: %s",
++				__func__, sqlite3_errmsg(dbh));
++		goto out_err;
++	}
++
++	ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
++				SQLITE_STATIC);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
++				sqlite3_errmsg(dbh));
++		goto out_err;
++	}
++
++	ret = sqlite3_step(stmt);
++	if (ret == SQLITE_DONE)
++		ret = SQLITE_OK;
++	else
++		xlog(L_ERROR, "%s: unexpected return code from delete: %d",
++				__func__, ret);
++
++out_err:
++	xlog(D_GENERAL, "%s: returning %d", __func__, ret);
++	sqlite3_finalize(stmt);
++	return ret;
++}
++
++/*
++ * Is the given clname in the clients table? If so, then update its timestamp
++ * and return success. If the record isn't present, or the update fails, then
++ * return an error.
++ */
++int
++sqlite_check_client(const unsigned char *clname, const size_t namelen)
++{
++	int ret;
++	sqlite3_stmt *stmt = NULL;
++
++	ret = sqlite3_prepare_v2(dbh, "SELECT count(*) FROM clients WHERE "
++				      "id==?", -1, &stmt, NULL);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "%s: unable to prepare update statement: %s",
++				__func__, sqlite3_errmsg(dbh));
++		goto out_err;
++	}
++
++	ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
++				SQLITE_STATIC);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "%s: bind blob failed: %s",
++				__func__, sqlite3_errmsg(dbh));
++		goto out_err;
++	}
++
++	ret = sqlite3_step(stmt);
++	if (ret != SQLITE_ROW) {
++		xlog(L_ERROR, "%s: unexpected return code from select: %d",
++				__func__, ret);
++		goto out_err;
++	}
++
++	ret = sqlite3_column_int(stmt, 0);
++	xlog(D_GENERAL, "%s: select returned %d rows", ret);
++	if (ret != 1) {
++		ret = -EACCES;
++		goto out_err;
++	}
++
++	sqlite3_finalize(stmt);
++	stmt = NULL;
++	ret = sqlite3_prepare_v2(dbh, "UPDATE OR FAIL clients SET "
++				      "time=strftime('%s', 'now') WHERE id==?",
++				 -1, &stmt, NULL);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "%s: unable to prepare update statement: %s",
++				__func__, sqlite3_errmsg(dbh));
++		goto out_err;
++	}
++
++	ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
++				SQLITE_STATIC);
++	if (ret != SQLITE_OK) {
++		xlog(L_ERROR, "%s: bind blob failed: %s",
++				__func__, sqlite3_errmsg(dbh));
++		goto out_err;
++	}
++
++	ret = sqlite3_step(stmt);
++	if (ret == SQLITE_DONE)
++		ret = SQLITE_OK;
++	else
++		xlog(L_ERROR, "%s: unexpected return code from update: %s",
++				__func__, sqlite3_errmsg(dbh));
++
++out_err:
++	xlog(D_GENERAL, "%s: returning %d", __func__, ret);
++	sqlite3_finalize(stmt);
++	return ret;
++}
++
++/*
++ * remove any client records that were not reclaimed since grace_start.
++ */
++int
++sqlite_remove_unreclaimed(time_t grace_start)
++{
++	int ret;
++	char *err = NULL;
++
++	ret = snprintf(buf, sizeof(buf), "DELETE FROM clients WHERE time < %ld",
++			grace_start);
++	if (ret < 0) {
++		return ret;
++	} else if ((size_t)ret >= sizeof(buf)) {
++		ret = -EINVAL;
++		return ret;
++	}
++
++	ret = sqlite3_exec(dbh, buf, NULL, NULL, &err);
++	if (ret != SQLITE_OK)
++		xlog(L_ERROR, "%s: delete failed: %s", __func__, err);
++
++	xlog(D_GENERAL, "%s: returning %d", __func__, ret);
++	sqlite3_free(err);
++	return ret;
++}
+diff --git a/utils/nfsdcld/sqlite.h b/utils/nfsdcld/sqlite.h
+new file mode 100644
+index 0000000..c85e7d6
+--- /dev/null
++++ b/utils/nfsdcld/sqlite.h
+@@ -0,0 +1,29 @@
++/*
++ * Copyright (C) 2011  Red Hat, Jeff Layton <jlayton at 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.
++ */
++
++#ifndef _SQLITE_H_
++#define _SQLITE_H_
++
++int sqlite_maindb_init(char *topdir);
++int sqlite_insert_client(const unsigned char *clname, const size_t namelen);
++int sqlite_remove_client(const unsigned char *clname, const size_t namelen);
++int sqlite_check_client(const unsigned char *clname, const size_t namelen);
++int sqlite_remove_unreclaimed(const time_t grace_start);
++
++#endif /* _SQLITE_H */
+diff --git a/utils/nfsidmap/Makefile.am b/utils/nfsidmap/Makefile.am
+index f837b91..c0675c4 100644
+--- a/utils/nfsidmap/Makefile.am
++++ b/utils/nfsidmap/Makefile.am
+@@ -4,6 +4,6 @@ man8_MANS = nfsidmap.man
+ 
+ sbin_PROGRAMS	= nfsidmap
+ nfsidmap_SOURCES = nfsidmap.c
+-nfsidmap_LDADD = -lnfsidmap -lkeyutils
++nfsidmap_LDADD = $(LIBNFSIDMAP) -lkeyutils ../../support/nfs/libnfs.a
+ 
+ MAINTAINERCLEANFILES = Makefile.in
+diff --git a/utils/nfsidmap/nfsidmap.c b/utils/nfsidmap/nfsidmap.c
+index 2d87381..cf11551 100644
+--- a/utils/nfsidmap/nfsidmap.c
++++ b/utils/nfsidmap/nfsidmap.c
+@@ -3,21 +3,33 @@
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
++#include <errno.h>
+ 
+ #include <pwd.h>
+ #include <grp.h>
+ #include <keyutils.h>
+ #include <nfsidmap.h>
+ 
+-#include <syslog.h>
++#include <unistd.h>
++#include "xlog.h"
+ 
+-/* gcc nfsidmap.c -o nfsidmap -l nfsidmap -l keyutils */
++int verbose = 0;
++char *usage="Usage: %s [-v] [-c || [-u|-g|-r key] || [-t timeout] key desc]";
+ 
+ #define MAX_ID_LEN   11
+ #define IDMAP_NAMESZ 128
+ #define USER  1
+ #define GROUP 0
+ 
++#define PROCKEYS "/proc/keys"
++#ifndef DEFAULT_KEYRING
++#define DEFAULT_KEYRING "id_resolver"
++#endif
++
++static int keyring_clear(char *keyring);
++
++#define UIDKEYS 0x1
++#define GIDKEYS 0x2
+ 
+ /*
+  * Find either a user or group id based on the name at domain string
+@@ -36,9 +48,31 @@ int id_lookup(char *name_at_domain, key_serial_t key, int type)
+ 		rc = nfs4_group_owner_to_gid(name_at_domain, &gid);
+ 		sprintf(id, "%u", gid);
+ 	}
++	if (rc < 0)
++		xlog_err("id_lookup: %s: failed: %m",
++			(type == USER ? "nfs4_owner_to_uid" : "nfs4_group_owner_to_gid"));
+ 
+-	if (rc == 0)
++	if (rc == 0) {
+ 		rc = keyctl_instantiate(key, id, strlen(id) + 1, 0);
++		if (rc < 0) {
++			switch(rc) {
++			case -EDQUOT:
++			case -ENFILE:
++			case -ENOMEM:
++				/*
++			 	 * The keyring is full. Clear the keyring and try again
++			 	 */
++				rc = keyring_clear(DEFAULT_KEYRING);
++				if (rc == 0)
++					rc = keyctl_instantiate(key, id, strlen(id) + 1, 0);
++				break;
++			default:
++				break;
++			}
++		}
++		if (rc < 0)
++			xlog_err("id_lookup: keyctl_instantiate failed: %m");
++	}
+ 
+ 	return rc;
+ }
+@@ -57,6 +91,7 @@ int name_lookup(char *id, key_serial_t key, int type)
+ 	rc = nfs4_get_default_domain(NULL, domain, NFS4_MAX_DOMAIN_LEN);
+ 	if (rc != 0) {
+ 		rc = -1;
++		xlog_err("name_lookup: nfs4_get_default_domain failed: %m");
+ 		goto out;
+ 	}
+ 
+@@ -67,39 +102,206 @@ int name_lookup(char *id, key_serial_t key, int type)
+ 		gid = atoi(id);
+ 		rc = nfs4_gid_to_name(gid, domain, name, IDMAP_NAMESZ);
+ 	}
++	if (rc < 0)
++		xlog_err("name_lookup: %s: failed: %m",
++			(type == USER ? "nfs4_uid_to_name" : "nfs4_gid_to_name"));
+ 
+-	if (rc == 0)
++	if (rc == 0) {
+ 		rc = keyctl_instantiate(key, &name, strlen(name), 0);
+-
++		if (rc < 0)
++			xlog_err("name_lookup: keyctl_instantiate failed: %m");
++	}
+ out:
+ 	return rc;
+ }
++/*
++ * Clear all the keys on the given keyring
++ */
++static int keyring_clear(char *keyring)
++{
++	FILE *fp;
++	char buf[BUFSIZ];
++	key_serial_t key;
++
++	if (keyring == NULL)
++		keyring = DEFAULT_KEYRING;
++
++	if ((fp = fopen(PROCKEYS, "r")) == NULL) {
++		xlog_err("fopen(%s) failed: %m", PROCKEYS);
++		return 1;
++	}
++
++	while(fgets(buf, BUFSIZ, fp) != NULL) {
++		if (strstr(buf, "keyring") == NULL)
++			continue;
++		if (strstr(buf, keyring) == NULL)
++			continue;
++		if (verbose) {
++			*(strchr(buf, '\n')) = '\0';
++			xlog_warn("clearing '%s'", buf);
++		}
++		/*
++		 * The key is the first arugment in the string
++		 */
++		*(strchr(buf, ' ')) = '\0';
++		sscanf(buf, "%x", &key);
++		if (keyctl_clear(key) < 0) {
++			xlog_err("keyctl_clear(0x%x) failed: %m", key);
++			fclose(fp);
++			return 1;
++		}
++		fclose(fp);
++		return 0;
++	}
++	xlog_err("'%s' keyring was not found.", keyring);
++	fclose(fp);
++	return 1;
++}
++/*
++ * Revoke a key 
++ */
++static int key_revoke(char *keystr, int keymask)
++{
++	FILE *fp;
++	char buf[BUFSIZ], *ptr;
++	key_serial_t key;
++	int mask;
++
++	xlog_syslog(0);
++
++	if ((fp = fopen(PROCKEYS, "r")) == NULL) {
++		xlog_err("fopen(%s) failed: %m", PROCKEYS);
++		return 1;
++	}
++
++	while(fgets(buf, BUFSIZ, fp) != NULL) {
++		if (strstr(buf, "keyring") != NULL)
++			continue;
++
++		mask = 0;
++		if ((ptr = strstr(buf, "uid:")) != NULL)
++			mask = UIDKEYS;
++		else if ((ptr = strstr(buf, "gid:")) != NULL)
++			mask = GIDKEYS;
++		else 
++			continue;
++
++		if ((keymask & mask) == 0)
++			continue;
++
++		if (strncmp(ptr+4, keystr, strlen(keystr)) != 0)
++			continue;
++
++		if (verbose) {
++			*(strchr(buf, '\n')) = '\0';
++			xlog_warn("revoking '%s'", buf);
++		}
++		/*
++		 * The key is the first arugment in the string
++		 */
++		*(strchr(buf, ' ')) = '\0';
++		sscanf(buf, "%x", &key);
++
++		if (keyctl_revoke(key) < 0) {
++			xlog_err("keyctl_revoke(0x%x) failed: %m", key);
++			fclose(fp);
++			return 1;
++		}
++
++		keymask &= ~mask;
++		if (keymask == 0) {
++			fclose(fp);
++			return 0;
++		}
++	}
++	xlog_err("'%s' key was not found.", keystr);
++	fclose(fp);
++	return 1;
++}
+ 
+ int main(int argc, char **argv)
+ {
+ 	char *arg;
+ 	char *value;
+ 	char *type;
+-	int rc = 1;
++	int rc = 1, opt;
+ 	int timeout = 600;
+ 	key_serial_t key;
++	char *progname, *keystr = NULL;
++	int clearing = 0, keymask = 0;
++
++	/* Set the basename */
++	if ((progname = strrchr(argv[0], '/')) != NULL)
++		progname++;
++	else
++		progname = argv[0];
+ 
+-	if (argc < 3)
++	xlog_open(progname);
++
++	while ((opt = getopt(argc, argv, "u:g:r:ct:v")) != -1) {
++		switch (opt) {
++		case 'u':
++			keymask = UIDKEYS;
++			keystr = strdup(optarg);
++			break;
++		case 'g':
++			keymask = GIDKEYS;
++			keystr = strdup(optarg);
++			break;
++		case 'r':
++			keymask = GIDKEYS|UIDKEYS;
++			keystr = strdup(optarg);
++			break;
++		case 'c':
++			clearing++;
++			break;
++		case 'v':
++			verbose++;
++			break;
++		case 't':
++			timeout = atoi(optarg);
++			break;
++		default:
++			xlog_warn(usage, progname);
++			break;
++		}
++	}
++
++	if (keystr) {
++		rc = key_revoke(keystr, keymask);
++		return rc;		
++	}
++	if (clearing) {
++		xlog_syslog(0);
++		rc = keyring_clear(DEFAULT_KEYRING);
++		return rc;		
++	}
++
++	xlog_stderr(0);
++	if ((argc - optind) != 2) {
++		xlog_err("Bad arg count. Check /etc/request-key.conf");
++		xlog_warn(usage, progname);
+ 		return 1;
++	}
++
++	if (verbose)
++		nfs4_set_debug(verbose, NULL);
++
++	key = strtol(argv[optind++], NULL, 10);
+ 
+-	arg = malloc(sizeof(char) * strlen(argv[2]) + 1);
+-	strcpy(arg, argv[2]);
++	arg = strdup(argv[optind]);
++	if (arg == NULL) {
++		xlog_err("strdup failed: %m");
++		return 1;
++	}
+ 	type = strtok(arg, ":");
+ 	value = strtok(NULL, ":");
+ 
+-	if (argc == 4) {
+-		timeout = atoi(argv[3]);
+-		if (timeout < 0)
+-			timeout = 0;
++	if (verbose) {
++		xlog_warn("key: 0x%lx type: %s value: %s timeout %ld",
++			key, type, value, timeout);
+ 	}
+ 
+-	key = strtol(argv[1], NULL, 10);
+-
+ 	if (strcmp(type, "uid") == 0)
+ 		rc = id_lookup(value, key, USER);
+ 	else if (strcmp(type, "gid") == 0)
+@@ -109,7 +311,7 @@ int main(int argc, char **argv)
+ 	else if (strcmp(type, "group") == 0)
+ 		rc = name_lookup(value, key, GROUP);
+ 
+-	/* Set timeout to 5 (600 seconds) minutes */
++	/* Set timeout to 10 (600 seconds) minutes */
+ 	if (rc == 0)
+ 		keyctl_set_timeout(key, timeout);
+ 
+diff --git a/utils/nfsidmap/nfsidmap.man b/utils/nfsidmap/nfsidmap.man
+index 2381908..3a3a523 100644
+--- a/utils/nfsidmap/nfsidmap.man
++++ b/utils/nfsidmap/nfsidmap.man
+@@ -5,6 +5,12 @@
+ .TH nfsidmap 5 "1 October 2010"
+ .SH NAME
+ nfsidmap \- The NFS idmapper upcall program
++.SH SYNOPSIS
++.B "nfsidmap [-v] [-t timeout] key desc"
++.br
++.B "nfsidmap [-v] [-c]"
++.br
++.B "nfsidmap [-v] [-u|-g|-r user]"
+ .SH DESCRIPTION
+ The file
+ .I /usr/sbin/nfsidmap
+@@ -12,11 +18,36 @@ is used by the NFS idmapper to translate user and group ids into names, and to
+ translate user and group names into ids. Idmapper uses request-key to perform
+ the upcall and cache the result.
+ .I /usr/sbin/nfsidmap
+-should only be called by request-key, and will perform the translation and
++is called by /sbin/request-key, and will perform the translation and
+ initialize a key with the resulting information.
+ .PP
+-NFS_USE_NEW_IDMAPPER must be selected when configuring the kernel to use this
+-feature.
++.I nfsidmap
++can also used to clear the keyring of all the keys or 
++revoke one particular key.  
++This is useful when the id mappings have failed to due 
++to a lookup error resulting in all the cached uids/gids to be set 
++to the user id nobody.
++.SH OPTIONS
++.TP
++.B -c 
++Clear the keyring of all the keys.
++.TP
++.B -g user
++Revoke the gid key of the given user.
++.TP
++.B -r user
++Revoke both the uid and gid key of the given user.
++.TP
++.B -t timeout
++Set the expiration timer, in seconds, on the key.
++The default is 600 seconds (10 mins).
++.TP
++.B -u user
++Revoke the uid key of the given user.
++.TP
++.B -v
++Increases the verbosity of the output to syslog 
++(can be specified multiple times).
+ .SH CONFIGURING
+ The file
+ .I /etc/request-key.conf
+@@ -25,11 +56,13 @@ will need to be modified so
+ can properly direct the upcall. The following line should be added before a call
+ to keyctl negate:
+ .PP
+-create	id_resolver	*	*	/usr/sbin/nfsidmap %k %d 600
++create	id_resolver	*	*	/usr/sbin/nfsidmap -t 600 %k %d 
+ .PP
+ This will direct all id_resolver requests to the program
+-.I /usr/sbin/nfsidmap
+-The last parameter, 600, defines how many seconds into the future the key will
++.I /usr/sbin/nfsidmap.
++The 
++.B -t 600 
++defines how many seconds into the future the key will
+ expire.  This is an optional parameter for
+ .I /usr/sbin/nfsidmap
+ and will default to 600 seconds when not specified.
+@@ -48,9 +81,9 @@ You can choose to handle any of these individually, rather than using the
+ generic upcall program.  If you would like to use your own program for a uid
+ lookup then you would edit your request-key.conf so it looks similar to this:
+ .PP
+-create	id_resolver	uid:*	*	/some/other/program %k %d 600
++create	id_resolver	uid:*	*	/some/other/program %k %d
+ .br
+-create	id_resolver	*		*	/usr/sbin/nfsidmap %k %d 600
++create	id_resolver	*		*	/usr/sbin/nfsidmap %k %d
+ .PP
+ Notice that the new line was added above the line for the generic program.
+ request-key will find the first matching line and run the corresponding program.
+diff --git a/utils/osd_login/Makefile.am b/utils/osd_login/Makefile.am
+new file mode 100644
+index 0000000..adc493a
+--- /dev/null
++++ b/utils/osd_login/Makefile.am
+@@ -0,0 +1,12 @@
++## Process this file with automake to produce Makefile.in
++
++OSD_LOGIN_FILES= osd_login
++
++EXTRA_DIST= $(OSD_LOGIN_FILES)
++
++all-local: $(OSD_LOGIN_FILES)
++
++install-data-hook:
++	$(INSTALL) --mode 755 osd_login $(DESTDIR)/sbin/osd_login
++
++MAINTAINERCLEANFILES = Makefile.in
+diff --git a/utils/osd_login/osd_login b/utils/osd_login/osd_login
+new file mode 100644
+index 0000000..08cd2d2
+--- /dev/null
++++ b/utils/osd_login/osd_login
+@@ -0,0 +1,118 @@
++#!/bin/bash
++#
++# osd_login : This script is part of the autologin feature
++#             mandated by the pnfs-objects standard.
++# It is called from objlayoutdriver.ko in the kernel.
++
++# Copyright (C) 2012, Sachin Bhamare <sbhamare at panasas.com>
++# Copyright (C) 2012, Boaz Harrosh <bharrosh at panasas.com>
++#
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License version 2 as
++# published by the Free Software Foundation.
++#
++# 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
++
++umask 022
++
++PATH="/sbin:/usr/sbin:/bin:/usr/bin"
++
++iscsiadm=/sbin/iscsiadm
++
++PARENT_PID=$BASHPID
++WATCHDOG_TIMEOUT=15
++
++protocol=""
++portal=""
++uri=""
++osdname=""
++systemid=""
++
++usage()
++{
++	echo "Usage: $0 -u <URI> -o <OSDNAME> -s <SYSTEMID>"
++	echo "Options:"
++	echo  "-u		target uri e.g. iscsi://<ip>:<port>"
++	echo  "-o		osdname of the target OSD"
++	echo  "-s		systemid of the target OSD"
++}
++
++parse_cmdline()
++{
++	argc=$#
++	if [ $# -lt 3 ]; then
++		usage
++		exit 1
++	fi
++
++	# parse the input arguments
++	while getopts "u:o:s:" options; do
++	    case $options in
++		u ) uri=$OPTARG;;
++		o ) osdname=$OPTARG;;
++		s ) systemid=$OPTARG;;
++		\? ) usage
++			exit 1;;
++		* )  usage
++			exit 1;;
++	    esac
++	done
++
++	echo "-u : $uri"
++	echo "-o : $osdname"
++	echo "-s : $systemid"
++
++	protocol=`echo $uri | awk -F ':' '{print $1}'`
++	portal=`echo $uri | awk -F '//' '{print $2}'`
++}
++
++watchdog()
++{
++	timeout=$1
++	portal=$2
++
++	sleep $timeout
++	if kill -9 $PARENT_PID; then
++	    echo "watchdog : Timed out (>$timeout seconds) while login into $portal" | logger -t "osd_login"
++	fi
++	echo "watchdog: exiting .."
++	exit 2
++}
++
++login_iscsi_osd()
++{
++	echo "login into: $1"
++	if ! $iscsiadm -m discovery -o nonpersistent -t sendtargets -p $1 --login; then
++		echo "$iscsiadm -m discovery -t sendtargets -p $1 --login returned error $? !"
++		sleep 1;
++	fi
++}
++
++echo "============= osd_login ========="
++echo "progname : $0"
++parse_cmdline "$@"
++echo "protocol: $protocol"
++echo "portal: $portal"
++
++watchdog $WATCHDOG_TIMEOUT $portal &
++watchdog_pid=$!
++
++case $protocol in
++iscsi)
++	login_iscsi_osd $portal |& logger -t "osd_login"
++	;;
++*)
++	echo "Error: protocol $protocol not supported !" | logger -t "osd_login"
++	;;
++esac
++
++kill -9 $watchdog_pid
++exit 0
+diff --git a/utils/showmount/Makefile.am b/utils/showmount/Makefile.am
+index 077b2c7..4ba5ead 100644
+--- a/utils/showmount/Makefile.am
++++ b/utils/showmount/Makefile.am
+@@ -7,7 +7,8 @@ sbin_PROGRAMS	= showmount
+ showmount_SOURCES = showmount.c
+ showmount_LDADD = ../../support/export/libexport.a \
+ 		  ../../support/nfs/libnfs.a \
+-	  	  ../../support/misc/libmisc.a
++		  ../../support/misc/libmisc.a \
++		  $(LIBTIRPC)
+ showmount_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) \
+ 		   -I$(top_builddir)/support/export
+ 
+diff --git a/utils/statd/Makefile.am b/utils/statd/Makefile.am
+index 1744791..dc2bfc4 100644
+--- a/utils/statd/Makefile.am
++++ b/utils/statd/Makefile.am
+@@ -15,10 +15,10 @@ BUILT_SOURCES = $(GENFILES)
+ statd_LDADD = ../../support/nsm/libnsm.a \
+ 	      ../../support/nfs/libnfs.a \
+ 	      ../../support/misc/libmisc.a \
+-	      $(LIBWRAP) $(LIBNSL) $(LIBCAP)
++	      $(LIBWRAP) $(LIBNSL) $(LIBCAP) $(LIBTIRPC)
+ sm_notify_LDADD = ../../support/nsm/libnsm.a \
+ 		  ../../support/nfs/libnfs.a \
+-		  $(LIBNSL) $(LIBCAP)
++		  $(LIBNSL) $(LIBCAP) $(LIBTIRPC)
+ 
+ EXTRA_DIST = sim_sm_inter.x $(man8_MANS) COPYRIGHT simulate.c
+ 
diff --git a/nfs-utils.spec b/nfs-utils.spec
index 288ce45..6ef9b13 100644
--- a/nfs-utils.spec
+++ b/nfs-utils.spec
@@ -2,7 +2,7 @@ Summary: NFS utilities and supporting clients and daemons for the kernel NFS ser
 Name: nfs-utils
 URL: http://sourceforge.net/projects/nfs
 Version: 1.2.5
-Release: 15%{?dist}
+Release: 16%{?dist}
 Epoch: 1
 
 # group all 32bit related archs
@@ -30,12 +30,7 @@ Source52: nfs-server.postconfig
 
 Source60: nfs4-modalias.conf
 
-Patch001: nfs-utils-1.2.6-rc6.patch
-Patch002: nfs-utils-1.2.4-mountshortcut.patch
-Patch003: nfs-utils-1.2.5-libidmap-hide-syms.patch
-Patch004: nfs-utils-1.2.5-nfsd-new-default.patch
-Patch005: nfs-utils-1.2.5-gssd-usercreds.patch
-Patch006: nfs-utils-1.2.5-gssd-nolibgssapi-krb5.patch
+Patch001: nfs-utils-1.2.6-rc7.patch
 
 Patch100: nfs-utils-1.2.1-statdpath-man.patch
 Patch101: nfs-utils-1.2.1-exp-subtree-warn-off.patch
@@ -93,11 +88,6 @@ This package also contains the mount.nfs and umount.nfs program.
 %setup -q
 
 %patch001 -p1
-%patch002 -p1
-%patch003 -p1
-%patch004 -p1
-%patch005 -p1
-%patch006 -p1
 
 %patch100 -p1
 %patch101 -p1
@@ -262,6 +252,7 @@ fi
 %doc linux-nfs/ChangeLog linux-nfs/KNOWNBUGS linux-nfs/NEW linux-nfs/README
 %doc linux-nfs/THANKS linux-nfs/TODO
 /sbin/rpc.statd
+/sbin/osd_login
 /usr/sbin/exportfs
 /usr/sbin/nfsstat
 /usr/sbin/rpcdebug
@@ -290,6 +281,9 @@ fi
 %attr(4755,root,root)   /sbin/umount.nfs4
 
 %changelog
+* Thu May  3 2012 Steve Dickson <steved at redhat.com> 1.2.5-16
+- Update to the latest RC release: nfs-utils-1.2.6-rc7
+
 * Thu Apr 26 2012 Josh Boyer <jwboyer at redhat.com> 1.2.5-15
 - Add modprobe config file to alias 'nfs4' to 'nfs' (bz 806333)
 


More information about the scm-commits mailing list