Blame SOURCES/nfs-utils-1.3.0-nfsdcltrack-v2schema.patch

d2edb4
diff --git a/Makefile.am b/Makefile.am
d2edb4
index c9e9f87..6f3f3d6 100644
d2edb4
--- a/Makefile.am
d2edb4
+++ b/Makefile.am
d2edb4
@@ -9,34 +9,6 @@ MAINTAINERCLEANFILES = Makefile.in
d2edb4
 EXTRA_DIST = \
d2edb4
 	autogen.sh \
d2edb4
 	\
d2edb4
-	debian/changelog \
d2edb4
-	debian/control \
d2edb4
-	debian/copyright \
d2edb4
-	debian/etc.exports \
d2edb4
-	debian/idmapd.conf \
d2edb4
-	debian/nfs-common.conffiles \
d2edb4
-	debian/nfs-common.default \
d2edb4
-	debian/nfs-common.dirs \
d2edb4
-	debian/nfs-common.files \
d2edb4
-	debian/nfs-common.init \
d2edb4
-	debian/nfs-common.install \
d2edb4
-	debian/nfs-common.postinst \
d2edb4
-	debian/nfs-common.postrm \
d2edb4
-	debian/nfs-common.prerm \
d2edb4
-	debian/nfs-kernel-server.NEWS \
d2edb4
-	debian/nfs-kernel-server.conffiles \
d2edb4
-	debian/nfs-kernel-server.default \
d2edb4
-	debian/nfs-kernel-server.dirs \
d2edb4
-	debian/nfs-kernel-server.init \
d2edb4
-	debian/nfs-kernel-server.postinst \
d2edb4
-	debian/nfs-kernel-server.postrm \
d2edb4
-	debian/nfs-kernel-server.prerm \
d2edb4
-	debian/nhfsstone.dirs \
d2edb4
-	debian/nhfsstone.files \
d2edb4
-	debian/nhfsstone.postinst \
d2edb4
-	debian/nhfsstone.prerm \
d2edb4
-	debian/rules \
d2edb4
-	\
d2edb4
 	aclocal/bsdsignals.m4 \
d2edb4
 	aclocal/nfs-utils.m4 \
d2edb4
 	aclocal/kerberos5.m4 \
d2edb4
diff --git a/support/include/Makefile.am b/support/include/Makefile.am
d2edb4
index 4b33ee9..5c80c8b 100644
d2edb4
--- a/support/include/Makefile.am
d2edb4
+++ b/support/include/Makefile.am
d2edb4
@@ -3,6 +3,7 @@
d2edb4
 SUBDIRS = nfs rpcsvc sys
d2edb4
 
d2edb4
 noinst_HEADERS = \
d2edb4
+	cld.h \
d2edb4
 	exportfs.h \
d2edb4
 	ha-callout.h \
d2edb4
 	misc.h \
d2edb4
@@ -10,9 +11,13 @@ noinst_HEADERS = \
d2edb4
 	nfs_paths.h \
d2edb4
 	nfslib.h \
d2edb4
 	nfsrpc.h \
d2edb4
+	nls.h \
d2edb4
 	nsm.h \
d2edb4
+	pseudoflavors.h \
d2edb4
 	rpcmisc.h \
d2edb4
+	sockaddr.h \
d2edb4
 	tcpwrapper.h \
d2edb4
+	v4root.h \
d2edb4
 	xio.h \
d2edb4
 	xlog.h \
d2edb4
 	xmalloc.h \
d2edb4
diff --git a/tests/Makefile.am b/tests/Makefile.am
d2edb4
index faa8197..1f96264 100644
d2edb4
--- a/tests/Makefile.am
d2edb4
+++ b/tests/Makefile.am
d2edb4
@@ -11,3 +11,4 @@ SUBDIRS = nsm_client
d2edb4
 MAINTAINERCLEANFILES = Makefile.in
d2edb4
 
d2edb4
 TESTS = t0001-statd-basic-mon-unmon.sh
d2edb4
+EXTRA_DIST = test-lib.sh $(TESTS)
d2edb4
diff --git a/tests/nsm_client/Makefile.am b/tests/nsm_client/Makefile.am
d2edb4
index 4c15346..a8fc131 100644
d2edb4
--- a/tests/nsm_client/Makefile.am
d2edb4
+++ b/tests/nsm_client/Makefile.am
d2edb4
@@ -7,6 +7,7 @@ GENFILES_H	= nlm_sm_inter.h
d2edb4
 
d2edb4
 GENFILES	= $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H)
d2edb4
 
d2edb4
+EXTRA_DIST = nlm_sm_inter.x
d2edb4
 
d2edb4
 check_PROGRAMS	= nsm_client
d2edb4
 nsm_client_SOURCES = $(GENFILES) nsm_client.c
d2edb4
diff --git a/utils/gssd/Makefile.am b/utils/gssd/Makefile.am
d2edb4
index af59791..a4e9c56 100644
d2edb4
--- a/utils/gssd/Makefile.am
d2edb4
+++ b/utils/gssd/Makefile.am
d2edb4
@@ -8,7 +8,6 @@ sbin_PREFIXED	= gssd svcgssd
d2edb4
 sbin_PROGRAMS	= $(sbin_PREFIXED)
d2edb4
 
d2edb4
 EXTRA_DIST = \
d2edb4
-	gss_destroy_creds \
d2edb4
 	$(man8_MANS)
d2edb4
 
d2edb4
 COMMON_SRCS = \
