git: a849842f510a - main - loader: Add support for booting from a ZFS snapshot

From: Allan Jude <allanjude_at_FreeBSD.org>
Date: Tue, 14 Mar 2023 14:19:37 UTC
The branch main has been updated by allanjude:

URL: https://cgit.FreeBSD.org/src/commit/?id=a849842f510af48717e35ff709623e0dd1b80b20

commit a849842f510af48717e35ff709623e0dd1b80b20
Author:     Allan Jude <allanjude@FreeBSD.org>
AuthorDate: 2022-11-26 18:11:13 +0000
Commit:     Allan Jude <allanjude@FreeBSD.org>
CommitDate: 2023-03-14 14:18:29 +0000

    loader: Add support for booting from a ZFS snapshot
    
    When booting from a snapshot we need to follow a different code path
    to turn the objset ID into the name, and for forward lookups we need
    to walk the parent's snapnames_zap.
    
    With this, it is possible to set the pools BOOTFS property to a
    snapshot and boot with a read-only filesystem of that snapshot.
    
    Reviewed by:    tsoome, rew, imp
    Sponsored By:   Beckhoff Automation GmbH & Co. KG
    Sponsored By:   Klara, Inc.
    Differential Revision:  https://reviews.freebsd.org/D38600
---
 stand/libsa/zfs/zfsimpl.c | 56 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 54 insertions(+), 2 deletions(-)

diff --git a/stand/libsa/zfs/zfsimpl.c b/stand/libsa/zfs/zfsimpl.c
index 36c90613e827..76063e76225f 100644
--- a/stand/libsa/zfs/zfsimpl.c
+++ b/stand/libsa/zfs/zfsimpl.c
@@ -3068,11 +3068,12 @@ zfs_rlookup(const spa_t *spa, uint64_t objnum, char *result)
 	char name[256];
 	char component[256];
 	uint64_t dir_obj, parent_obj, child_dir_zapobj;
-	dnode_phys_t child_dir_zap, dataset, dir, parent;
+	dnode_phys_t child_dir_zap, snapnames_zap, dataset, dir, parent;
 	dsl_dir_phys_t *dd;
 	dsl_dataset_phys_t *ds;
 	char *p;
 	int len;
+	boolean_t issnap = B_FALSE;
 
 	p = &name[sizeof(name) - 1];
 	*p = '\0';
@@ -3083,6 +3084,8 @@ zfs_rlookup(const spa_t *spa, uint64_t objnum, char *result)
 	}
 	ds = (dsl_dataset_phys_t *)&dataset.dn_bonus;
 	dir_obj = ds->ds_dir_obj;
+	if (ds->ds_snapnames_zapobj == 0)
+		issnap = B_TRUE;
 
 	for (;;) {
 		if (objset_get_dnode(spa, spa->spa_mos, dir_obj, &dir) != 0)
@@ -3098,6 +3101,34 @@ zfs_rlookup(const spa_t *spa, uint64_t objnum, char *result)
 		    &parent) != 0)
 			return (EIO);
 		dd = (dsl_dir_phys_t *)&parent.dn_bonus;
+		if (issnap == B_TRUE) {
+			/*
+			 * The dataset we are looking up is a snapshot
+			 * the dir_obj is the parent already, we don't want
+			 * the grandparent just yet. Reset to the parent.
+			 */
+			dd = (dsl_dir_phys_t *)&dir.dn_bonus;
+			/* Lookup the dataset to get the snapname ZAP */
+			if (objset_get_dnode(spa, spa->spa_mos,
+			    dd->dd_head_dataset_obj, &dataset))
+				return (EIO);
+			ds = (dsl_dataset_phys_t *)&dataset.dn_bonus;
+			if (objset_get_dnode(spa, spa->spa_mos,
+			    ds->ds_snapnames_zapobj, &snapnames_zap) != 0)
+				return (EIO);
+			/* Get the name of the snapshot */
+			if (zap_rlookup(spa, &snapnames_zap, component,
+			    objnum) != 0)
+				return (EIO);
+			len = strlen(component);
+			p -= len;
+			memcpy(p, component, len);
+			--p;
+			*p = '@';
+			issnap = B_FALSE;
+			continue;
+		}
+
 		child_dir_zapobj = dd->dd_child_dir_zapobj;
 		if (objset_get_dnode(spa, spa->spa_mos, child_dir_zapobj,
 		    &child_dir_zap) != 0)
@@ -3127,9 +3158,11 @@ zfs_lookup_dataset(const spa_t *spa, const char *name, uint64_t *objnum)
 {
 	char element[256];
 	uint64_t dir_obj, child_dir_zapobj;
-	dnode_phys_t child_dir_zap, dir;
+	dnode_phys_t child_dir_zap, snapnames_zap, dir, dataset;
 	dsl_dir_phys_t *dd;
+	dsl_dataset_phys_t *ds;
 	const char *p, *q;
+	boolean_t issnap = B_FALSE;
 
 	if (objset_get_dnode(spa, spa->spa_mos,
 	    DMU_POOL_DIRECTORY_OBJECT, &dir))
@@ -3160,6 +3193,25 @@ zfs_lookup_dataset(const spa_t *spa, const char *name, uint64_t *objnum)
 			p += strlen(p);
 		}
 
+		if (issnap == B_TRUE) {
+		        if (objset_get_dnode(spa, spa->spa_mos,
+			    dd->dd_head_dataset_obj, &dataset))
+		                return (EIO);
+			ds = (dsl_dataset_phys_t *)&dataset.dn_bonus;
+			if (objset_get_dnode(spa, spa->spa_mos,
+			    ds->ds_snapnames_zapobj, &snapnames_zap) != 0)
+				return (EIO);
+			/* Actual loop condition #2. */
+			if (zap_lookup(spa, &snapnames_zap, element,
+			    sizeof (dir_obj), 1, &dir_obj) != 0)
+				return (ENOENT);
+			*objnum = dir_obj;
+			return (0);
+		} else if ((q = strchr(element, '@')) != NULL) {
+			issnap = B_TRUE;
+			element[q - element] = '\0';
+			p = q + 1;
+		}
 		child_dir_zapobj = dd->dd_child_dir_zapobj;
 		if (objset_get_dnode(spa, spa->spa_mos, child_dir_zapobj,
 		    &child_dir_zap) != 0)