Skip to contentSkip to conversation

nfs-utils: support for lifting grace period early

PATCH Superseded Jeff Layton 2 participants 9 messages Sep 15, 2014 – Sep 19, 2014

Subsystems (2)

v4 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

v4 cover nfs-utils: support for lifting grace period early

v4:
- replace "reclaim_complete" timestamp column with "has_session" boolean
- change to use NFSDCLTRACK_CLIENT_HAS_SESSION environment variable
- only set timestamp on "has_session" clients during "create" op

v3:
- account for change to NFSDCLTRACK_RECLAIM_COMPLETE env var

This is v4 of the patchset to allow the lifting of the grace period early. The changes in this set are a bit more substantial than the kernel piece.

Last week Bruce noted that we have an existing bug in how nfsdcltrack works with v4.1+ clients. It updates the timestamp for v4.1+ clients on a "check" operation, which can lead to the server handing out conflicting locks if the v4.1+ client never sends a RECLAIM_COMPLETE.

To fix this, the program must be able to distingush between v4.0 and v4.1+ clients during both the create and check operations. This set adds the ability for it to do so by looking for the correct environment variables as passed by the kernel.

Historically, nfsdcltrack has not distinguished between v4.0 and v4.1+ clients at alli and has basically applied the v4.0 rules to all clients. On a DB upgrade, the kernel assumes that all clients are v4.0. Once the kernel sends a "create" upcall that indicates that it's a v4.1 client it will update the DB accordingly.

Thus, fixing the bug that Bruce spotted requires both an updated nfs-utils and kernel. With only one or the other, things should basically work as they have so far (with all clients being treated as v4.0 clients).

Jeff Layton (7):
sm-notify: inform the kernel if there were no hosts to notify
nfsdcltrack: update comments in sqlite.c
nfsdcltrack: rename CLD_* constants with CLTRACK_* prefixes
nfsdcltrack: overhaul database initializtion
nfsdcltrack: update schema to v2
nfsdcltrack: grab the NFSDCLTRACK_CLIENT_HAS_SESSION env var if it's
present
nfsdcltrack: fetch NFSDCLTRACK_GRACE_START out of environment

 utils/nfsdcltrack/nfsdcltrack.c | 103 ++++++++++-
 utils/nfsdcltrack/sqlite.c      | 388 ++++++++++++++++++++++++++++++----------
 utils/nfsdcltrack/sqlite.h      |   8 +-
 utils/statd/sm-notify.c         |  25 +++
 4 files changed, 421 insertions(+), 103 deletions(-)
Signature · 1 line
1.9.3
#1 lore raw

PATCH v4 1/7 sm-notify: inform the kernel if there were no hosts to notify

In the event that there no hosts to be notified after a reboot, there's no real reason to force lockd to wait the entire grace period before handing out locks. We're not expecting any reclaim requests to come in that situation.

Have sm-notify do a write to /proc/fs/lockd/nlm_end_grace if that file is present. That informs the kernel that it's OK to go ahead and lift lockd's grace period early.

---
 utils/statd/sm-notify.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)
utils/statd/sm-notify.c +25 -0
index 9dbe5d908336..828a6991f8e6 100644
@@ -42,6 +42,8 @@
 #define NSM_TIMEOUT	2
 #define NSM_MAX_TIMEOUT	120	/* don't make this too big */
 
+#define NLM_END_GRACE_FILE	"/proc/fs/lockd/nlm_end_grace"
+
 struct nsm_host {
 	struct nsm_host *	next;
 	char *			name;
@@ -450,6 +452,28 @@ retry:
 	return sock;
 }
 