d2edb4
diff --git a/utils/idmapd/Makefile.am b/utils/idmapd/Makefile.am
d2edb4
index 58b33ec..c2f8ba1 100644
d2edb4
--- a/utils/idmapd/Makefile.am
d2edb4
+++ b/utils/idmapd/Makefile.am
d2edb4
@@ -7,8 +7,7 @@ KPREFIX		= @kprefix@
d2edb4
 sbin_PROGRAMS	= idmapd
d2edb4
 
d2edb4
 EXTRA_DIST = \
d2edb4
-	$(man8_MANS) \
d2edb4
-	idmapd.conf
d2edb4
+	$(man8_MANS)
d2edb4
 
d2edb4
 idmapd_SOURCES = \
d2edb4
 	idmapd.c \
d2edb4
diff --git a/utils/mount/Makefile.am b/utils/mount/Makefile.am
d2edb4
index 5810936..e24f3bd 100644
d2edb4
--- a/utils/mount/Makefile.am
d2edb4
+++ b/utils/mount/Makefile.am
d2edb4
@@ -8,19 +8,21 @@ man8_MANS	= mount.nfs.man umount.nfs.man
d2edb4
 man5_MANS	= nfs.man
d2edb4
 
d2edb4
 sbin_PROGRAMS	= mount.nfs
d2edb4
-EXTRA_DIST = nfsmount.x $(man8_MANS) $(man5_MANS)
d2edb4
+EXTRA_DIST = nfsmount.conf $(man8_MANS) $(man5_MANS)
d2edb4
 mount_common = error.c network.c token.c \
d2edb4
 		    parse_opt.c parse_dev.c \
d2edb4
 		    nfsmount.c nfs4mount.c stropts.c\
d2edb4
 		    mount_constants.h error.h network.h token.h \
d2edb4
 		    parse_opt.h parse_dev.h \
d2edb4
-		    nfs4_mount.h nfs_mount4.h stropts.h version.h \
d2edb4
-		    mount_config.h utils.c utils.h
d2edb4
+		    nfs4_mount.h stropts.h version.h \
d2edb4
+		    mount_config.h utils.c utils.h \
d2edb4
+		    nfs_mount.h
d2edb4
 
d2edb4
 if MOUNT_CONFIG
d2edb4
 mount_common += configfile.c
d2edb4
 man5_MANS += nfsmount.conf.man
d2edb4
-EXTRA_DIST += nfsmount.conf
d2edb4
+else
d2edb4
+EXTRA_DIST += nfsmount.conf.man
d2edb4
 endif
d2edb4
 
d2edb4
 mount_nfs_LDADD = ../../support/nfs/libnfs.a \
d2edb4
diff --git a/utils/mountd/Makefile.am b/utils/mountd/Makefile.am
d2edb4
index 7db968b..9e1ab5c 100644
d2edb4
--- a/utils/mountd/Makefile.am
d2edb4
+++ b/utils/mountd/Makefile.am
d2edb4
@@ -7,6 +7,7 @@ RPCPREFIX	= rpc.
d2edb4
 KPREFIX		= @kprefix@
d2edb4
 sbin_PROGRAMS	= mountd
d2edb4
 
d2edb4
+noinst_HEADERS = fsloc.h
d2edb4
 mountd_SOURCES = mountd.c mount_dispatch.c auth.c rmtab.c cache.c \
d2edb4
 		 svc_run.c fsloc.c v4root.c mountd.h
d2edb4
 mountd_LDADD = ../../support/export/libexport.a \
d2edb4
diff --git a/utils/nfsd/Makefile.am b/utils/nfsd/Makefile.am
d2edb4
index 1536065..39a6e6f 100644
d2edb4
--- a/utils/nfsd/Makefile.am
d2edb4
+++ b/utils/nfsd/Makefile.am
d2edb4
@@ -7,6 +7,7 @@ RPCPREFIX	= rpc.
d2edb4
 KPREFIX		= @kprefix@
d2edb4
 sbin_PROGRAMS	= nfsd
d2edb4
 
d2edb4
+noinst_HEADERS = nfssvc.h
d2edb4
 nfsd_SOURCES = nfsd.c nfssvc.c
d2edb4
 nfsd_LDADD = ../../support/nfs/libnfs.a $(LIBTIRPC)
d2edb4
 
d2edb4
diff --git a/utils/nfsdcltrack/Makefile.am b/utils/nfsdcltrack/Makefile.am
d2edb4
index a860ffb..d603f92 100644
d2edb4
--- a/utils/nfsdcltrack/Makefile.am
d2edb4
+++ b/utils/nfsdcltrack/Makefile.am
d2edb4
@@ -6,6 +6,8 @@ EXTRA_DIST	= $(man8_MANS)
d2edb4
 AM_CFLAGS	+= -D_LARGEFILE64_SOURCE
d2edb4
 sbin_PROGRAMS	= nfsdcltrack
d2edb4
 
d2edb4
+noinst_HEADERS	= sqlite.h
d2edb4
+
d2edb4
 nfsdcltrack_SOURCES = nfsdcltrack.c sqlite.c
d2edb4
 nfsdcltrack_LDADD = ../../support/nfs/libnfs.a $(LIBSQLITE) $(LIBCAP)
d2edb4
 
d2edb4
diff --git a/utils/nfsdcltrack/nfsdcltrack.c b/utils/nfsdcltrack/nfsdcltrack.c
d2edb4
index 4334340..fcdda7f 100644
d2edb4
--- a/utils/nfsdcltrack/nfsdcltrack.c
d2edb4
+++ b/utils/nfsdcltrack/nfsdcltrack.c
d2edb4
@@ -37,6 +37,7 @@
d2edb4
 #include <libgen.h>
d2edb4
 #include <sys/inotify.h>
d2edb4
 #include <dirent.h>
