From 35f198de1851a7d57064546b7ced677b6fabee27 Mon Sep 17 00:00:00 2001
From: Rusty Russell <rusty@rustcorp.com.au>
Date: Wed, 30 Nov 2011 12:09:18 +1030
Subject: [PATCH] tdb2: add a capability list from the header.

This allows even more extensibility in future: in particular, the top
bits of each capability tell us what to do if we don't understand it:
fail the open, fail to open for write, or don't try to check the
format.

tdb_check needs to understand the capability list so it can know to
skip over it: each element in the list is prefixed with the type tag
and the length.
---
 ccan/tdb2/check.c                          |  43 +++-
 ccan/tdb2/open.c                           |  54 +++++
 ccan/tdb2/private.h                        |  21 +-
 ccan/tdb2/test/failtest_helper.h           |   2 +-
 ccan/tdb2/test/layout.c                    |  65 +++++-
 ccan/tdb2/test/layout.h                    |  22 ++-
 ccan/tdb2/test/run-03-coalesce.c           |  10 +-
 ccan/tdb2/test/run-50-multiple-freelists.c |   2 +-
 ccan/tdb2/test/run-capabilities.c          | 218 +++++++++++++++++++++
 tools/ccanlint/ccanlint.c                  |   1 +
 10 files changed, 411 insertions(+), 27 deletions(-)
 create mode 100644 ccan/tdb2/test/run-capabilities.c

diff --git a/ccan/tdb2/check.c b/ccan/tdb2/check.c
index 230eaee8..238a5b3a 100644
--- a/ccan/tdb2/check.c
+++ b/ccan/tdb2/check.c
@@ -31,11 +31,12 @@ static bool append(tdb_off_t **arr, size_t *num, tdb_off_t off)
 }
 
 static enum TDB_ERROR check_header(struct tdb_context *tdb, tdb_off_t *recovery,
-				   uint64_t *features)
+				   uint64_t *features, size_t *num_capabilities)
 {
 	uint64_t hash_test;
 	struct tdb_header hdr;
 	enum TDB_ERROR ecode;
+	tdb_off_t off, next;
 
 	ecode = tdb_read_convert(tdb, 0, &hdr, sizeof(hdr));
 	if (ecode != TDB_SUCCESS) {
@@ -81,6 +82,24 @@ static enum TDB_ERROR check_header(struct tdb_context *tdb, tdb_off_t *recovery,
 		}
 	}
 
+	for (off = hdr.capabilities; off && ecode == TDB_SUCCESS; off = next) {
+		const struct tdb_capability *cap;
+		enum TDB_ERROR err;
+
+		cap = tdb_access_read(tdb, off, sizeof(*cap), true);
+		if (TDB_PTR_IS_ERR(cap)) {
+			return TDB_PTR_ERR(cap);
+		}
+
+		/* All capabilities are unknown. */
+		err = unknown_capability(tdb, "tdb_check", cap->type);
+		next = cap->next;
+		tdb_access_release(tdb, cap);
+		if (err)
+			return err;
+		(*num_capabilities)++;
+	}
+
 	/* Don't check reserved: they *can* be used later. */
 	return TDB_SUCCESS;
 }