+/* Inform the kernel that it's OK to lift lockd's grace period */
+static void
+nsm_lift_grace_period(void)
+{
+	int fd;
+
+	fd = open(NLM_END_GRACE_FILE, O_WRONLY);
+	if (fd < 0) {
+		/* Don't warn if file isn't present */
+		if (errno != ENOENT)
+			xlog(L_WARNING, "Unable to open %s: %m",
+				NLM_END_GRACE_FILE);
+		return;
+	}
+
+	if (write(fd, "Y", 1) < 0)
+		xlog(L_WARNING, "Unable to write to %s: %m", NLM_END_GRACE_FILE);
+
+	close(fd);
+	return;
+}
+
 int
 main(int argc, char **argv)
 {
@@ -534,6 +558,7 @@ usage:		fprintf(stderr,
 	(void)nsm_retire_monitored_hosts();
 	if (nsm_load_notify_list(smn_get_host) == 0) {
 		xlog(D_GENERAL, "No hosts to notify; exiting");
+		nsm_lift_grace_period();
 		return 0;
 	}
 
Signature · 1 line
1.9.3
#2 lore raw

PATCH v4 2/7 nfsdcltrack: update comments in sqlite.c

In reply to Jeff Layton

Clean up and fix some inaccuracies.

---
 utils/nfsdcltrack/sqlite.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)
utils/nfsdcltrack/sqlite.c +4 -6
index bac678980938..352f029d55c0 100644
@@ -21,17 +21,15 @@
  * Explanation:
  *
  * This file contains the code to manage the sqlite backend database for the
- * clstated upcall daemon.
+ * nfsdcltrack usermodehelper upcall program.
  *
  * 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?
+ * clients: an "id" column containing a BLOB with the long-form clientid as
+ * 	    sent by the client, a "time" column containing a timestamp (in
+ * 	    epoch seconds) of when the record was last updated.
  */
 
 #ifdef HAVE_CONFIG_H
Signature · 1 line
1.9.3
#3 lore raw

PATCH v4 3/7 nfsdcltrack: rename CLD_* constants with CLTRACK_* prefixes

In reply to Jeff Layton

From: Jeff Layton <[email protected]>

Since nfsdcld has been dead for a few years now, clean up the prefixes on the constants.

---
 utils/nfsdcltrack/sqlite.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
utils/nfsdcltrack/sqlite.c +6 -6
index 352f029d55c0..52fcaa4d7ca3 100644
@@ -50,10 +50,10 @@
 
 #include "xlog.h"
 
-#define CLD_SQLITE_SCHEMA_VERSION 1
+#define CLTRACK_SQLITE_LATEST_SCHEMA_VERSION 1
 
 /* in milliseconds */
-#define CLD_SQLITE_BUSY_TIMEOUT 10000
+#define CLTRACK_SQLITE_BUSY_TIMEOUT 10000
 
 /* private data structures */
 
@@ -111,7 +111,7 @@ sqlite_prepare_dbh(const char *topdir)
 		return ret;
 	}
 
-	ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT);
+	ret = sqlite3_busy_timeout(dbh, CLTRACK_SQLITE_BUSY_TIMEOUT);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "Unable to set sqlite busy timeout: %d", ret);
 		sqlite3_close(dbh);
@@ -156,7 +156,7 @@ sqlite_maindb_init(const char *topdir)
 	/* 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);
+		       "\"%d\");", CLTRACK_SQLITE_CURRENT_SCHEMA_VERSION);
 	if (ret < 0) {
 		goto out_err;
 	} else if ((size_t)ret >= sizeof(buf)) {
@@ -189,10 +189,10 @@ sqlite_maindb_init(const char *topdir)
 
 	/* process SELECT result */
 	ret = sqlite3_column_int(stmt, 0);
-	if (ret != CLD_SQLITE_SCHEMA_VERSION) {
+	if (ret != CLTRACK_SQLITE_CURRENT_SCHEMA_VERSION) {
 		xlog(L_ERROR, "Unsupported database schema version! "
 			"Expected %d, got %d.",
-			CLD_SQLITE_SCHEMA_VERSION, ret);
+			CLTRACK_SQLITE_CURRENT_SCHEMA_VERSION, ret);
 		ret = -EINVAL;
 		goto out_err;
 	}
Signature · 1 line
1.9.3
#4 lore raw