d2edb4
+#include <limits.h>
d2edb4
 #ifdef HAVE_SYS_CAPABILITY_H
d2edb4
 #include <sys/prctl.h>
d2edb4
 #include <sys/capability.h>
d2edb4
@@ -49,6 +50,8 @@
d2edb4
 #define CLD_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcltrack"
d2edb4
 #endif
d2edb4
 
d2edb4
+#define NFSD_END_GRACE_FILE "/proc/fs/nfsd/v4_end_grace"
d2edb4
+
d2edb4
 /* defined by RFC 3530 */
d2edb4
 #define NFS4_OPAQUE_LIMIT	1024
d2edb4
 
d2edb4
@@ -210,6 +213,64 @@ cltrack_set_caps(void)
d2edb4
 	return ret;
d2edb4
 }
d2edb4
 
d2edb4
+/* Inform the kernel that it's OK to lift nfsd's grace period */
d2edb4
+static void
d2edb4
+cltrack_lift_grace_period(void)
d2edb4
+{
d2edb4
+	int fd;
d2edb4
+
d2edb4
+	fd = open(NFSD_END_GRACE_FILE, O_WRONLY);
d2edb4
+	if (fd < 0) {
d2edb4
+		/* Don't warn if file isn't present */
d2edb4
+		if (errno != ENOENT)
d2edb4
+			xlog(L_WARNING, "Unable to open %s: %m",
d2edb4
+				NFSD_END_GRACE_FILE);
d2edb4
+		return;
d2edb4
+	}
d2edb4
+
d2edb4
+	if (write(fd, "Y", 1) < 0)
d2edb4
+		xlog(L_WARNING, "Unable to write to %s: %m",
d2edb4
+				NFSD_END_GRACE_FILE);
d2edb4
+
d2edb4
+	close(fd);
d2edb4
+	return;
d2edb4
+}
d2edb4
+
d2edb4
+/*
d2edb4
+ * Fetch the contents of the NFSDCLTRACK_GRACE_START env var. If it's not set
d2edb4
+ * or there's an error converting it to time_t, then return LONG_MAX.
d2edb4
+ */
d2edb4
+static time_t
d2edb4
+cltrack_get_grace_start(void)
d2edb4
+{
d2edb4
+	time_t grace_start;
d2edb4
+	char *end;
d2edb4
+	char *grace_start_str = getenv("NFSDCLTRACK_GRACE_START");
d2edb4
+
d2edb4
+	if (!grace_start_str)
d2edb4
+		return LONG_MAX;
d2edb4
+
d2edb4
+	errno = 0;
d2edb4
+	grace_start = strtol(grace_start_str, &end, 0);
d2edb4
+	/* Problem converting or value is too large? */
d2edb4
+	if (errno)
d2edb4
+		return LONG_MAX;
d2edb4
+
d2edb4
+	return grace_start;
d2edb4
+}
d2edb4
+
d2edb4
+static bool
d2edb4
+cltrack_reclaims_complete(void)
d2edb4
+{
d2edb4
+	time_t grace_start = cltrack_get_grace_start();
d2edb4
+
d2edb4
+	/* Don't query DB if we didn't get a valid boot time */
d2edb4
+	if (grace_start == LONG_MAX)
d2edb4
+		return false;
d2edb4
+
d2edb4
+	return !sqlite_query_reclaiming(grace_start);
d2edb4
+}
d2edb4
+
d2edb4
 static int
d2edb4
 cltrack_init(const char __attribute__((unused)) *unused)
d2edb4
 {
d2edb4
@@ -241,7 +302,7 @@ cltrack_init(const char __attribute__((unused)) *unused)
d2edb4
 	}
d2edb4
 
d2edb4
 	/* set up storage db */
d2edb4
-	ret = sqlite_maindb_init(storagedir);
d2edb4
+	ret = sqlite_prepare_dbh(storagedir);
d2edb4
 	if (ret) {
d2edb4
 		xlog(L_ERROR, "Failed to init database: %d", ret);
d2edb4
 		/*
d2edb4
@@ -250,15 +311,36 @@ cltrack_init(const char __attribute__((unused)) *unused)
d2edb4
 		 * stop upcalling until the problem is resolved.
d2edb4
 		 */
d2edb4
 		ret = -EACCES;
d2edb4
+	} else {
d2edb4
+		if (cltrack_reclaims_complete())
d2edb4
+			cltrack_lift_grace_period();
d2edb4
 	}
d2edb4
+
d2edb4
 	return ret;
d2edb4
 }
d2edb4
 
d2edb4
+/*
d2edb4
+ * Fetch the contents of the NFSDCLTRACK_CLIENT_HAS_SESSION env var. If
d2edb4
+ * it's set and the first character is 'Y' then return true. Otherwise
d2edb4
+ * return false.
d2edb4
+ */
d2edb4
+static bool
d2edb4
+cltrack_client_has_session(void)
d2edb4
+{
d2edb4
+	char *has_session = getenv("NFSDCLTRACK_CLIENT_HAS_SESSION");
d2edb4
+
d2edb4
+	if (has_session && *has_session == 'Y')
d2edb4
+		return true;
d2edb4
+
d2edb4
+	return false;
d2edb4
+}
d2edb4
+
d2edb4
 static int
d2edb4
 cltrack_create(const char *id)
