From 400d74f6a676b77a9153dd4636c75fdece823790 Mon Sep 17 00:00:00 2001 From: Fabian Keil Date: Thu, 4 Nov 2021 16:27:31 +0100 Subject: [PATCH 1/3] sbin/geom/misc: Add g_metadata_search() ... which is needed to implement a "search" subcommand for geli. Obtained from: ElectroBSD --- sbin/geom/misc/subr.c | 75 +++++++++++++++++++++++++++++++++++++++++++ sbin/geom/misc/subr.h | 3 ++ 2 files changed, 78 insertions(+) diff --git a/sbin/geom/misc/subr.c b/sbin/geom/misc/subr.c index 3985ae56edc6..5141ce256c67 100644 --- a/sbin/geom/misc/subr.c +++ b/sbin/geom/misc/subr.c @@ -273,6 +273,81 @@ g_metadata_read(const char *name, unsigned char *md, size_t size, return (error); } +int +g_metadata_search(const char *name, unsigned char *md, size_t size, + const char *magic, off_t *metadata_offset) +{ + struct std_metadata stdmd; + unsigned char *sector; + ssize_t sectorsize; + off_t mediasize; + int error, fd; + off_t offset; + + *metadata_offset = 0; + sector = NULL; + error = 0; + + fd = g_open(name, 0); + if (fd == -1) + return (errno); + mediasize = g_mediasize(fd); + if (mediasize == -1) { + error = errno; + goto out; + } + sectorsize = g_sectorsize(fd); + if (sectorsize == -1) { + error = errno; + goto out; + } + assert(sectorsize >= (ssize_t)size); + sector = malloc(sectorsize); + if (sector == NULL) { + error = ENOMEM; + goto out; + } + + fprintf(stderr, "Searching for %s metadata on %s.\n", magic, name); + + offset = mediasize - sectorsize; + while (offset > 0) { + if (offset % 1073741824 == 0) { + /* Should be reworded */ + fprintf(stderr, + "Next search offset is %ju.\n", (uintmax_t)offset); + } + if (pread(fd, sector, sectorsize, offset) != + sectorsize) { + error = errno; + fprintf(stderr, "Failed to read at offset %ju.\n", + (uintmax_t)offset); + goto out; + } + if (magic != NULL) { + std_metadata_decode(sector, &stdmd); + if (strcmp(stdmd.md_magic, magic) != 0) { + offset -= sectorsize; + continue; + } else { + fprintf(stderr, + "Found %s meta data at offset %ju.\n", + magic, (uintmax_t)offset); + *metadata_offset = offset; + break; + } + } + offset -= sectorsize; + } + + bcopy(sector, md, size); +out: + if (sector != NULL) + free(sector); + g_close(fd); + return (error); +} + /* * Actually write the GEOM label to the provider * diff --git a/sbin/geom/misc/subr.h b/sbin/geom/misc/subr.h index 579c94c21491..9a05d47f2c6f 100644 --- a/sbin/geom/misc/subr.h +++ b/sbin/geom/misc/subr.h @@ -41,6 +41,9 @@ unsigned int g_get_sectorsize(const char *name); int g_metadata_read(const char *name, unsigned char *md, size_t size, const char *magic); +int +g_metadata_search(const char *name, unsigned char *md, size_t size, + const char *magic, off_t *metadata_offset); int g_metadata_store(const char *name, const unsigned char *md, size_t size); int g_metadata_clear(const char *name, const char *magic); -- 2.32.0 From d8b8ca1dd73f28fa21a3c23fc6cbf0ecfaea63e5 Mon Sep 17 00:00:00 2001 From: Fabian Keil Date: Thu, 4 Nov 2021 16:35:00 +0100 Subject: [PATCH 2/3] geli: Implement a "search" subcommand ... which can be used to make a provider accessible again if, for example, the partition data is lost but the geli metadata on disk is still valid. Example usage: [fk@steffen ~]$ sudo geli search /dev/ada1 Searching for GEOM::ELI metadata on /dev/ada1. Found GEOM::ELI meta data at offset 4000785927680. Metadata found on /dev/ada1: magic: GEOM::ELI version: 7 flags: 0x0 ealgo: AES-XTS keylen: 128 provsize: 3985542778880 sectorsize: 512 keys: 0x01 iterations: 447024 Salt: 23[...]05 Master Key: 20[...]29 MD5 hash: 9fdffeb97ca6b34379512a9191cbfaeb Try making the data attachable with: gnop create -o 15243149312 -s 3985542778880 /dev/ada1 [fk@steffen ~]$ sudo gnop create -o 15243149312 -s 3985542778880 /dev/ada1 [fk@steffen ~]$ sudo geli attach /dev/ada1.nop Enter passphrase: [fk@steffen ~]$ sudo zpool import dpool [fk@steffen ~]$ sudo zpool status -v dpool pool: dpool state: ONLINE status: One or more devices has experienced an error resulting in data corruption. Applications may be affected. action: Restore the file in question if possible. Otherwise restore the entire pool from backup. see: https://illumos.org/msg/ZFS-8000-8A scan: scrub repaired 0 in 1 days 09:00:47 with 66 errors on 2021-12-07 18:40:55 config: NAME STATE READ WRITE CKSUM dpool ONLINE 0 0 0 ada1.nop.eli ONLINE 0 0 0 errors: Permanent errors have been detected in the following files: dpool/ggated/cloudia2:<0x1> dpool/ggated/cloudia2@2017-04-20_21:27:<0x1> Obtained from: ElectroBSD --- lib/geom/eli/geom_eli.c | 103 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/lib/geom/eli/geom_eli.c b/lib/geom/eli/geom_eli.c index 48ab08a6dd8c..3346d1ea4a5e 100644 --- a/lib/geom/eli/geom_eli.c +++ b/lib/geom/eli/geom_eli.c @@ -84,6 +84,7 @@ static void eli_resize(struct gctl_req *req); static void eli_version(struct gctl_req *req); static void eli_clear(struct gctl_req *req); static void eli_dump(struct gctl_req *req); +static void eli_search(struct gctl_req *req); static int eli_backup_create(struct gctl_req *req, const char *prov, const char *file); @@ -109,6 +110,7 @@ static int eli_backup_create(struct gctl_req *req, const char *prov, * version [prov ...] * clear [-v] prov ... * dump [-v] prov ... + * search [-v] prov ... */ struct g_command class_commands[] = { { "init", G_FLAG_VERBOSE, eli_main, @@ -277,6 +279,9 @@ struct g_command class_commands[] = { { "dump", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, "[-v] prov ..." }, + { "search", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, + "[-v] prov ..." + }, G_CMD_SENTINEL }; @@ -339,6 +344,8 @@ eli_main(struct gctl_req *req, unsigned int flags) eli_restore(req); else if (strcmp(name, "resize") == 0) eli_resize(req); + else if (strcmp(name, "search") == 0) + eli_search(req); else if (strcmp(name, "version") == 0) eli_version(req); else if (strcmp(name, "dump") == 0) @@ -650,6 +657,62 @@ eli_metadata_read(struct gctl_req *req, const char *prov, return (0); } +static int +eli_metadata_search(struct gctl_req *req, const char *prov, + struct g_eli_metadata *md, off_t *metadata_offset) +{ + unsigned char sector[sizeof(struct g_eli_metadata)]; + int error; + + if (g_get_sectorsize(prov) == 0) { + int fd; + + /* This is a file probably. */ + fd = open(prov, O_RDONLY); + if (fd == -1) { + gctl_error(req, "Cannot open %s: %s.", prov, + strerror(errno)); + return (-1); + } + if (read(fd, sector, sizeof(sector)) != sizeof(sector)) { + gctl_error(req, "Cannot read metadata from %s: %s.", + prov, strerror(errno)); + close(fd); + return (-1); + } + close(fd); + } else { + /* This is a GEOM provider. */ + error = g_metadata_search(prov, sector, sizeof(sector), + G_ELI_MAGIC, metadata_offset); + if (error != 0) { + gctl_error(req, "Cannot read metadata from %s: %s.", + prov, strerror(error)); + return (-1); + } + } + error = eli_metadata_decode(sector, md); + switch (error) { + case 0: + break; + case EOPNOTSUPP: + gctl_error(req, + "Provider's %s metadata version %u is too new.\n" + "geli: The highest supported version is %u.", + prov, (unsigned int)md->md_version, G_ELI_VERSION); + return (-1); + case EINVAL: + gctl_error(req, "Inconsistent provider's %s metadata.", prov); + return (-1); + default: + gctl_error(req, + "Unexpected error while decoding provider's %s metadata: %s.", + prov, strerror(error)); + return (-1); + } + return (0); +} + static int eli_metadata_store(struct gctl_req *req, const char *prov, struct g_eli_metadata *md) @@ -1994,3 +2057,43 @@ eli_dump(struct gctl_req *req) printf("\n"); } } + +static void +eli_suggest_gnop_command(const char *name, struct g_eli_metadata *md, + off_t metadata_offset) +{ + long long unsigned gnop_size = md->md_provsize; + long long unsigned gnop_offset = metadata_offset - md->md_provsize + + md->md_sectorsize; + + printf("\nTry making the data attachable with: " + "gnop create -o %llu -s %llu %s", + gnop_offset, gnop_size, name); +} + +static void +eli_search(struct gctl_req *req) +{ + struct g_eli_metadata md; + const char *name; + off_t metadata_offset; + int i, nargs; + + nargs = gctl_get_int(req, "nargs"); + if (nargs < 1) { + gctl_error(req, "Too few arguments."); + return; + } + + for (i = 0; i < nargs; i++) { + name = gctl_get_ascii(req, "arg%d", i); + if (eli_metadata_search(NULL, name, &md, &metadata_offset) == -1) { + gctl_error(req, "Not fully done."); + continue; + } + printf("Metadata found on %s:\n", name); + eli_metadata_dump(&md); + eli_suggest_gnop_command(name, &md, metadata_offset); + printf("\n"); + } +} -- 2.32.0 From 55bc8edfe8d23cbc5e3176d7077e7c84530f91a3 Mon Sep 17 00:00:00 2001 From: Fabian Keil Date: Fri, 12 Nov 2021 03:56:15 +0100 Subject: [PATCH 3/3] geli(8): Document the "search" subcommand Obtained from: ElectroBSD --- lib/geom/eli/geli.8 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/geom/eli/geli.8 b/lib/geom/eli/geli.8 index 23bad1999806..dace8bc68254 100644 --- a/lib/geom/eli/geli.8 +++ b/lib/geom/eli/geli.8 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd October 15, 2020 +.Dd November 12, 2021 .Dt GELI 8 .Os .Sh NAME @@ -91,6 +91,9 @@ utility: .Op Fl bBdDgGtT .Ar prov ... .Nm +.Cm search +.Ar prov +.Nm .Cm setkey .Op Fl pPv .Op Fl i Ar iterations @@ -547,6 +550,13 @@ subcommand. .It Fl T Disable TRIM/UNMAP passthru. .El +.It Cm search +Search for metadata on the provider, starting at the end and +going backwards until valid metadata is found or the beginning +of the provider is reached. +This subcommand may be useful if, for example, the GPT partition +data got corrupted or deleted while the data on the previously +accessible partitions is still expected to be valid. .It Cm setkey Install a copy of the Master Key into the selected slot, encrypted with a new User Key. -- 2.32.0