Re: [PATCH v4 3/7] nfsdcltrack: rename CLD_* constants with CLTRACK_* prefixes

This patch does not compile do to the following errors:
http://fpaste.org/134872/41113211/

With s/CLTRACK_SQLITE_LATEST_SCHEMA_VERSION/CLTRACK_SQLITE_CURRENT_SCHEMA_VERSION/s But this causes the 4 patch not to compile because CLTRACK_SQLITE_LATEST_SCHEMA_VERSION is not defined...

Please straight this out, retest and resubmit!

steved.

Quoted 59 lines · Jeff Layton

From: Jeff Layton <[email protected]>

Since nfsdcld has been dead for a few years now, clean up the prefixes on the constants.

---
 utils/nfsdcltrack/sqlite.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
utils/nfsdcltrack/sqlite.c +6 -6
index 352f029d55c0..52fcaa4d7ca3 100644
@@ -50,10 +50,10 @@
 
 #include "xlog.h"
 
-#define CLD_SQLITE_SCHEMA_VERSION 1
+#define CLTRACK_SQLITE_LATEST_SCHEMA_VERSION 1
 
 /* in milliseconds */
-#define CLD_SQLITE_BUSY_TIMEOUT 10000
+#define CLTRACK_SQLITE_BUSY_TIMEOUT 10000
 
 /* private data structures */
 
@@ -111,7 +111,7 @@ sqlite_prepare_dbh(const char *topdir)
 		return ret;
 	}
 
-	ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT);
+	ret = sqlite3_busy_timeout(dbh, CLTRACK_SQLITE_BUSY_TIMEOUT);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "Unable to set sqlite busy timeout: %d", ret);
 		sqlite3_close(dbh);
@@ -156,7 +156,7 @@ sqlite_maindb_init(const char *topdir)
 	/* 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);
+		       "\"%d\");", CLTRACK_SQLITE_CURRENT_SCHEMA_VERSION);
 	if (ret < 0) {
 		goto out_err;
 	} else if ((size_t)ret >= sizeof(buf)) {
@@ -189,10 +189,10 @@ sqlite_maindb_init(const char *topdir)
 
 	/* process SELECT result */
 	ret = sqlite3_column_int(stmt, 0);
-	if (ret != CLD_SQLITE_SCHEMA_VERSION) {
+	if (ret != CLTRACK_SQLITE_CURRENT_SCHEMA_VERSION) {
 		xlog(L_ERROR, "Unsupported database schema version! "
 			"Expected %d, got %d.",
-			CLD_SQLITE_SCHEMA_VERSION, ret);
+			CLTRACK_SQLITE_CURRENT_SCHEMA_VERSION, ret);
 		ret = -EINVAL;
 		goto out_err;
 	}
#5 lore raw

PATCH v4 4/7 nfsdcltrack: overhaul database initializtion

In reply to Jeff Layton

We have some possibility for races with nfsdcltrack when the DB schema is upgraded.

Suppose we update the nfs-utils package on a machine after the DB has been initialized. With the current scheme of initializing the DB only during the "init" phase, we could end up with a new program that expects a new schema with an old database.

We could try to do a one-time update when the package is installed, but that could be racy. We could get an upcall between when the program is installed and when we run the update. Also, relying on packaging to get that right is tricky at best. To fix this, change how the database initialization and checking of the schema revision works.

On every upcall, attempt to open the db as we normally would. If that fails, then try to create the directory if it doesn't exist and then retry the open. If it fails again, then give up.

If we get a successful open, then query the DB for the schema version. If it matches what we expect, then declare success and move on. If the query fails then assume that the DB isn't set up yet. Start an exclusive transaction, check the schema version again and then set up the DB if no one raced in to create it in the meantime.

This should only add a tiny bit of overhead on most upcalls (just an extra select of the parameters table), and should improve the performance of the "init" upcall. It'll also make it possible to handle DB schema changes sanely.