d2edb4
 {
d2edb4
 	int ret;
d2edb4
 	ssize_t len;
d2edb4
+	bool has_session;
d2edb4
 
d2edb4
 	xlog(D_GENERAL, "%s: create client record.", __func__);
d2edb4
 
d2edb4
@@ -270,7 +352,12 @@ cltrack_create(const char *id)
d2edb4
 	if (len < 0)
d2edb4
 		return (int)len;
d2edb4
 
d2edb4
-	ret = sqlite_insert_client(blob, len);
d2edb4
+	has_session = cltrack_client_has_session();
d2edb4
+
d2edb4
+	ret = sqlite_insert_client(blob, len, has_session, false);
d2edb4
+
d2edb4
+	if (!ret && has_session && cltrack_reclaims_complete())
d2edb4
+		cltrack_lift_grace_period();
d2edb4
 
d2edb4
 	return ret ? -EREMOTEIO : ret;
d2edb4
 }
d2edb4
@@ -297,7 +384,8 @@ cltrack_remove(const char *id)
d2edb4
 }
d2edb4
 
d2edb4
 static int
d2edb4
-cltrack_check_legacy(const unsigned char *blob, const ssize_t len)
d2edb4
+cltrack_check_legacy(const unsigned char *blob, const ssize_t len,
d2edb4
+			bool has_session)
d2edb4
 {
d2edb4
 	int ret;
d2edb4
 	struct stat st;
d2edb4
@@ -323,7 +411,7 @@ cltrack_check_legacy(const unsigned char *blob, const ssize_t len)
d2edb4
 	}
d2edb4
 
d2edb4
 	/* Dir exists, try to insert record into db */
d2edb4
-	ret = sqlite_insert_client(blob, len);
d2edb4
+	ret = sqlite_insert_client(blob, len, has_session, has_session);
d2edb4
 	if (ret) {
d2edb4
 		xlog(D_GENERAL, "Failed to insert client: %d", ret);
d2edb4
 		return -EREMOTEIO;
d2edb4
@@ -343,6 +431,7 @@ cltrack_check(const char *id)
d2edb4
 {
d2edb4
 	int ret;
d2edb4
 	ssize_t len;
d2edb4
+	bool has_session;
d2edb4
 
d2edb4
 	xlog(D_GENERAL, "%s: check client record", __func__);
d2edb4
 
d2edb4
@@ -354,9 +443,11 @@ cltrack_check(const char *id)
d2edb4
 	if (len < 0)
d2edb4
 		return (int)len;
d2edb4
 
d2edb4
-	ret = sqlite_check_client(blob, len);
d2edb4
+	has_session = cltrack_client_has_session();
d2edb4
+
d2edb4
+	ret = sqlite_check_client(blob, len, has_session);
d2edb4
 	if (ret)
d2edb4
-		ret = cltrack_check_legacy(blob, len);
d2edb4
+		ret = cltrack_check_legacy(blob, len, has_session);
d2edb4
 
d2edb4
 	return ret ? -EPERM : ret;
d2edb4
 }
d2edb4
diff --git a/utils/nfsdcltrack/sqlite.c b/utils/nfsdcltrack/sqlite.c
d2edb4
index bac6789..eb1711a 100644
d2edb4
--- a/utils/nfsdcltrack/sqlite.c
d2edb4
+++ b/utils/nfsdcltrack/sqlite.c
d2edb4
@@ -21,17 +21,15 @@
d2edb4
  * Explanation:
d2edb4
  *
d2edb4
  * This file contains the code to manage the sqlite backend database for the
d2edb4
- * clstated upcall daemon.
d2edb4
+ * nfsdcltrack usermodehelper upcall program.
d2edb4
  *
d2edb4
  * The main database is called main.sqlite and contains the following tables:
d2edb4
  *
d2edb4
  * parameters: simple key/value pairs for storing database info
d2edb4
  *
d2edb4
- * clients: one column containing a BLOB with the as sent by the client
d2edb4
- * 	    and a timestamp (in epoch seconds) of when the record was
d2edb4
- * 	    established
d2edb4
- *
d2edb4
- * FIXME: should we also record the fsid being accessed?
d2edb4
+ * clients: an "id" column containing a BLOB with the long-form clientid as
d2edb4
+ * 	    sent by the client, a "time" column containing a timestamp (in
d2edb4
+ * 	    epoch seconds) of when the record was last updated.
d2edb4
  */
d2edb4
 
d2edb4
 #ifdef HAVE_CONFIG_H
d2edb4
@@ -52,10 +50,10 @@
d2edb4
 
d2edb4
 #include "xlog.h"
d2edb4
 
d2edb4
-#define CLD_SQLITE_SCHEMA_VERSION 1
d2edb4
+#define CLTRACK_SQLITE_LATEST_SCHEMA_VERSION 1
d2edb4
 
d2edb4
 /* in milliseconds */
d2edb4
-#define CLD_SQLITE_BUSY_TIMEOUT 10000
d2edb4
+#define CLTRACK_SQLITE_BUSY_TIMEOUT 10000
d2edb4
 
d2edb4
 /* private data structures */
d2edb4
 
d2edb4
@@ -90,135 +88,192 @@ mkdir_if_not_exist(const char *dirname)
d2edb4
 	return ret;
d2edb4
 }
d2edb4
 
