svn commit: r212934 - in head: sbin/geom/class/eli tools/regression/geom_eli

Brian Somers brian at FreeBSD.org
Mon Sep 20 22:05:00 UTC 2010


Author: brian
Date: Mon Sep 20 22:04:59 2010
New Revision: 212934
URL: http://svn.freebsd.org/changeset/base/212934

Log:
  Add a geli resize subcommand to resize encrypted filesystems prior
  to growing the filesystem.
  
  Refuse to attach providers where the metadata provider size is
  wrong.  This makes post-boot attaches behave consistently with
  pre-boot attaches.  Also refuse to restore metadata to a provider
  of the wrong size without the new -f switch.  The new -f switch
  forces the metadata restoration despite the provider size, and
  updates the provider size in the restored metadata to the correct
  value.
  
  Helped by:	pjd
  Reviewed by:	pjd

Added:
  head/tools/regression/geom_eli/resize.t   (contents, props changed)
Modified:
  head/sbin/geom/class/eli/geli.8
  head/sbin/geom/class/eli/geom_eli.c

Modified: head/sbin/geom/class/eli/geli.8
==============================================================================
--- head/sbin/geom/class/eli/geli.8	Mon Sep 20 21:38:52 2010	(r212933)
+++ head/sbin/geom/class/eli/geli.8	Mon Sep 20 22:04:59 2010	(r212934)
@@ -24,7 +24,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd August 29, 2008
+.Dd September 20, 2010
 .Dt GELI 8
 .Os
 .Sh NAME
@@ -111,10 +111,15 @@ utility:
 .Ar file
 .Nm
 .Cm restore
-.Op Fl v
+.Op Fl fv
 .Ar file
 .Ar prov
 .Nm
+.Cm resize
+.Op Fl v
+.Fl s Ar oldsize
+.Ar prov
+.Nm
 .Cm clear
 .Op Fl v
 .Ar prov ...
@@ -464,6 +469,34 @@ If specified, all currently attached pro
 Backup metadata from the given provider to the given file.
 .It Cm restore
 Restore metadata from the given file to the given provider.
+.Bl -tag -width ".Fl f"
+.It Fl f
+Metadata contains the size of the provider to ensure that the correct
+partition or slice is attached.
+If an attempt is made to restore metadata to a provider that has a different
+size,
+.Nm
+will refuse to restore the data unless the
+.Fl f
+switch is used.
+If the partition or slice has been grown, the
+.Cm resize
+subcommand should be used rather than attempting to relocate the metadata
+through
+.Cm backup
+and
+.Cm restore .
+.El
+.It Cm resize
+Inform
+.Nm
+that the provider has been resized.
+The old metadata block is relocated to the correct position at the end of the
+provider and the provider size is updated.
+.Bl -tag -width ".Fl s Ar oldsize"
+.It Fl s Ar oldsize
+The size of the provider before it was resized.
+.El
 .It Cm clear
 Clear metadata from the given providers.
 .It Cm dump
@@ -665,6 +698,17 @@ geli: Cannot read metadata from /dev/da0
 # geli attach /dev/da0
 Enter passphrase:
 .Ed
+.Pp
+If an encrypted filesystem is extended, it is necessary to relocate and
+update the metadata:
+.Bd -literal -offset indent
+# gpart create -s GPT ada0
+# gpart add -s 1g -t freebsd-ufs -i 1 ada0
+# geli init -K keyfile -P ada0p1
+# gpart resize -s 2g -i 1 ada0
+# geli resize -s 1g ada0p1
+# geli attach -k keyfile -p ada0p1
+.Ed
 .Sh DATA AUTHENTICATION
 .Nm
 can verify data integrity when an authentication algorithm is specified.