---
 utils/nfsdcltrack/nfsdcltrack.c |   2 +-
 utils/nfsdcltrack/sqlite.c      | 225 +++++++++++++++++++++++++---------------
 utils/nfsdcltrack/sqlite.h      |   1 -
 3 files changed, 142 insertions(+), 86 deletions(-)
utils/nfsdcltrack/nfsdcltrack.c +1 -1
index 4334340197b1..f2813610be73 100644
@@ -241,7 +241,7 @@ cltrack_init(const char __attribute__((unused)) *unused)
 	}
 
 	/* set up storage db */
-	ret = sqlite_maindb_init(storagedir);
+	ret = sqlite_prepare_dbh(storagedir);
 	if (ret) {
 		xlog(L_ERROR, "Failed to init database: %d", ret);
 		/*
utils/nfsdcltrack/sqlite.c +141 -84

276 lines not on this page · raw message

utils/nfsdcltrack/sqlite.h +0 -1
index ebf04c3cdbaa..3713bfb66e2f 100644
@@ -21,7 +21,6 @@
 #define _SQLITE_H_
 
 int sqlite_prepare_dbh(const char *topdir);
-int sqlite_maindb_init(const 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);
Signature · 1 line
1.9.3
#6 lore raw

PATCH v4 5/7 nfsdcltrack: update schema to v2

In reply to Jeff Layton

From: Jeff Layton <[email protected]>

In order to allow knfsd's lock manager to lift its grace period early, we need to figure out whether all clients have finished reclaiming their state not. Unfortunately, the current code doesn't allow us to ascertain this. All we track for each client is a timestamp that tells us when the last "check" or "create" operation came in.

Not only is this insufficient with clients that use sessions, it's also wrong. We only want to update the timestamp on v4.1 clients when the "create" operation comes in or we can leave the server susceptible to edge condition #2 in RFC5661, section 8.4.3. Once the grace period is lifted, we disallow reclaim on subsequent reboots for clients that have not sent a RECLAIM_COMPLETE.

---
 utils/nfsdcltrack/sqlite.c | 101 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 93 insertions(+), 8 deletions(-)
utils/nfsdcltrack/sqlite.c +93 -8
index dde2f7ff8621..5d38ccb7c0fe 100644
@@ -29,7 +29,9 @@
  *
  * clients: an "id" column containing a BLOB with the long-form clientid as
  * 	    sent by the client, a "time" column containing a timestamp (in
- * 	    epoch seconds) of when the record was last updated.
+ * 	    epoch seconds) of when the record was last updated, and a
+ * 	    "has_session" column containing a boolean value indicating
+ * 	    whether the client has sessions (v4.1+) or not (v4.0).
  */
 
 #ifdef HAVE_CONFIG_H
@@ -50,7 +52,7 @@
 
 #include "xlog.h"
 
-#define CLTRACK_SQLITE_LATEST_SCHEMA_VERSION 1
+#define CLTRACK_SQLITE_LATEST_SCHEMA_VERSION 2
 
 /* in milliseconds */
 #define CLTRACK_SQLITE_BUSY_TIMEOUT 10000
@@ -120,6 +122,81 @@ out:
 	return ret;
 }
 