d2edb4
-/* Open the database and set up the database handle for it */
d2edb4
-int
d2edb4
-sqlite_prepare_dbh(const char *topdir)
d2edb4
+static int
d2edb4
+sqlite_query_schema_version(void)
d2edb4
 {
d2edb4
 	int ret;
d2edb4
+	sqlite3_stmt *stmt = NULL;
d2edb4
 
d2edb4
-	/* Do nothing if the database handle is already set up */
d2edb4
-	if (dbh)
d2edb4
-		return 0;
d2edb4
-
d2edb4
-	ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", topdir);
d2edb4
-	if (ret < 0)
d2edb4
-		return ret;
d2edb4
-
d2edb4
-	buf[PATH_MAX - 1] = '\0';
d2edb4
-
d2edb4
-	ret = sqlite3_open(buf, &dbh;;
d2edb4
+	/* prepare select query */
d2edb4
+	ret = sqlite3_prepare_v2(dbh,
d2edb4
+		"SELECT value FROM parameters WHERE key == \"version\";",
d2edb4
+		 -1, &stmt, NULL);
d2edb4
 	if (ret != SQLITE_OK) {
d2edb4
-		xlog(L_ERROR, "Unable to open main database: %d", ret);
d2edb4
-		dbh = NULL;
d2edb4
-		return ret;
d2edb4
+		xlog(L_ERROR, "Unable to prepare select statement: %s",
d2edb4
+			sqlite3_errmsg(dbh));
d2edb4
+		ret = 0;
d2edb4
+		goto out;
d2edb4
 	}
d2edb4
 
d2edb4
-	ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT);
d2edb4
-	if (ret != SQLITE_OK) {
d2edb4
-		xlog(L_ERROR, "Unable to set sqlite busy timeout: %d", ret);
d2edb4
-		sqlite3_close(dbh);
d2edb4
-		dbh = NULL;
d2edb4
+	/* query schema version */
d2edb4
+	ret = sqlite3_step(stmt);
d2edb4
+	if (ret != SQLITE_ROW) {
d2edb4
+		xlog(L_ERROR, "Select statement execution failed: %s",
d2edb4
+				sqlite3_errmsg(dbh));
d2edb4
+		ret = 0;
d2edb4
+		goto out;
d2edb4
 	}
d2edb4
 
d2edb4
+	ret = sqlite3_column_int(stmt, 0);
d2edb4
+out:
d2edb4
+	sqlite3_finalize(stmt);
d2edb4
 	return ret;
d2edb4
 }
d2edb4
 
d2edb4
 /*
d2edb4
- * Open the "main" database, and attempt to initialize it by creating the
d2edb4
- * parameters table and inserting the schema version into it. Ignore any errors
d2edb4
- * from that, and then attempt to select the version out of it again. If the
d2edb4
- * version appears wrong, then assume that the DB is corrupt or has been
d2edb4
- * upgraded, and return an error. If all of that works, then attempt to create
d2edb4
- * the "clients" table.
d2edb4
+ * Start an exclusive transaction and recheck the DB schema version. If it's
d2edb4
+ * still zero (indicating a new database) then set it up. If that all works,
d2edb4
+ * then insert schema version into the parameters table and commit the
d2edb4
+ * transaction. On any error, rollback the transaction.
d2edb4
  */
d2edb4
 int
d2edb4
-sqlite_maindb_init(const char *topdir)
d2edb4
+sqlite_maindb_init_v1(void)
d2edb4
 {
d2edb4
 	int ret;
d2edb4
 	char *err = NULL;
d2edb4
-	sqlite3_stmt *stmt = NULL;
d2edb4
 
d2edb4
-	ret = mkdir_if_not_exist(topdir);
d2edb4
-	if (ret)
d2edb4
+	/* Start a transaction */
d2edb4
+	ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
d2edb4
+				&err;;
d2edb4
+	if (ret != SQLITE_OK) {
d2edb4
+		xlog(L_ERROR, "Unable to begin transaction: %s", err);
d2edb4
 		return ret;
d2edb4
+	}
d2edb4
 
d2edb4
-	ret = sqlite_prepare_dbh(topdir);
d2edb4
-	if (ret)
d2edb4
-		return ret;
d2edb4
+	/*
d2edb4
+	 * Check schema version again. This time, under an exclusive
d2edb4
+	 * transaction to guard against racing DB setup attempts
d2edb4
+	 */
d2edb4
+	ret = sqlite_query_schema_version();
d2edb4
+	switch (ret) {
d2edb4
+	case 0:
d2edb4
+		/* Query failed again -- set up DB */
d2edb4
+		break;
d2edb4
+	case CLTRACK_SQLITE_LATEST_SCHEMA_VERSION:
d2edb4
+		/* Someone else raced in and set it up */
d2edb4
+		ret = 0;
d2edb4
+		goto rollback;
d2edb4
+	default:
d2edb4
+		/* Something went wrong -- fail! */
d2edb4
+		ret = -EINVAL;
d2edb4
+		goto rollback;
d2edb4
+	}
d2edb4
 
d2edb4
-	/* Try to create table */
d2edb4
-	ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS parameters "
d2edb4
+	ret = sqlite3_exec(dbh, "CREATE TABLE parameters "
d2edb4
 				"(key TEXT PRIMARY KEY, value TEXT);",
d2edb4
 				NULL, NULL, &err;;
d2edb4
 	if (ret != SQLITE_OK) {
d2edb4
-		xlog(L_ERROR, "Unable to create parameter table: %d", ret);
d2edb4
-		goto out_err;
d2edb4
+		xlog(L_ERROR, "Unable to create parameter table: %s", err);
d2edb4
+		goto rollback;
d2edb4
 	}
d2edb4
 
d2edb4
-	/* insert version into table -- ignore error if it fails */
d2edb4
-	ret = snprintf(buf, sizeof(buf),
d2edb4
-		       "INSERT OR IGNORE INTO parameters values (\"version\", "
d2edb4
-		       "\"%d\");", CLD_SQLITE_SCHEMA_VERSION);
d2edb4
+	/* create the "clients" table */
d2edb4
+	ret = sqlite3_exec(dbh, "CREATE TABLE clients (id BLOB PRIMARY KEY, "
d2edb4
+				"time INTEGER);",
d2edb4
+				NULL, NULL, &err;;
d2edb4
+	if (ret != SQLITE_OK) {
d2edb4
+		xlog(L_ERROR, "Unable to create clients table: %s", err);
d2edb4
+		goto rollback;
d2edb4
+	}
d2edb4
+
d2edb4
+
d2edb4
+	/* insert version into parameters table */
d2edb4
+	ret = snprintf(buf, sizeof(buf), "INSERT OR FAIL INTO parameters "
d2edb4
+			"values (\"version\", \"%d\");",
d2edb4
+			CLTRACK_SQLITE_LATEST_SCHEMA_VERSION);
d2edb4
 	if (ret < 0) {
d2edb4
-		goto out_err;
d2edb4
+		xlog(L_ERROR, "sprintf failed!");
d2edb4
+		goto rollback;
d2edb4
 	} else if ((size_t)ret >= sizeof(buf)) {
d2edb4
+		xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
d2edb4
 		ret = -EINVAL;
d2edb4
-		goto out_err;
d2edb4
+		goto rollback;
d2edb4
 	}
d2edb4
 
d2edb4
 	ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err;;
d2edb4
 	if (ret != SQLITE_OK) {
d2edb4
-		xlog(L_ERROR, "Unable to insert into parameter table: %d",
d2edb4
-				ret);
d2edb4
-		goto out_err;
d2edb4
+		xlog(L_ERROR, "Unable to insert into parameter table: %s", err);
d2edb4
+		goto rollback;
d2edb4
 	}
d2edb4
 
d2edb4
-	ret = sqlite3_prepare_v2(dbh,
d2edb4
-		"SELECT value FROM parameters WHERE key == \"version\";",
d2edb4
-		 -1, &stmt, NULL);
d2edb4
+	ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err;;
d2edb4
 	if (ret != SQLITE_OK) {
d2edb4
-		xlog(L_ERROR, "Unable to prepare select statement: %d", ret);
d2edb4
-		goto out_err;
d2edb4
+		xlog(L_ERROR, "Unable to commit transaction: %s", err);
d2edb4
+		goto rollback;
d2edb4
 	}
d2edb4
+out:
d2edb4
+	sqlite3_free(err);
d2edb4
+	return ret;
d2edb4
 
d2edb4
-	/* check schema version */
d2edb4
-	ret = sqlite3_step(stmt);
d2edb4
-	if (ret != SQLITE_ROW) {
d2edb4
-		xlog(L_ERROR, "Select statement execution failed: %s",
d2edb4
+rollback:
d2edb4
+	/* Attempt to rollback the transaction */
d2edb4
+	sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err;;
d2edb4
+	goto out;
d2edb4
+}
d2edb4
+
d2edb4
+/* Open the database and set up the database handle for it */
d2edb4
+int
d2edb4
+sqlite_prepare_dbh(const char *topdir)
d2edb4
+{
d2edb4
+	int ret;
d2edb4
+
d2edb4
+	/* Do nothing if the database handle is already set up */
d2edb4
+	if (dbh)
d2edb4
+		return 0;
d2edb4
+
d2edb4
+	ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", topdir);
d2edb4
+	if (ret < 0)
d2edb4
+		return ret;
d2edb4
+
d2edb4
+	buf[PATH_MAX - 1] = '\0';
d2edb4
+
d2edb4
+	/* open a new DB handle */
d2edb4
+	ret = sqlite3_open(buf, &dbh;;
d2edb4
+	if (ret != SQLITE_OK) {
d2edb4
+		/* try to create the dir */
d2edb4
+		ret = mkdir_if_not_exist(topdir);
d2edb4
+		if (ret)
d2edb4
+			goto out_close;
d2edb4
+
d2edb4
+		/* retry open */
d2edb4
+		ret = sqlite3_open(buf, &dbh;;
d2edb4
+		if (ret != SQLITE_OK)
d2edb4
+			goto out_close;
d2edb4
+	}
d2edb4
+
d2edb4
+	/* set busy timeout */
d2edb4
+	ret = sqlite3_busy_timeout(dbh, CLTRACK_SQLITE_BUSY_TIMEOUT);
d2edb4
+	if (ret != SQLITE_OK) {
d2edb4
+		xlog(L_ERROR, "Unable to set sqlite busy timeout: %s",
d2edb4
 				sqlite3_errmsg(dbh));
d2edb4
-		goto out_err;
d2edb4
+		goto out_close;
d2edb4
 	}
d2edb4
 
d2edb4
-	/* process SELECT result */
d2edb4
-	ret = sqlite3_column_int(stmt, 0);
d2edb4
-	if (ret != CLD_SQLITE_SCHEMA_VERSION) {
d2edb4
+	ret = sqlite_query_schema_version();
d2edb4
+	switch (ret) {
d2edb4
+	case CLTRACK_SQLITE_LATEST_SCHEMA_VERSION:
d2edb4
+		/* DB is already set up. Do nothing */
d2edb4
+		ret = 0;
d2edb4
+		break;
d2edb4
+	case 0:
d2edb4
+		/* Query failed -- try to set up new DB */
d2edb4
+		ret = sqlite_maindb_init_v1();
d2edb4
+		if (ret)
d2edb4
+			goto out_close;
d2edb4
+		break;
d2edb4
+	default:
d2edb4
+		/* Unknown DB version -- downgrade? Fail */
d2edb4
 		xlog(L_ERROR, "Unsupported database schema version! "
d2edb4
 			"Expected %d, got %d.",
d2edb4
-			CLD_SQLITE_SCHEMA_VERSION, ret);
d2edb4
+			CLTRACK_SQLITE_LATEST_SCHEMA_VERSION, ret);
d2edb4
 		ret = -EINVAL;
d2edb4
-		goto out_err;
d2edb4
-	}
d2edb4
-
d2edb4
-	/* now create the "clients" table */
d2edb4
-	ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS clients "
d2edb4
-				"(id BLOB PRIMARY KEY, time INTEGER);",
d2edb4
-				NULL, NULL, &err;;
d2edb4
-	if (ret != SQLITE_OK) {
d2edb4
-		xlog(L_ERROR, "Unable to create clients table: %s", err);
d2edb4
-		goto out_err;
d2edb4
+		goto out_close;
d2edb4
 	}
d2edb4
 
d2edb4
-	sqlite3_free(err);
d2edb4
-	sqlite3_finalize(stmt);
d2edb4
-	return 0;
d2edb4
-
d2edb4
-out_err:
d2edb4
-	if (err) {
d2edb4
-		xlog(L_ERROR, "sqlite error: %s", err);
d2edb4
-		sqlite3_free(err);
d2edb4
-	}
d2edb4
-	sqlite3_finalize(stmt);
d2edb4
+	return ret;
d2edb4
+out_close:
d2edb4
 	sqlite3_close(dbh);
d2edb4
+	dbh = NULL;
d2edb4
 	return ret;
d2edb4
 }
d2edb4
 
d2edb4
@@ -228,14 +283,20 @@ out_err:
d2edb4
  * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0)
d2edb4
  */
d2edb4
 int
d2edb4
-sqlite_insert_client(const unsigned char *clname, const size_t namelen)
d2edb4
+sqlite_insert_client(const unsigned char *clname, const size_t namelen,
d2edb4
+			const bool has_session, const bool zerotime)
d2edb4
 {
d2edb4
 	int ret;
d2edb4
 	sqlite3_stmt *stmt = NULL;
d2edb4
 
d2edb4
-	ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients VALUES "
d2edb4
-				      "(?, strftime('%s', 'now'));", -1,
d2edb4
-					&stmt, NULL);
d2edb4
+	if (zerotime)
d2edb4
+		ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients "
d2edb4
+				"VALUES (?, 0, ?);", -1, &stmt, NULL);
d2edb4
+	else
d2edb4
+		ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients "
d2edb4
+				"VALUES (?, strftime('%s', 'now'), ?);", -1,
d2edb4
+				&stmt, NULL);
d2edb4
+
d2edb4
 	if (ret != SQLITE_OK) {
d2edb4
 		xlog(L_ERROR, "%s: insert statement prepare failed: %s",
d2edb4
 			__func__, sqlite3_errmsg(dbh));
d2edb4
@@ -250,6 +311,13 @@ sqlite_insert_client(const unsigned char *clname, const size_t namelen)
d2edb4
 		goto out_err;
d2edb4
 	}
d2edb4
 
d2edb4
+	ret = sqlite3_bind_int(stmt, 2, (int)has_session);
d2edb4
+	if (ret != SQLITE_OK) {
d2edb4
+		xlog(L_ERROR, "%s: bind int failed: %s", __func__,
d2edb4
+				sqlite3_errmsg(dbh));
d2edb4
+		goto out_err;
d2edb4
+	}
d2edb4
+
d2edb4
 	ret = sqlite3_step(stmt);
d2edb4
 	if (ret == SQLITE_DONE)
d2edb4
 		ret = SQLITE_OK;
d2edb4
@@ -305,7 +373,8 @@ out_err:
d2edb4
  * return an error.
d2edb4
  */
d2edb4
 int
d2edb4
-sqlite_check_client(const unsigned char *clname, const size_t namelen)
d2edb4
+sqlite_check_client(const unsigned char *clname, const size_t namelen,
d2edb4
+			const bool has_session)
d2edb4
 {
d2edb4
 	int ret;
d2edb4
 	sqlite3_stmt *stmt = NULL;
d2edb4
@@ -340,6 +409,12 @@ sqlite_check_client(const unsigned char *clname, const size_t namelen)
d2edb4
 		goto out_err;
d2edb4
 	}
d2edb4
 
d2edb4
+	/* Only update timestamp for v4.0 clients */
d2edb4
+	if (has_session) {
d2edb4
+		ret = SQLITE_OK;
d2edb4
+		goto out_err;
d2edb4
+	}
d2edb4
+
d2edb4
 	sqlite3_finalize(stmt);
d2edb4
 	stmt = NULL;
d2edb4
 	ret = sqlite3_prepare_v2(dbh, "UPDATE OR FAIL clients SET "
d2edb4
@@ -398,3 +473,43 @@ sqlite_remove_unreclaimed(time_t grace_start)
d2edb4
 	sqlite3_free(err);
d2edb4
 	return ret;
d2edb4
 }
d2edb4
+
d2edb4
+/*
d2edb4
+ * Are there any clients that are possibly still reclaiming? Return a positive
d2edb4
+ * integer (usually number of clients) if so. If not, then return 0. On any
d2edb4
+ * error, return non-zero.
d2edb4
+ */
d2edb4
+int
d2edb4
+sqlite_query_reclaiming(const time_t grace_start)
d2edb4
+{
d2edb4
+	int ret;
d2edb4
+	sqlite3_stmt *stmt = NULL;
d2edb4
+
d2edb4
+	ret = sqlite3_prepare_v2(dbh, "SELECT count(*) FROM clients WHERE "
d2edb4
+				      "time < ? OR has_session != 1", -1, &stmt, NULL);
d2edb4
+	if (ret != SQLITE_OK) {
d2edb4
+		xlog(L_ERROR, "%s: unable to prepare select statement: %s",
d2edb4
+				__func__, sqlite3_errmsg(dbh));
d2edb4
+		return ret;
d2edb4
+	}
d2edb4
+
d2edb4
+	ret = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)grace_start);
d2edb4
+	if (ret != SQLITE_OK) {
d2edb4
+		xlog(L_ERROR, "%s: bind int64 failed: %s",
d2edb4
+				__func__, sqlite3_errmsg(dbh));
d2edb4
+		return ret;
d2edb4
+	}
d2edb4
+
d2edb4
+	ret = sqlite3_step(stmt);
d2edb4
+	if (ret != SQLITE_ROW) {
d2edb4
+		xlog(L_ERROR, "%s: unexpected return code from select: %s",
d2edb4
+				__func__, sqlite3_errmsg(dbh));
d2edb4
+		return ret;
d2edb4
+	}
d2edb4
+
d2edb4
+	ret = sqlite3_column_int(stmt, 0);
d2edb4
+	sqlite3_finalize(stmt);
d2edb4
+	xlog(D_GENERAL, "%s: there are %d clients that have not completed "
d2edb4
+			"reclaim", __func__, ret);
d2edb4
+	return ret;
d2edb4
+}
d2edb4
diff --git a/utils/nfsdcltrack/sqlite.h b/utils/nfsdcltrack/sqlite.h
d2edb4
index ebf04c3..06e7c04 100644
d2edb4
--- a/utils/nfsdcltrack/sqlite.h
d2edb4
+++ b/utils/nfsdcltrack/sqlite.h
d2edb4
@@ -21,10 +21,12 @@
d2edb4
 #define _SQLITE_H_
d2edb4
 
d2edb4
 int sqlite_prepare_dbh(const char *topdir);
d2edb4
-int sqlite_maindb_init(const char *topdir);
d2edb4
-int sqlite_insert_client(const unsigned char *clname, const size_t namelen);
d2edb4
+int sqlite_insert_client(const unsigned char *clname, const size_t namelen,
d2edb4
+				const bool has_session, const bool zerotime);
d2edb4
 int sqlite_remove_client(const unsigned char *clname, const size_t namelen);
d2edb4
-int sqlite_check_client(const unsigned char *clname, const size_t namelen);
d2edb4
+int sqlite_check_client(const unsigned char *clname, const size_t namelen,
d2edb4
+				const bool has_session);
d2edb4
 int sqlite_remove_unreclaimed(const time_t grace_start);
d2edb4
+int sqlite_query_reclaiming(const time_t grace_start);
d2edb4
 
d2edb4
 #endif /* _SQLITE_H */
d2edb4
diff --git a/utils/nfsidmap/Makefile.am b/utils/nfsidmap/Makefile.am
d2edb4
index 737a219..91cedfd 100644
d2edb4
--- a/utils/nfsidmap/Makefile.am
d2edb4
+++ b/utils/nfsidmap/Makefile.am
d2edb4
@@ -7,4 +7,4 @@ nfsidmap_SOURCES = nfsidmap.c
d2edb4
 nfsidmap_LDADD = $(LIBNFSIDMAP) -lkeyutils ../../support/nfs/libnfs.a
d2edb4
 
d2edb4
 MAINTAINERCLEANFILES = Makefile.in
d2edb4
-EXTRA_DIST = id_resolver.conf
d2edb4
+EXTRA_DIST = id_resolver.conf $(man8_MANS)
d2edb4
diff --git a/utils/osd_login/Makefile.am b/utils/osd_login/Makefile.am
d2edb4
index 20c2d8c..ded1fd3 100644
d2edb4
--- a/utils/osd_login/Makefile.am
d2edb4
+++ b/utils/osd_login/Makefile.am
d2edb4
@@ -4,6 +4,6 @@
d2edb4
 # overridden at config time.
d2edb4
 sbindir = /sbin
d2edb4
 
d2edb4
-sbin_SCRIPTS = osd_login
d2edb4
+dist_sbin_SCRIPTS = osd_login
d2edb4
 
d2edb4
 MAINTAINERCLEANFILES = Makefile.in
d2edb4
diff --git a/utils/statd/Makefile.am b/utils/statd/Makefile.am
d2edb4
index dc2bfc4..152b680 100644
d2edb4
--- a/utils/statd/Makefile.am
d2edb4
+++ b/utils/statd/Makefile.am
d2edb4
@@ -8,7 +8,7 @@ sbin_PROGRAMS	= statd sm-notify
d2edb4
 dist_sbin_SCRIPTS	= start-statd
d2edb4
 statd_SOURCES = callback.c notlist.c misc.c monitor.c hostname.c \
d2edb4
 	        simu.c stat.c statd.c svc_run.c rmtcall.c \
d2edb4
-	        notlist.h statd.h system.h version.h
d2edb4
+	        notlist.h statd.h system.h
d2edb4
 sm_notify_SOURCES = sm-notify.c
d2edb4
 
d2edb4
 BUILT_SOURCES = $(GENFILES)
d2edb4
@@ -20,7 +20,7 @@ sm_notify_LDADD = ../../support/nsm/libnsm.a \
d2edb4
 		  ../../support/nfs/libnfs.a \
d2edb4
 		  $(LIBNSL) $(LIBCAP) $(LIBTIRPC)
d2edb4
 
d2edb4
-EXTRA_DIST = sim_sm_inter.x $(man8_MANS) COPYRIGHT simulate.c
d2edb4
+EXTRA_DIST = sim_sm_inter.x $(man8_MANS) simulate.c
d2edb4
 
d2edb4
 if CONFIG_RPCGEN
d2edb4
 RPCGEN	= $(top_builddir)/tools/rpcgen/rpcgen