zfs label bootenv should store data as nvlist

nvlist does allow us to support different data types and systems.

To encapsulate user data to/from nvlist, the libzfsbootenv library is
provided.

Reviewed-by: Arvind Sankar <nivedita@alum.mit.edu>
Reviewed-by: Allan Jude <allan@klarasystems.com>
Reviewed-by: Paul Dagnelie <pcd@delphix.com>
Reviewed-by: Igor Kozhukhov <igor@dilos.org>
Signed-off-by: Toomas Soome <tsoome@me.com>
Closes #10774
This commit is contained in:
Toomas Soome
2020-09-16 01:42:27 +03:00
committed by GitHub
parent 37325e4749
commit 1db9e6e4e4
29 changed files with 886 additions and 68 deletions
+91 -25
View File
@@ -149,6 +149,8 @@
#include <sys/dsl_scan.h>
#include <sys/abd.h>
#include <sys/fs/zfs.h>
#include <sys/byteorder.h>
#include <sys/zfs_bootenv.h>
/*
* Basic routines to read and write from a vdev label.
@@ -1233,13 +1235,9 @@ vdev_label_read_bootenv_impl(zio_t *zio, vdev_t *vd, int flags)
* bootloader should have rewritten them all to be the same on boot,
* and any changes we made since boot have been the same across all
* labels.
*
* While grub supports writing to all four labels, other bootloaders
* don't, so we only use the first two labels to store boot
* information.
*/
if (vd->vdev_ops->vdev_op_leaf && vdev_readable(vd)) {
for (int l = 0; l < VDEV_LABELS / 2; l++) {
for (int l = 0; l < VDEV_LABELS; l++) {
vdev_label_read(zio, vd, l,
abd_alloc_linear(VDEV_PAD_SIZE, B_FALSE),
offsetof(vdev_label_t, vl_be), VDEV_PAD_SIZE,
@@ -1249,14 +1247,15 @@ vdev_label_read_bootenv_impl(zio_t *zio, vdev_t *vd, int flags)
}
int
vdev_label_read_bootenv(vdev_t *rvd, nvlist_t *command)
vdev_label_read_bootenv(vdev_t *rvd, nvlist_t *bootenv)
{
nvlist_t *config;
spa_t *spa = rvd->vdev_spa;
abd_t *abd = NULL;
int flags = ZIO_FLAG_CONFIG_WRITER | ZIO_FLAG_CANFAIL |
ZIO_FLAG_SPECULATIVE | ZIO_FLAG_TRYHARD;
ASSERT(command);
ASSERT(bootenv);
ASSERT(spa_config_held(spa, SCL_ALL, RW_WRITER) == SCL_ALL);
zio_t *zio = zio_root(spa, NULL, &abd, flags);
@@ -1264,39 +1263,81 @@ vdev_label_read_bootenv(vdev_t *rvd, nvlist_t *command)
int err = zio_wait(zio);
if (abd != NULL) {
char *buf;
vdev_boot_envblock_t *vbe = abd_to_buf(abd);
if (vbe->vbe_version != VB_RAW) {
abd_free(abd);
return (SET_ERROR(ENOTSUP));
vbe->vbe_version = ntohll(vbe->vbe_version);
switch (vbe->vbe_version) {
case VB_RAW:
/*
* if we have textual data in vbe_bootenv, create nvlist
* with key "envmap".
*/
fnvlist_add_uint64(bootenv, BOOTENV_VERSION, VB_RAW);
vbe->vbe_bootenv[sizeof (vbe->vbe_bootenv) - 1] = '\0';
fnvlist_add_string(bootenv, GRUB_ENVMAP,
vbe->vbe_bootenv);
break;
case VB_NVLIST:
err = nvlist_unpack(vbe->vbe_bootenv,
sizeof (vbe->vbe_bootenv), &config, 0);
if (err == 0) {
fnvlist_merge(bootenv, config);
nvlist_free(config);
break;
}
/* FALLTHROUGH */
default:
/* Check for FreeBSD zfs bootonce command string */
buf = abd_to_buf(abd);
if (*buf == '\0') {
fnvlist_add_uint64(bootenv, BOOTENV_VERSION,
VB_NVLIST);
break;
}
fnvlist_add_string(bootenv, FREEBSD_BOOTONCE, buf);
}
vbe->vbe_bootenv[sizeof (vbe->vbe_bootenv) - 1] = '\0';
fnvlist_add_string(command, "envmap", vbe->vbe_bootenv);
/* abd was allocated in vdev_label_read_bootenv_impl() */
/*
* abd was allocated in vdev_label_read_bootenv_impl()
*/
abd_free(abd);
/* If we managed to read any successfully, return success. */
/*
* If we managed to read any successfully,
* return success.
*/
return (0);
}
return (err);
}
int
vdev_label_write_bootenv(vdev_t *vd, char *envmap)
vdev_label_write_bootenv(vdev_t *vd, nvlist_t *env)
{
zio_t *zio;
spa_t *spa = vd->vdev_spa;
vdev_boot_envblock_t *bootenv;
int flags = ZIO_FLAG_CONFIG_WRITER | ZIO_FLAG_CANFAIL;
int error = ENXIO;
int error;
size_t nvsize;
char *nvbuf;
if (strlen(envmap) >= sizeof (bootenv->vbe_bootenv)) {
error = nvlist_size(env, &nvsize, NV_ENCODE_XDR);
if (error != 0)
return (SET_ERROR(error));
if (nvsize >= sizeof (bootenv->vbe_bootenv)) {
return (SET_ERROR(E2BIG));
}
ASSERT(spa_config_held(spa, SCL_ALL, RW_WRITER) == SCL_ALL);
error = ENXIO;
for (int c = 0; c < vd->vdev_children; c++) {
int child_err = vdev_label_write_bootenv(vd->vdev_child[c],
envmap);
int child_err;
child_err = vdev_label_write_bootenv(vd->vdev_child[c], env);
/*
* As long as any of the disks managed to write all of their
* labels successfully, return success.
@@ -1312,16 +1353,41 @@ vdev_label_write_bootenv(vdev_t *vd, char *envmap)
ASSERT3U(sizeof (*bootenv), ==, VDEV_PAD_SIZE);
abd_t *abd = abd_alloc_for_io(VDEV_PAD_SIZE, B_TRUE);
abd_zero(abd, VDEV_PAD_SIZE);
bootenv = abd_borrow_buf_copy(abd, VDEV_PAD_SIZE);
char *buf = bootenv->vbe_bootenv;
(void) strlcpy(buf, envmap, sizeof (bootenv->vbe_bootenv));
bootenv->vbe_version = VB_RAW;
abd_return_buf_copy(abd, bootenv, VDEV_PAD_SIZE);
bootenv = abd_borrow_buf_copy(abd, VDEV_PAD_SIZE);
nvbuf = bootenv->vbe_bootenv;
nvsize = sizeof (bootenv->vbe_bootenv);
bootenv->vbe_version = fnvlist_lookup_uint64(env, BOOTENV_VERSION);
switch (bootenv->vbe_version) {
case VB_RAW:
if (nvlist_lookup_string(env, GRUB_ENVMAP, &nvbuf) == 0) {
(void) strlcpy(bootenv->vbe_bootenv, nvbuf, nvsize);
}
error = 0;
break;
case VB_NVLIST:
error = nvlist_pack(env, &nvbuf, &nvsize, NV_ENCODE_XDR,
KM_SLEEP);
break;
default:
error = EINVAL;
break;
}
if (error == 0) {
bootenv->vbe_version = htonll(bootenv->vbe_version);
abd_return_buf_copy(abd, bootenv, VDEV_PAD_SIZE);
} else {
abd_free(abd);
return (SET_ERROR(error));
}
retry:
zio = zio_root(spa, NULL, NULL, flags);
for (int l = 0; l < VDEV_LABELS / 2; l++) {
for (int l = 0; l < VDEV_LABELS; l++) {
vdev_label_write(zio, vd, l, abd,
offsetof(vdev_label_t, vl_be),
VDEV_PAD_SIZE, NULL, NULL, flags);