+static int
+sqlite_maindb_update_v1_to_v2(void)
+{
+	int ret, ret2;
+	char *err;
+
+	/* begin transaction */
+	ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
+				&err);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "Unable to begin transaction: %s", err);
+		goto rollback;
+	}
+
+	/*
+	 * Check schema version again. This time, under an exclusive
+	 * transaction to guard against racing DB setup attempts
+	 */
+	ret = sqlite_query_schema_version();
+	switch (ret) {
+	case 1:
+		/* Still at v1 -- do conversion */
+		break;
+	case CLTRACK_SQLITE_LATEST_SCHEMA_VERSION:
+		/* Someone else raced in and set it up */
+		ret = 0;
+		goto rollback;
+	default:
+		/* Something went wrong -- fail! */
+		ret = -EINVAL;
+		goto rollback;
+	}
+
+	/* create v2 clients table */
+	ret = sqlite3_exec(dbh, "ALTER TABLE clients ADD COLUMN "
+				"has_session INTEGER;",
+				NULL, NULL, &err);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "Unable to update clients table: %s", err);
+		goto rollback;
+	}
+
+	ret = snprintf(buf, sizeof(buf), "UPDATE parameters SET value = %d "
+			"WHERE key = \"version\";",
+			CLTRACK_SQLITE_LATEST_SCHEMA_VERSION);
+	if (ret < 0) {
+		xlog(L_ERROR, "sprintf failed!");
+		goto rollback;
+	} else if ((size_t)ret >= sizeof(buf)) {
+		xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+		ret = -EINVAL;
+		goto rollback;
+	}
+
+	ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "Unable to update schema version: %s", err);
+		goto rollback;
+	}
+
+	ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "Unable to commit transaction: %s", err);
+		goto rollback;
+	}
+out:
+	sqlite3_free(err);
+	return ret;
+rollback:
+	ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err);
+	if (ret2 != SQLITE_OK)
+		xlog(L_ERROR, "Unable to rollback transaction: %s", err);
+	goto out;
+}
+
 /*
  * Start an exclusive transaction and recheck the DB schema version. If it's
  * still zero (indicating a new database) then set it up. If that all works,
@@ -127,9 +204,9 @@ out:
  * transaction. On any error, rollback the transaction.
  */
 int
-sqlite_maindb_init_v1(void)
+sqlite_maindb_init_v2(void)
 {
-	int ret;
+	int ret, ret2;
 	char *err = NULL;
 
 	/* Start a transaction */
@@ -169,7 +246,7 @@ sqlite_maindb_init_v1(void)
 
 	/* create the "clients" table */
 	ret = sqlite3_exec(dbh, "CREATE TABLE clients (id BLOB PRIMARY KEY, "
-				"time INTEGER);",
+				"time INTEGER, has_session INTEGER);",
 				NULL, NULL, &err);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "Unable to create clients table: %s", err);
@@ -207,7 +284,9 @@ out:
 
 rollback:
 	/* Attempt to rollback the transaction */
-	sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err);
+	ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err);
+	if (ret2 != SQLITE_OK)
+		xlog(L_ERROR, "Unable to rollback transaction: %s", err);
 	goto out;
 }
 
@@ -255,9 +334,15 @@ sqlite_prepare_dbh(const char *topdir)
 		/* DB is already set up. Do nothing */
 		ret = 0;
 		break;
+	case 1:
+		/* Old DB -- update to new schema */
+		ret = sqlite_maindb_update_v1_to_v2();
+		if (ret)
+			goto out_close;
+		break;
 	case 0:
 		/* Query failed -- try to set up new DB */
-		ret = sqlite_maindb_init_v1();
+		ret = sqlite_maindb_init_v2();
 		if (ret)
 			goto out_close;
 		break;
@@ -289,7 +374,7 @@ sqlite_insert_client(const unsigned char *clname, const size_t namelen)
 	sqlite3_stmt *stmt = NULL;
 
 	ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients VALUES "
-				      "(?, strftime('%s', 'now'));", -1,
+					"(?, strftime('%s', 'now'), 0);", -1,
 					&stmt, NULL);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "%s: insert statement prepare failed: %s",
Signature · 1 line
1.9.3
#7 lore raw

PATCH v4 6/7 nfsdcltrack: grab the NFSDCLTRACK_CLIENT_HAS_SESSION env var if it's present

In reply to Jeff Layton

...and set the has_session field in the DB based on whether it's true or not. Since we no longer set the timestamp for v4.1+ clients on a check operation, we must be careful to set the timestamp to zero for v4.1+ clients found via the legacy tracker.

---
 utils/nfsdcltrack/nfsdcltrack.c | 31 ++++++++++++++++++++++++++-----
 utils/nfsdcltrack/sqlite.c      | 30 +++++++++++++++++++++++++-----
 utils/nfsdcltrack/sqlite.h      |  6 ++++--
 3 files changed, 55 insertions(+), 12 deletions(-)
