[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