Modified: head/sbin/geom/class/eli/geom_eli.c
==============================================================================
--- head/sbin/geom/class/eli/geom_eli.c	Mon Sep 20 21:38:52 2010	(r212933)
+++ head/sbin/geom/class/eli/geom_eli.c	Mon Sep 20 22:04:59 2010	(r212934)
@@ -66,6 +66,7 @@ static void eli_delkey(struct gctl_req *
 static void eli_kill(struct gctl_req *req);
 static void eli_backup(struct gctl_req *req);
 static void eli_restore(struct gctl_req *req);
+static void eli_resize(struct gctl_req *req);
 static void eli_clear(struct gctl_req *req);
 static void eli_dump(struct gctl_req *req);
 
@@ -86,7 +87,8 @@ static int eli_backup_create(struct gctl
  * delkey [-afv] [-n keyno] prov
  * kill [-av] [prov ...]
  * backup [-v] prov file
- * restore [-v] file prov
+ * restore [-fv] file prov
+ * resize [-v] -s oldsize prov
  * clear [-v] prov ...
  * dump [-v] prov ...
  */
@@ -197,8 +199,19 @@ struct g_command class_commands[] = {
 	{ "backup", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
 	    "[-v] prov file"
 	},
-	{ "restore", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
-	    "[-v] file prov"
+	{ "restore", G_FLAG_VERBOSE, eli_main,
+	    {
+		{ 'f', "force", NULL, G_TYPE_BOOL },
+		G_OPT_SENTINEL
+	    },
+	    "[-fv] file prov"
+	},
+	{ "resize", G_FLAG_VERBOSE, eli_main,
+	    {
+		{ 's', "oldsize", NULL, G_TYPE_NUMBER },
+		G_OPT_SENTINEL
+	    },
+	    "[-v] -s oldsize prov"
 	},
 	{ "clear", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
 	    "[-v] prov ..."
@@ -264,6 +277,8 @@ eli_main(struct gctl_req *req, unsigned 
 		eli_backup(req);
 	else if (strcmp(name, "restore") == 0)
 		eli_restore(req);
+	else if (strcmp(name, "resize") == 0)
+		eli_resize(req);
 	else if (strcmp(name, "dump") == 0)
 		eli_dump(req);
 	else if (strcmp(name, "clear") == 0)
@@ -683,6 +698,7 @@ eli_attach(struct gctl_req *req)
 	struct g_eli_metadata md;
 	unsigned char key[G_ELI_USERKEYLEN];
 	const char *prov;
+	off_t mediasize;
 	int nargs;
 
 	nargs = gctl_get_int(req, "nargs");
@@ -695,6 +711,12 @@ eli_attach(struct gctl_req *req)
 	if (eli_metadata_read(req, prov, &md) == -1)
 		return;
 
+	mediasize = g_get_mediasize(prov);
+	if (md.md_provsize != (uint64_t)mediasize) {
+		gctl_error(req, "Provider size mismatch.");
+		return;
+	}
+
 	if (eli_genkey(req, &md, key, 0) == NULL) {
 		bzero(key, sizeof(key));
 		return;
@@ -1212,6 +1234,17 @@ eli_restore(struct gctl_req *req)
 		gctl_error(req, "MD5 hash mismatch: not a geli backup file?");
 		goto out;
 	}
+	/* Check if the provider size has changed since we did the backup. */
+	if (md.md_provsize != (uint64_t)mediasize) {
+		if (gctl_get_int(req, "force")) {
+			md.md_provsize = mediasize;
+			eli_metadata_encode(&md, sector);
+		} else {
+			gctl_error(req, "Provider size mismatch: "
+			    "wrong backup file?");
+			goto out;
+		}
+	}
 	/* Write metadata from the provider. */
 	if (pwrite(provfd, sector, secsize, mediasize - secsize) !=
 	    (ssize_t)secsize) {
@@ -1230,6 +1263,111 @@ out:
 }
 
 static void
+eli_resize(struct gctl_req *req)
+{
+	struct g_eli_metadata md;
+	const char *prov;
+	unsigned char *sector;
+	unsigned secsize;
+	off_t mediasize, oldsize;
+	int nargs, provfd;
+
+	nargs = gctl_get_int(req, "nargs");
+	if (nargs != 1) {
+		gctl_error(req, "Invalid number of arguments.");
+		return;
+	}
+	prov = gctl_get_ascii(req, "arg0");
+
+	provfd = -1;
+	sector = NULL;
+	secsize = 0;
+
+	provfd = open(prov, O_RDWR);
+	if (provfd == -1 && errno == ENOENT && prov[0] != '/') {
+		char devprov[MAXPATHLEN];
+
+		snprintf(devprov, sizeof(devprov), "%s%s", _PATH_DEV, prov);
+		provfd = open(devprov, O_RDWR);
+	}
+	if (provfd == -1) {
+		gctl_error(req, "Cannot open %s: %s.", prov, strerror(errno));
+		goto out;
+	}
+
+	mediasize = g_get_mediasize(prov);
+	secsize = g_get_sectorsize(prov);
+	if (mediasize == 0 || secsize == 0) {
+		gctl_error(req, "Cannot get information about %s: %s.", prov,
+		    strerror(errno));
+		goto out;
+	}
+
+	sector = malloc(secsize);
+	if (sector == NULL) {
+		gctl_error(req, "Cannot allocate memory.");
+		goto out;
+	}
+
+	oldsize = gctl_get_intmax(req, "oldsize");
+	if (oldsize < 0 || oldsize > mediasize) {
+		gctl_error(req, "Invalid oldsize: Out of range.");
+		goto out;
+	}
+
+	/* Read metadata from the 'oldsize' offset. */
+	if (pread(provfd, sector, secsize, oldsize - secsize) !=
+	    (ssize_t)secsize) {
+		gctl_error(req, "Cannot read old metadata: %s.",
+		    strerror(errno));
+		goto out;
+	}
+
+	/* Check if this sector contains geli metadata. */
+	if (eli_metadata_decode(sector, &md) != 0) {
+		gctl_error(req, "MD5 hash mismatch: no metadata for oldsize.");
+		goto out;
+	}
+
+	/*
+	 * If the old metadata doesn't have a correct provider size, refuse
+	 * to resize.
+	 */
+	if (md.md_provsize != (uint64_t)oldsize) {
+		gctl_error(req, "Provider size mismatch at oldsize.");
+		goto out;
+	}
+
+	/*
+	 * Update the old metadata with the current provider size and write
+	 * it back to the correct place on the provider.
+	 */
+	md.md_provsize = mediasize;
+	eli_metadata_encode(&md, sector);
+	if (pwrite(provfd, sector, secsize, mediasize - secsize) !=
+	    (ssize_t)secsize) {
+		gctl_error(req, "Cannot write metadata: %s.", strerror(errno));
+		goto out;
+	}
+
+	/* Now trash the old metadata. */
+	arc4rand(sector, secsize);
+	if (pwrite(provfd, sector, secsize, oldsize - secsize) !=
+	    (ssize_t)secsize) {
+		gctl_error(req, "Failed to clobber old metadata: %s.",
+		    strerror(errno));
+		goto out;
+	}
+out:
+	if (provfd > 0)
+		close(provfd);
+	if (sector != NULL) {
+		bzero(sector, secsize);
+		free(sector);
+	}
+}
+
+static void
 eli_clear(struct gctl_req *req)
 {
 	const char *name;

Added: head/tools/regression/geom_eli/resize.t
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/tools/regression/geom_eli/resize.t	Mon Sep 20 22:04:59 2010	(r212934)
@@ -0,0 +1,149 @@
+#! /bin/sh
+#
+# $FreeBSD$
+
+echo 1..27
+
+BLK=512
+BLKS_PER_MB=2048
+
+md=$(mdconfig -s40m) || exit 1
+unit=${md#md}
+i=1
+
+setsize() {
+    partszMB=$1 unitszMB=$2
+
+    {
+	echo a: $(($partszMB * $BLKS_PER_MB)) 0 4.2BSD 1024 8192
+	echo c: $(($unitszMB * $BLKS_PER_MB)) 0 unused 0 0
+    } | disklabel -R $md /dev/stdin
+}
+
+# Initialise
+
+kldload geom_eli >/dev/null 2>&1
+
+setsize 10 40 || echo -n "not "
+echo ok $i - "Sized ${md}a to 10m"
+i=$((i + 1))
+
+echo secret >tmp.key
+geli init -Bnone -PKtmp.key ${md}a || echo -n "not "
+echo ok $i - "Initialised geli on ${md}a"
+i=$((i + 1))
+geli attach -pk tmp.key ${md}a || echo -n "not "
+echo ok $i - "Attached ${md}a as ${md}a.eli"
+i=$((i + 1))
+
+newfs -U ${md}a.eli >/dev/null || echo -n "not "
+echo ok $i - "Initialised the filesystem on ${md}a.eli"
+i=$((i + 1))
+out=$(fsck -tufs -y ${md}a.eli)
+echo "$out" | fgrep -q MODIFIED && echo -n "not "
+echo ok $i - "fsck says ${md}a.eli is clean," $(echo $(echo "$out" | wc -l)) \
+    "lines of output"
+i=$((i + 1))
+
+
+# Doing a backup, resize & restore must be forced (with -f) as geli
+# verifies that the provider size in the metadata matches the consumer.
+
+geli backup ${md}a tmp.meta || echo -n "not "
+echo ok $i - "Backed up ${md}a metadata"
+i=$((i + 1))
+
+geli detach ${md}a.eli || echo -n "not "
+echo ok $i - "Detached ${md}a.eli"
+i=$((i + 1))
+
+setsize 20 40 || echo -n "not "
+echo ok $i - "Sized ${md}a to 20m"
+i=$((i + 1))
+geli attach -pktmp.key ${md}a && echo -n "not "
+echo ok $i - "Attaching ${md}a fails after resizing the consumer"
+i=$((i + 1))
+
+geli restore tmp.meta ${md}a && echo -n "not "
+echo ok $i - "Restoring metadata on ${md}a.eli fails without -f"
+i=$((i + 1))
+geli restore -f tmp.meta ${md}a || echo -n "not "
+echo ok $i - "Restoring metadata on ${md}a.eli can be forced"
+i=$((i + 1))
+
+geli attach -pktmp.key ${md}a || echo -n "not "
+echo ok $i - "Attaching ${md}a is now possible"
+i=$((i + 1))
+
+growfs -y ${md}a.eli >/dev/null || echo -n "not "
+echo ok $i - "Extended the filesystem on ${md}a.eli"
+i=$((i + 1))
+
+out=$(fsck -tufs -y ${md}a.eli)
+echo "$out" | fgrep -q MODIFIED && echo -n "not "
+echo ok $i - "fsck says ${md}a.eli is clean," $(echo $(echo "$out" | wc -l)) \
+    "lines of output"
+i=$((i + 1))
+
+
+# Now do the resize properly
+
+geli detach ${md}a.eli || echo -n "not "
+echo ok $i - "Detached ${md}a.eli"
+i=$((i + 1))
+
+setsize 30 40 || echo -n "not "
+echo ok $i - "Sized ${md}a to 30m"
+i=$((i + 1))
+
+geli resize -s20m ${md}a || echo -n "not "
+echo ok $i - "Resizing works ok"
+i=$((i + 1))
+geli resize -s20m ${md}a && echo -n "not "
+echo ok $i - "Resizing doesn't work a 2nd time (no old metadata)"
+i=$((i + 1))
+
+geli attach -pktmp.key ${md}a || echo -n "not "
+echo ok $i - "Attaching ${md}a works ok"
+i=$((i + 1))
+
+growfs -y ${md}a.eli >/dev/null || echo -n "not "
+echo ok $i - "Extended the filesystem on ${md}a.eli"
+i=$((i + 1))
+
+out=$(fsck -tufs -y ${md}a.eli)
+echo "$out" | fgrep -q MODIFIED && echo -n "not "
+echo ok $i - "fsck says ${md}a.eli is clean," $(echo $(echo "$out" | wc -l)) \
+    "lines of output"
+i=$((i + 1))
+
+geli detach ${md}a.eli
+
+
+# Verify that the man page example works, changing ada0 to $md,
+# 1g to 20m, 2g to 30m and keyfile to tmp.key, and adding -B none
+# to geli init.
+
+gpart create -s GPT md0 || echo -n "not "
+echo ok $i - "Installed an MBR on md0"
+i=$((i + 1))
+gpart add -s 20m -t freebsd-ufs -i 1 $md || echo -n "not "
+echo ok $i - "Added a 20m partition in slot 1"
+i=$((i + 1))
+geli init -B none -K tmp.key -P ${md}p1 || echo -n "not "
+echo ok $i - "Initialised geli on ${md}p1"
+i=$((i + 1))
+gpart resize -s 30m -i 1 $md || echo -n "not "
+echo ok $i - "Resized partition ${md}p1 to 30m"
+i=$((i + 1))
+geli resize -s 20m ${md}p1 || echo -n "not "
+echo ok $i - "Resized geli on ${md}p1 to 30m"
+i=$((i + 1))
+geli attach -k tmp.key -p ${md}p1 || echo -n "not "
+echo ok $i - "Attached ${md}p1.eli"
+i=$((i + 1))
+
+geli detach ${md}p1.eli
+mdconfig -du$unit
+
+rm tmp.*


More information about the svn-src-head mailing list