utils/nfsdcltrack/nfsdcltrack.c +26 -5
index f2813610be73..80e7e456306e 100644
@@ -37,6 +37,7 @@
 #include <libgen.h>
 #include <sys/inotify.h>
 #include <dirent.h>
+#include <limits.h>
 #ifdef HAVE_SYS_CAPABILITY_H
 #include <sys/prctl.h>
 #include <sys/capability.h>
@@ -254,6 +255,22 @@ cltrack_init(const char __attribute__((unused)) *unused)
 	return ret;
 }
 
+/*
+ * Fetch the contents of the NFSDCLTRACK_CLIENT_HAS_SESSION env var. If
+ * it's set and the first character is 'Y' then return true. Otherwise
+ * return false.
+ */
+static bool
+cltrack_client_has_session(void)
+{
+	char *has_session = getenv("NFSDCLTRACK_CLIENT_HAS_SESSION");
+
+	if (has_session && *has_session == 'Y')
+		return true;
+
+	return false;
+}
+
 static int
 cltrack_create(const char *id)
 {
@@ -270,7 +287,7 @@ cltrack_create(const char *id)
 	if (len < 0)
 		return (int)len;
 
-	ret = sqlite_insert_client(blob, len);
+	ret = sqlite_insert_client(blob, len, cltrack_client_has_session(), false);
 
 	return ret ? -EREMOTEIO : ret;
 }
@@ -297,7 +314,8 @@ cltrack_remove(const char *id)
 }
 
 static int
-cltrack_check_legacy(const unsigned char *blob, const ssize_t len)
+cltrack_check_legacy(const unsigned char *blob, const ssize_t len,
+			bool has_session)
 {
 	int ret;
 	struct stat st;
@@ -323,7 +341,7 @@ cltrack_check_legacy(const unsigned char *blob, const ssize_t len)
 	}
 
 	/* Dir exists, try to insert record into db */
-	ret = sqlite_insert_client(blob, len);
+	ret = sqlite_insert_client(blob, len, has_session, has_session);
 	if (ret) {
 		xlog(D_GENERAL, "Failed to insert client: %d", ret);
 		return -EREMOTEIO;
@@ -343,6 +361,7 @@ cltrack_check(const char *id)
 {
 	int ret;
 	ssize_t len;
+	bool has_session;
 
 	xlog(D_GENERAL, "%s: check client record", __func__);
 
@@ -354,9 +373,11 @@ cltrack_check(const char *id)
 	if (len < 0)
 		return (int)len;
 
-	ret = sqlite_check_client(blob, len);
+	has_session = cltrack_client_has_session();
+
+	ret = sqlite_check_client(blob, len, has_session);
 	if (ret)
-		ret = cltrack_check_legacy(blob, len);
+		ret = cltrack_check_legacy(blob, len, has_session);
 
 	return ret ? -EPERM : ret;
 }
utils/nfsdcltrack/sqlite.c +25 -5
index 5d38ccb7c0fe..4ff0a37b30e9 100644
@@ -368,14 +368,20 @@ out_close:
  * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0)
  */
 int
-sqlite_insert_client(const unsigned char *clname, const size_t namelen)
+sqlite_insert_client(const unsigned char *clname, const size_t namelen,
+			const bool has_session, const bool zerotime)
 {
 	int ret;
 	sqlite3_stmt *stmt = NULL;
 
-	ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients VALUES "
-					"(?, strftime('%s', 'now'), 0);", -1,
-					&stmt, NULL);
+	if (zerotime)
+		ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients "
+				"VALUES (?, 0, ?);", -1, &stmt, NULL);
+	else
+		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));
@@ -390,6 +396,13 @@ sqlite_insert_client(const unsigned char *clname, const size_t namelen)
 		goto out_err;
 	}
 
+	ret = sqlite3_bind_int(stmt, 2, (int)has_session);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "%s: bind int failed: %s", __func__,
+				sqlite3_errmsg(dbh));
+		goto out_err;
+	}
+
 	ret = sqlite3_step(stmt);
 	if (ret == SQLITE_DONE)
 		ret = SQLITE_OK;