@@ -435,12 +454,12 @@ fail:
 
 static enum TDB_ERROR check_hash(struct tdb_context *tdb,
 				 tdb_off_t used[],
-				 size_t num_used, size_t num_ftables,
+				 size_t num_used, size_t num_other_used,
 				 enum TDB_ERROR (*check)(TDB_DATA, TDB_DATA, void *),
 				 void *data)
 {
-	/* Free tables also show up as used. */
-	size_t num_found = num_ftables;
+	/* Free tables and capabilities also show up as used. */
+	size_t num_found = num_other_used;
 	enum TDB_ERROR ecode;
 
 	ecode = check_hash_tree(tdb, offsetof(struct tdb_header, hashtable),
@@ -698,7 +717,8 @@ static enum TDB_ERROR check_linear(struct tdb_context *tdb,
 		} else if (rec_magic(&rec.u) == TDB_USED_MAGIC
 			   || rec_magic(&rec.u) == TDB_CHAIN_MAGIC
 			   || rec_magic(&rec.u) == TDB_HTABLE_MAGIC
-			   || rec_magic(&rec.u) == TDB_FTABLE_MAGIC) {
+			   || rec_magic(&rec.u) == TDB_FTABLE_MAGIC
+			   || rec_magic(&rec.u) == TDB_CAP_MAGIC) {
 			uint64_t klen, dlen, extra;
 
 			/* This record is used! */
@@ -734,7 +754,8 @@ static enum TDB_ERROR check_linear(struct tdb_context *tdb,
 
 			/* Check that records have correct 0 at end (but may
 			 * not in future). */
-			if (extra && !features) {
+			if (extra && !features
+			    && rec_magic(&rec.u) != TDB_CAP_MAGIC) {
 				const char *p;
 				char c;
 				p = tdb_access_read(tdb, off + sizeof(rec.u)
@@ -778,13 +799,14 @@ enum TDB_ERROR tdb_check_(struct tdb_context *tdb,
 			  void *data)
 {
 	tdb_off_t *fr = NULL, *used = NULL, ft, recovery;
-	size_t num_free = 0, num_used = 0, num_found = 0, num_ftables = 0;
+	size_t num_free = 0, num_used = 0, num_found = 0, num_ftables = 0,
+		num_capabilities = 0;
 	uint64_t features;
 	enum TDB_ERROR ecode;
 
 	if (tdb->flags & TDB_CANT_CHECK) {
 		return tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING,
-				  "tdb_check: database has unknown features,"
+				  "tdb_check: database has unknown capability,"
 				  " cannot check.");
 	}
 
@@ -805,7 +827,7 @@ enum TDB_ERROR tdb_check_(struct tdb_context *tdb,
 		return tdb->last_error = ecode;
 	}
 
-	ecode = check_header(tdb, &recovery, &features);
+	ecode = check_header(tdb, &recovery, &features, &num_capabilities);
 	if (ecode != TDB_SUCCESS)
 		goto out;
 
@@ -828,7 +850,8 @@ enum TDB_ERROR tdb_check_(struct tdb_context *tdb,
 	}
 
 	/* FIXME: Check key uniqueness? */
-	ecode = check_hash(tdb, used, num_used, num_ftables, check, data);
+	ecode = check_hash(tdb, used, num_used, num_ftables + num_capabilities,
+			   check, data);
 	if (ecode != TDB_SUCCESS)
 		goto out;
 
diff --git a/ccan/tdb2/open.c b/ccan/tdb2/open.c
index 3c6abe72..02ec0eb6 100644
--- a/ccan/tdb2/open.c
+++ b/ccan/tdb2/open.c
@@ -135,6 +135,7 @@ static enum TDB_ERROR tdb_new_database(struct tdb_context *tdb,
 	newdb.hdr.recovery = 0;
 	newdb.hdr.features_used = newdb.hdr.features_offered = TDB_FEATURE_MASK;
 	newdb.hdr.seqnum = 0;
+	newdb.hdr.capabilities = 0;
 	memset(newdb.hdr.reserved, 0, sizeof(newdb.hdr.reserved));
 	/* Initial hashes are empty. */
 	memset(newdb.hdr.hashtable, 0, sizeof(newdb.hdr.hashtable));
@@ -375,6 +376,54 @@ static bool is_tdb1(struct tdb1_header *hdr, const void *buf, ssize_t rlen)
 		|| hdr->version == TDB1_BYTEREV(TDB1_VERSION);
 }
 
+/* The top three bits of the capability tell us whether it matters. */
+enum TDB_ERROR unknown_capability(struct tdb_context *tdb, const char *caller,
+				  tdb_off_t type)
+{
+	if (type & TDB_CAP_NOOPEN) {
+		return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR,
+				  "%s: file has unknown capability %llu",
+				  caller, type & TDB_CAP_NOOPEN);
+	}
+
+	if ((type & TDB_CAP_NOWRITE) && !(tdb->flags & TDB_RDONLY)) {
+		return tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_ERROR,
+				  "%s: file has unknown capability %llu"
+				  " (cannot write to it)",
+				  caller, type & TDB_CAP_NOOPEN);
+	}
+
+	if (type & TDB_CAP_NOCHECK) {
+		tdb->flags |= TDB_CANT_CHECK;
+	}
+	return TDB_SUCCESS;
+}
+
+static enum TDB_ERROR capabilities_ok(struct tdb_context *tdb,
+				      tdb_off_t capabilities)
+{
+	tdb_off_t off, next;
+	enum TDB_ERROR ecode = TDB_SUCCESS;
+	const struct tdb_capability *cap;
+
+	/* Check capability list. */
+	for (off = capabilities; off && ecode == TDB_SUCCESS; off = next) {
+		cap = tdb_access_read(tdb, off, sizeof(*cap), true);
+		if (TDB_PTR_IS_ERR(cap)) {
+			return TDB_PTR_ERR(cap);
+		}
+
+		switch (cap->type & TDB_CAP_TYPE_MASK) {
+		/* We don't understand any capabilities (yet). */
+		default:
+			ecode = unknown_capability(tdb, "tdb_open", cap->type);
+		}
+		next = cap->next;
+		tdb_access_release(tdb, cap);
+	}
+	return ecode;
+}
+
 struct tdb_context *tdb_open(const char *name, int tdb_flags,
 			     int open_flags, mode_t mode,
 			     union tdb_attribute *attr)
@@ -667,6 +716,11 @@ struct tdb_context *tdb_open(const char *name, int tdb_flags,
 		goto fail;
 	}
 
+	ecode = capabilities_ok(tdb, hdr.capabilities);
+	if (ecode != TDB_SUCCESS) {
+		goto fail;
+	}
+
 	/* Clear any features we don't understand. */
  	if ((open_flags & O_ACCMODE) != O_RDONLY) {
 		hdr.features_used &= TDB_FEATURE_MASK;
diff --git a/ccan/tdb2/private.h b/ccan/tdb2/private.h
index 8778dbc3..2062ac29 100644
--- a/ccan/tdb2/private.h
+++ b/ccan/tdb2/private.h
@@ -60,11 +60,18 @@ typedef uint64_t tdb_off_t;
 #define TDB_HTABLE_MAGIC ((uint64_t)0x1888)
 #define TDB_CHAIN_MAGIC ((uint64_t)0x1777)
 #define TDB_FTABLE_MAGIC ((uint64_t)0x1666)
+#define TDB_CAP_MAGIC ((uint64_t)0x1555)
 #define TDB_FREE_MAGIC ((uint64_t)0xFE)
 #define TDB_HASH_MAGIC (0xA1ABE11A01092008ULL)
 #define TDB_RECOVERY_MAGIC (0xf53bc0e7ad124589ULL)
 #define TDB_RECOVERY_INVALID_MAGIC (0x0ULL)
 
+/* Capability bits. */
+#define TDB_CAP_TYPE_MASK	0x1FFFFFFFFFFFFFFFULL
+#define TDB_CAP_NOCHECK		0x8000000000000000ULL
+#define TDB_CAP_NOWRITE		0x4000000000000000ULL
+#define TDB_CAP_NOOPEN		0x2000000000000000ULL
+
 #define TDB_OFF_IS_ERR(off) unlikely(off >= (tdb_off_t)(long)TDB_ERR_LAST)
 #define TDB_OFF_TO_ERR(off) ((enum TDB_ERROR)(long)(off))
 #define TDB_ERR_TO_OFF(ecode) ((tdb_off_t)(long)(ecode))
@@ -230,7 +237,8 @@ struct tdb_header {
 
 	uint64_t seqnum; /* Sequence number for TDB_SEQNUM */
 
-	tdb_off_t reserved[23];
+	tdb_off_t capabilities; /* Optional linked list of capabilities. */
+	tdb_off_t reserved[22];
 
 	/* Top level hash table. */
 	tdb_off_t hashtable[1ULL << TDB_TOPLEVEL_HASH_BITS];
@@ -242,6 +250,13 @@ struct tdb_freetable {
 	tdb_off_t buckets[TDB_FREE_BUCKETS];
 };
 
+struct tdb_capability {
+	struct tdb_used_record hdr;
+	tdb_off_t type;
+	tdb_off_t next;
+	/* ... */
+};
+
 /* Information about a particular (locked) hash entry. */
 struct hash_info {
 	/* Full hash value of entry. */
@@ -377,6 +392,8 @@ enum TDB_ERROR delete_from_hash(struct tdb_context *tdb, struct hash_info *h);
 
 /* For tdb_check */
 bool is_subhash(tdb_off_t val);
+enum TDB_ERROR unknown_capability(struct tdb_context *tdb, const char *caller,
+				  tdb_off_t type);
 
 /* free.c: */
 enum TDB_ERROR tdb_ftable_init(struct tdb_context *tdb);
@@ -395,7 +412,7 @@ enum TDB_ERROR add_free_record(struct tdb_context *tdb,
 			       enum tdb_lock_flags waitflag,
 			       bool coalesce_ok);
 
-/* Set up header for a used/ftable/htable/chain record. */
+/* Set up header for a used/ftable/htable/chain/capability record. */
 enum TDB_ERROR set_header(struct tdb_context *tdb,
 			  struct tdb_used_record *rec,
 			  unsigned magic, uint64_t keylen, uint64_t datalen,
diff --git a/ccan/tdb2/test/failtest_helper.h b/ccan/tdb2/test/failtest_helper.h
index a3c68088..4130aff1 100644
--- a/ccan/tdb2/test/failtest_helper.h
+++ b/ccan/tdb2/test/failtest_helper.h
@@ -4,7 +4,7 @@
 #include <stdbool.h>
 
 /* FIXME: Check these! */
-#define INITIAL_TDB_MALLOC	"open.c", 396, FAILTEST_MALLOC
+#define INITIAL_TDB_MALLOC	"open.c", 445, FAILTEST_MALLOC
 #define URANDOM_OPEN		"open.c", 62, FAILTEST_OPEN
 #define URANDOM_READ		"open.c", 42, FAILTEST_READ
 
diff --git a/ccan/tdb2/test/layout.c b/ccan/tdb2/test/layout.c
index 50b3cbe7..ae37f565 100644
--- a/ccan/tdb2/test/layout.c
+++ b/ccan/tdb2/test/layout.c
@@ -39,6 +39,26 @@ void tdb_layout_add_free(struct tdb_layout *layout, tdb_len_t len,
 	add(layout, elem);
 }
 
+void tdb_layout_add_capability(struct tdb_layout *layout,
+			       uint64_t type,
+			       bool write_breaks,
+			       bool check_breaks,
+			       bool open_breaks,
+			       tdb_len_t extra)
+{
+	union tdb_layout_elem elem;
+	elem.base.type = CAPABILITY;
+	elem.capability.type = type;
+	if (write_breaks)
+		elem.capability.type |= TDB_CAP_NOWRITE;
+	if (open_breaks)
+		elem.capability.type |= TDB_CAP_NOOPEN;
+	if (check_breaks)
+		elem.capability.type |= TDB_CAP_NOCHECK;
+	elem.capability.extra = extra;
+	add(layout, elem);
+}
+
 static struct tdb_data dup_key(struct tdb_data key)
 {
 	struct tdb_data ret;
@@ -81,6 +101,11 @@ static tdb_len_t hashtable_len(struct tle_hashtable *htable)
 		+ htable->extra;
 }
 
+static tdb_len_t capability_len(struct tle_capability *cap)
+{
+	return sizeof(struct tdb_capability) + cap->extra;
+}
+
 static tdb_len_t freetable_len(struct tle_freetable *ftable)
 {
 	return sizeof(struct tdb_freetable);
@@ -122,6 +147,26 @@ static void set_hashtable(void *mem, struct tdb_context *tdb,
 	add_zero_pad(u, len, htable->extra);
 }
 
+static void set_capability(void *mem, struct tdb_context *tdb,
+			   struct tle_capability *cap, struct tdb_header *hdr,
+			   tdb_off_t last_cap)
+{
+	struct tdb_capability *c = mem;
+	tdb_len_t len = sizeof(*c) - sizeof(struct tdb_used_record) + cap->extra;
+
+	c->type = cap->type;
+	c->next = 0;
+	set_header(tdb, &c->hdr, TDB_CAP_MAGIC, 0, len, len, 0);
+
+	/* Append to capability list. */
+	if (!last_cap) {
+		hdr->capabilities = cap->base.off;
+	} else {
+		c = (struct tdb_capability *)((char *)hdr + last_cap);
+		c->next = cap->base.off;
+	}
+}
+
 static void set_freetable(void *mem, struct tdb_context *tdb,
 			 struct tle_freetable *freetable, struct tdb_header *hdr,
 			 tdb_off_t last_ftable)
@@ -228,10 +273,11 @@ static struct tle_freetable *find_ftable(struct tdb_layout *layout, unsigned num
 
 /* FIXME: Support TDB_CONVERT */
 struct tdb_context *tdb_layout_get(struct tdb_layout *layout,
+				   void (*freefn)(void *),
 				   union tdb_attribute *attr)
 {
 	unsigned int i;
-	tdb_off_t off, len, last_ftable;
+	tdb_off_t off, len, last_ftable, last_cap;
 	char *mem;
 	struct tdb_context *tdb;
 
@@ -254,6 +300,9 @@ struct tdb_context *tdb_layout_get(struct tdb_layout *layout,
 		case HASHTABLE:
 			len = hashtable_len(&e->hashtable);
 			break;
+		case CAPABILITY:
+			len = capability_len(&e->capability);
+			break;
 		default:
 			abort();
 		}
@@ -268,11 +317,12 @@ struct tdb_context *tdb_layout_get(struct tdb_layout *layout,
 	memcpy(mem, tdb->file->map_ptr, sizeof(struct tdb_header));
 
 	/* Mug the tdb we have to make it use this. */
-	free(tdb->file->map_ptr);
+	freefn(tdb->file->map_ptr);
 	tdb->file->map_ptr = mem;
 	tdb->file->map_size = off;
 
 	last_ftable = 0;
+	last_cap = 0;
 	for (i = 0; i < layout->num_elems; i++) {
 		union tdb_layout_elem *e = &layout->elem[i];
 		switch (e->base.type) {
@@ -290,6 +340,11 @@ struct tdb_context *tdb_layout_get(struct tdb_layout *layout,
 		case HASHTABLE:
 			set_hashtable(mem + e->base.off, tdb, &e->hashtable);
 			break;
+		case CAPABILITY:
+			set_capability(mem + e->base.off, tdb, &e->capability,
+				       (struct tdb_header *)mem, last_cap);
+			last_cap = e->base.off;
+			break;
 		}
 	}
 	/* Must have a free table! */
@@ -316,10 +371,10 @@ struct tdb_context *tdb_layout_get(struct tdb_layout *layout,
 	return tdb;
 }
 
-void tdb_layout_write(struct tdb_layout *layout, union tdb_attribute *attr,
-		      const char *filename)
+void tdb_layout_write(struct tdb_layout *layout, void (*freefn)(void *),
+		       union tdb_attribute *attr, const char *filename)
 {
-	struct tdb_context *tdb = tdb_layout_get(layout, attr);
+	struct tdb_context *tdb = tdb_layout_get(layout, freefn, attr);
 	int fd;
 
 	fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT,  0600);
diff --git a/ccan/tdb2/test/layout.h b/ccan/tdb2/test/layout.h
index 66491137..9a714846 100644
--- a/ccan/tdb2/test/layout.h
+++ b/ccan/tdb2/test/layout.h
@@ -9,21 +9,30 @@ void tdb_layout_add_free(struct tdb_layout *layout, tdb_len_t len,
 void tdb_layout_add_used(struct tdb_layout *layout,
 			 TDB_DATA key, TDB_DATA data,
 			 tdb_len_t extra);
+void tdb_layout_add_capability(struct tdb_layout *layout,
+			       uint64_t type,
+			       bool write_breaks,
+			       bool check_breaks,
+			       bool open_breaks,
+			       tdb_len_t extra);
+
 #if 0 /* FIXME: Allow allocation of subtables */
 void tdb_layout_add_hashtable(struct tdb_layout *layout,
 			      int htable_parent, /* -1 == toplevel */
 			      unsigned int bucket,
 			      tdb_len_t extra);
 #endif
+/* freefn is needed if we're using failtest_free. */
 struct tdb_context *tdb_layout_get(struct tdb_layout *layout,
+				   void (*freefn)(void *),
 				   union tdb_attribute *attr);
-void tdb_layout_write(struct tdb_layout *layout, union tdb_attribute *attr,
-		      const char *filename);
+void tdb_layout_write(struct tdb_layout *layout, void (*freefn)(void *),
+		       union tdb_attribute *attr, const char *filename);
 
 void tdb_layout_free(struct tdb_layout *layout);
 
 enum layout_type {
-	FREETABLE, FREE, DATA, HASHTABLE,
+	FREETABLE, FREE, DATA, HASHTABLE, CAPABILITY
 };
 
 /* Shared by all union members. */
@@ -56,12 +65,19 @@ struct tle_hashtable {
 	tdb_len_t extra;
 };
 
+struct tle_capability {
+	struct tle_base base;
+	uint64_t type;
+	tdb_len_t extra;
+};
+
 union tdb_layout_elem {
 	struct tle_base base;
 	struct tle_freetable ftable;
 	struct tle_free free;
 	struct tle_used used;
 	struct tle_hashtable hashtable;
+	struct tle_capability capability;
 };
 
 struct tdb_layout {
diff --git a/ccan/tdb2/test/run-03-coalesce.c b/ccan/tdb2/test/run-03-coalesce.c
index c64b2bc5..99f94fe1 100644
--- a/ccan/tdb2/test/run-03-coalesce.c
+++ b/ccan/tdb2/test/run-03-coalesce.c
@@ -36,7 +36,7 @@ int main(int argc, char *argv[])
 	tdb_layout_add_freetable(layout);
 	len = 1024;
 	tdb_layout_add_free(layout, len, 0);
-	tdb_layout_write(layout, &tap_log_attr, "run-03-coalesce.tdb");
+	tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb");
 	/* NOMMAP is for lockcheck. */
 	tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0,
 		       &tap_log_attr);
@@ -62,7 +62,7 @@ int main(int argc, char *argv[])
 	tdb_layout_add_freetable(layout);
 	tdb_layout_add_free(layout, 1024, 0);
 	tdb_layout_add_used(layout, key, data, 6);
-	tdb_layout_write(layout, &tap_log_attr, "run-03-coalesce.tdb");
+	tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb");
 	/* NOMMAP is for lockcheck. */
 	tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0,
 		       &tap_log_attr);
@@ -88,7 +88,7 @@ int main(int argc, char *argv[])
 	tdb_layout_add_freetable(layout);
 	tdb_layout_add_free(layout, 1024, 0);
 	tdb_layout_add_free(layout, 2048, 0);
-	tdb_layout_write(layout, &tap_log_attr, "run-03-coalesce.tdb");
+	tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb");
 	/* NOMMAP is for lockcheck. */
 	tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0,
 		       &tap_log_attr);
@@ -118,7 +118,7 @@ int main(int argc, char *argv[])
 	tdb_layout_add_free(layout, 1024, 0);
 	tdb_layout_add_free(layout, 512, 0);
 	tdb_layout_add_used(layout, key, data, 6);
-	tdb_layout_write(layout, &tap_log_attr, "run-03-coalesce.tdb");
+	tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb");
 	/* NOMMAP is for lockcheck. */
 	tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0,
 		       &tap_log_attr);
@@ -147,7 +147,7 @@ int main(int argc, char *argv[])
 	tdb_layout_add_free(layout, 1024, 0);
 	tdb_layout_add_free(layout, 512, 0);
 	tdb_layout_add_free(layout, 256, 0);
-	tdb_layout_write(layout, &tap_log_attr, "run-03-coalesce.tdb");
+	tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb");
 	/* NOMMAP is for lockcheck. */
 	tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0,
 		       &tap_log_attr);
diff --git a/ccan/tdb2/test/run-50-multiple-freelists.c b/ccan/tdb2/test/run-50-multiple-freelists.c
index 10eaf41d..44fee941 100644
--- a/ccan/tdb2/test/run-50-multiple-freelists.c
+++ b/ccan/tdb2/test/run-50-multiple-freelists.c
@@ -35,7 +35,7 @@ int main(int argc, char *argv[])
 	key.dsize--;
 	tdb_layout_add_used(layout, key, data, 8);
 	tdb_layout_add_free(layout, 40, 0);
-	tdb = tdb_layout_get(layout, &seed);
+	tdb = tdb_layout_get(layout, free, &seed);
 	ok1(tdb_check(tdb, NULL, NULL) == 0);
 
 	off = get_free(tdb, 0, 80 - sizeof(struct tdb_used_record), 0,
diff --git a/ccan/tdb2/test/run-capabilities.c b/ccan/tdb2/test/run-capabilities.c
new file mode 100644
index 00000000..e3026561
--- /dev/null
+++ b/ccan/tdb2/test/run-capabilities.c
@@ -0,0 +1,218 @@
+#include <ccan/failtest/failtest_override.h>
+#include "tdb2-source.h"
+#include <ccan/tap/tap.h>
+#include "logging.h"
+#include "layout.h"
+#include "failtest_helper.h"
+#include <stdarg.h>
+#include <err.h>
+
+static size_t len_of(bool breaks_check, bool breaks_write, bool breaks_open)
+{
+	size_t len = 0;
+	if (breaks_check)
+		len += 8;
+	if (breaks_write)
+		len += 16;
+	if (breaks_open)
+		len += 32;
+	return len;
+}
+
+/* Creates a TDB with various capabilities. */
+static void create_tdb(const char *name,
+		       unsigned int cap,
+		       bool breaks_check,
+		       bool breaks_write,
+		       bool breaks_open, ...)
+{
+	TDB_DATA key, data;
+	va_list ap;
+	struct tdb_layout *layout;
+	struct tdb_context *tdb;
+	int fd;
+
+	key = tdb_mkdata("Hello", 5);
+	data = tdb_mkdata("world", 5);
+
+	/* Create a TDB with some data, and some capabilities */
+	layout = new_tdb_layout();
+	tdb_layout_add_freetable(layout);
+	tdb_layout_add_used(layout, key, data, 6);
+	tdb_layout_add_free(layout, 80, 0);
+	tdb_layout_add_capability(layout, cap,
+				  breaks_write, breaks_check, breaks_open,
+				  len_of(breaks_check, breaks_write, breaks_open));
+
+	va_start(ap, breaks_open);
+	while ((cap = va_arg(ap, int)) != 0) {
+		breaks_check = va_arg(ap, int);
+		breaks_write = va_arg(ap, int);
+		breaks_open = va_arg(ap, int);
+
+		key.dsize--;
+		tdb_layout_add_used(layout, key, data, 11 - key.dsize);
+		tdb_layout_add_free(layout, 80, 0);
+		tdb_layout_add_capability(layout, cap,
+					  breaks_write, breaks_check,
+					  breaks_open,
+					  len_of(breaks_check, breaks_write,
+						 breaks_open));
+	}
+	va_end(ap);
+
+	/* We open-code this, because we need to use the failtest write. */
+	tdb = tdb_layout_get(layout, failtest_free, &tap_log_attr);
+
+	fd = open(name, O_RDWR|O_TRUNC|O_CREAT, 0600);
+	if (fd < 0)
+		err(1, "opening %s for writing", name);
+	if (write(fd, tdb->file->map_ptr, tdb->file->map_size)
+	    != tdb->file->map_size)
+		err(1, "writing %s", name);
+	close(fd);
+	tdb_close(tdb);
+	tdb_layout_free(layout);
+}
+
+/* Note all the "goto out" early exits: they're to shorten failtest time. */
+int main(int argc, char *argv[])
+{
+	struct tdb_context *tdb;
+
+	failtest_init(argc, argv);
+	failtest_hook = block_repeat_failures;
+	failtest_exit_check = exit_check_log;
+	plan_tests(35);
+
+	failtest_suppress = true;
+	/* Capability says you can ignore it? */
+	create_tdb("run-capabilities.tdb", 1, false, false, false, 0);
+
+	failtest_suppress = false;
+	tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0,
+		       &tap_log_attr);
+	failtest_suppress = true;
+	if (!ok1(tdb))
+		goto out;
+	ok1(tap_log_messages == 0);
+	ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS);
+	ok1(tap_log_messages == 0);
+	tdb_close(tdb);
+
+	/* Two capabilitues say you can ignore them? */
+	create_tdb("run-capabilities.tdb",
+		   1, false, false, false,
+		   2, false, false, false, 0);
+
+	failtest_suppress = false;
+	tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0,
+		       &tap_log_attr);
+	failtest_suppress = true;
+	if (!ok1(tdb))
+		goto out;
+	ok1(tap_log_messages == 0);
+	ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS);
+	ok1(tap_log_messages == 0);
+	tdb_close(tdb);
+
+	/* Capability says you can't check. */
+	create_tdb("run-capabilities.tdb",
+		   1, false, false, false,
+		   2, true, false, false, 0);
+
+	failtest_suppress = false;
+	tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0,
+		       &tap_log_attr);
+	failtest_suppress = true;
+	if (!ok1(tdb))
+		goto out;
+	ok1(tap_log_messages == 0);
+	ok1(tdb_get_flags(tdb) & TDB_CANT_CHECK);
+	ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS);
+	/* We expect a warning! */
+	ok1(tap_log_messages == 1);
+	ok1(strstr(log_last, "capabilit"));
+	tdb_close(tdb);
+
+	/* Capability says you can't write. */
+	create_tdb("run-capabilities.tdb",
+		   1, false, false, false,
+		   2, false, true, false, 0);
+
+	failtest_suppress = false;
+	tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0,
+		       &tap_log_attr);
+	failtest_suppress = true;
+	/* We expect a message. */
+	ok1(!tdb);
+	if (!ok1(tap_log_messages == 2))
+		goto out;
+	if (!ok1(strstr(log_last, "unknown")))
+		goto out;
+	ok1(strstr(log_last, "write"));
+
+	/* We can open it read-only though! */
+	failtest_suppress = false;
+	tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDONLY, 0,
+		       &tap_log_attr);
+	failtest_suppress = true;
+	if (!ok1(tdb))
+		goto out;
+	ok1(tap_log_messages == 2);
+	ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS);
+	ok1(tap_log_messages == 2);
+	tdb_close(tdb);
+
+	/* Capability says you can't open. */
+	create_tdb("run-capabilities.tdb",
+		   1, false, false, false,
+		   2, false, false, true, 0);
+
+	failtest_suppress = false;
+	tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0,
+		       &tap_log_attr);
+	failtest_suppress = true;
+	/* We expect a message. */
+	ok1(!tdb);
+	if (!ok1(tap_log_messages == 3))
+		goto out;
+	if (!ok1(strstr(log_last, "unknown")))
+		goto out;
+
+	/* Combine capabilities correctly. */
+	create_tdb("run-capabilities.tdb",
+		   1, false, false, false,
+		   2, true, false, false,
+		   3, false, true, false, 0);
+
+	failtest_suppress = false;
+	tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0,
+		       &tap_log_attr);
+	failtest_suppress = true;
+	/* We expect a message. */
+	ok1(!tdb);
+	if (!ok1(tap_log_messages == 4))
+		goto out;
+	if (!ok1(strstr(log_last, "unknown")))
+		goto out;
+	ok1(strstr(log_last, "write"));
+
+	/* We can open it read-only though! */
+	failtest_suppress = false;
+	tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDONLY, 0,
+		       &tap_log_attr);
+	failtest_suppress = true;
+	if (!ok1(tdb))
+		goto out;
+	ok1(tap_log_messages == 4);
+	ok1(tdb_get_flags(tdb) & TDB_CANT_CHECK);
+	ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS);
+	/* We expect a warning! */
+	ok1(tap_log_messages == 5);
+	ok1(strstr(log_last, "unknown"));
+	tdb_close(tdb);
+
+out:
+	failtest_exit(exit_status());
+}
diff --git a/tools/ccanlint/ccanlint.c b/tools/ccanlint/ccanlint.c
index 4092d104..63724d2e 100644
--- a/tools/ccanlint/ccanlint.c
+++ b/tools/ccanlint/ccanlint.c
@@ -331,6 +331,7 @@ static char *keep_test(const char *testname, void *unused)
 {
 	struct ccanlint *i;
 
+	init_tests();
 	if (streq(testname, "all")) {
 		struct list_head *list;
 		foreach_ptr(list, &compulsory_tests, &normal_tests) {
-- 
2.30.9