@@ -445,7 +458,8 @@ out_err:
  * return an error.
  */
 int
-sqlite_check_client(const unsigned char *clname, const size_t namelen)
+sqlite_check_client(const unsigned char *clname, const size_t namelen,
+			const bool has_session)
 {
 	int ret;
 	sqlite3_stmt *stmt = NULL;
@@ -480,6 +494,12 @@ sqlite_check_client(const unsigned char *clname, const size_t namelen)
 		goto out_err;
 	}
 
+	/* Only update timestamp for v4.0 clients */
+	if (has_session) {
+		ret = SQLITE_OK;
+		goto out_err;
+	}
+
 	sqlite3_finalize(stmt);
 	stmt = NULL;
 	ret = sqlite3_prepare_v2(dbh, "UPDATE OR FAIL clients SET "
utils/nfsdcltrack/sqlite.h +4 -2
index 3713bfb66e2f..bf4ba352b99e 100644
@@ -21,9 +21,11 @@
 #define _SQLITE_H_
 
 int sqlite_prepare_dbh(const char *topdir);
-int sqlite_insert_client(const unsigned char *clname, const size_t namelen);
+int sqlite_insert_client(const unsigned char *clname, const size_t namelen,
+				const bool has_session, const bool zerotime);
 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_check_client(const unsigned char *clname, const size_t namelen,
+				const bool has_session);
 int sqlite_remove_unreclaimed(const time_t grace_start);
 
 #endif /* _SQLITE_H */
Signature · 1 line
1.9.3
#8 lore raw

PATCH v4 7/7 nfsdcltrack: fetch NFSDCLTRACK_GRACE_START out of environment

In reply to Jeff Layton

Allow the fetching of NFSDCLTRACK_GRACE_START out of environment variables. If it's present in the "create" or "init" upcalls, then we can use that to query the database to see whether there are any clients that have not issued a RECLAIM_COMPLETE since that time.

If there aren't any, then we know that all reclaim activity is now done and we can then cue the kernel to lift the grace period.

---
 utils/nfsdcltrack/nfsdcltrack.c | 72 ++++++++++++++++++++++++++++++++++++++++-
 utils/nfsdcltrack/sqlite.c      | 40 +++++++++++++++++++++++
 utils/nfsdcltrack/sqlite.h      |  1 +
 3 files changed, 112 insertions(+), 1 deletion(-)
utils/nfsdcltrack/nfsdcltrack.c +71 -1
index 80e7e456306e..fcdda7f66b8b 100644
@@ -50,6 +50,8 @@
 #define CLD_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcltrack"
 #endif
 
+#define NFSD_END_GRACE_FILE "/proc/fs/nfsd/v4_end_grace"
+
 /* defined by RFC 3530 */
 #define NFS4_OPAQUE_LIMIT	1024
 
@@ -211,6 +213,64 @@ cltrack_set_caps(void)
 	return ret;
 }
 
+/* Inform the kernel that it's OK to lift nfsd's grace period */
+static void
+cltrack_lift_grace_period(void)
+{
+	int fd;
+
+	fd = open(NFSD_END_GRACE_FILE, O_WRONLY);
+	if (fd < 0) {
+		/* Don't warn if file isn't present */
+		if (errno != ENOENT)
+			xlog(L_WARNING, "Unable to open %s: %m",
+				NFSD_END_GRACE_FILE);
+		return;
+	}
+
+	if (write(fd, "Y", 1) < 0)
+		xlog(L_WARNING, "Unable to write to %s: %m",
+				NFSD_END_GRACE_FILE);
+
+	close(fd);
+	return;
+}
+
+/*
+ * Fetch the contents of the NFSDCLTRACK_GRACE_START env var. If it's not set
+ * or there's an error converting it to time_t, then return LONG_MAX.
+ */
+static time_t
+cltrack_get_grace_start(void)
+{
+	time_t grace_start;
+	char *end;
+	char *grace_start_str = getenv("NFSDCLTRACK_GRACE_START");
+
+	if (!grace_start_str)
+		return LONG_MAX;
+
+	errno = 0;
+	grace_start = strtol(grace_start_str, &end, 0);
+	/* Problem converting or value is too large? */
+	if (errno)
+		return LONG_MAX;
+
+	return grace_start;
+}
+
+static bool
+cltrack_reclaims_complete(void)
+{
+	time_t grace_start = cltrack_get_grace_start();
+
+	/* Don't query DB if we didn't get a valid boot time */
+	if (grace_start == LONG_MAX)
+		return false;
+
+	return !sqlite_query_reclaiming(grace_start);
+}
+
 static int
 cltrack_init(const char __attribute__((unused)) *unused)
 {
@@ -251,7 +311,11 @@ cltrack_init(const char __attribute__((unused)) *unused)
 		 * stop upcalling until the problem is resolved.
 		 */
 		ret = -EACCES;
+	} else {
+		if (cltrack_reclaims_complete())
+			cltrack_lift_grace_period();
 	}
+
 	return ret;
 }
 
@@ -276,6 +340,7 @@ cltrack_create(const char *id)
 {
 	int ret;
 	ssize_t len;
+	bool has_session;
 
 	xlog(D_GENERAL, "%s: create client record.", __func__);
 
@@ -287,7 +352,12 @@ cltrack_create(const char *id)
 	if (len < 0)
 		return (int)len;
 
-	ret = sqlite_insert_client(blob, len, cltrack_client_has_session(), false);
+	has_session = cltrack_client_has_session();
+
+	ret = sqlite_insert_client(blob, len, has_session, false);
+
+	if (!ret && has_session && cltrack_reclaims_complete())
+		cltrack_lift_grace_period();
 
 	return ret ? -EREMOTEIO : ret;
 }
utils/nfsdcltrack/sqlite.c +40 -0
index 4ff0a37b30e9..fb45c4af5edb 100644
@@ -558,3 +558,43 @@ sqlite_remove_unreclaimed(time_t grace_start)
 	sqlite3_free(err);
 	return ret;
 }
+
+/*
+ * Are there any clients that are possibly still reclaiming? Return a positive
+ * integer (usually number of clients) if so. If not, then return 0. On any
+ * error, return non-zero.
+ */
+int
+sqlite_query_reclaiming(const time_t grace_start)
+{
+	int ret;
+	sqlite3_stmt *stmt = NULL;
+
+	ret = sqlite3_prepare_v2(dbh, "SELECT count(*) FROM clients WHERE "
+				      "time < ? OR has_session != 1", -1, &stmt, NULL);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "%s: unable to prepare select statement: %s",
+				__func__, sqlite3_errstr(ret));
+		return ret;
+	}
+
+	ret = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)grace_start);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "%s: bind int64 failed: %s",
+				__func__, sqlite3_errstr(ret));
+		return ret;
+	}
+
+	ret = sqlite3_step(stmt);
+	if (ret != SQLITE_ROW) {
+		xlog(L_ERROR, "%s: unexpected return code from select: %s",
+				__func__, sqlite3_errstr(ret));
+		return ret;
+	}
+
+	ret = sqlite3_column_int(stmt, 0);
+	sqlite3_finalize(stmt);
+	xlog(D_GENERAL, "%s: there are %d clients that have not completed "
+			"reclaim", __func__, ret);
+	return ret;
+}
utils/nfsdcltrack/sqlite.h +1 -0
index bf4ba352b99e..06e7c044a330 100644
@@ -27,5 +27,6 @@ int sqlite_remove_client(const unsigned char *clname, const size_t namelen);
 int sqlite_check_client(const unsigned char *clname, const size_t namelen,
 				const bool has_session);
 int sqlite_remove_unreclaimed(const time_t grace_start);
+int sqlite_query_reclaiming(const time_t grace_start);
 
 #endif /* _SQLITE_H */
Signature · 1 line
1.9.3