Add support for boot environment data to be stored in the label
Modern bootloaders leverage data stored in the root filesystem to enable some of their powerful features. GRUB specifically has a grubenv file which can store large amounts of configuration data that can be read and written at boot time and during normal operation. This allows sysadmins to configure useful features like automated failover after failed boot attempts. Unfortunately, due to the Copy-on-Write nature of ZFS, the standard behavior of these tools cannot handle writing to ZFS files safely at boot time. We need an alternative way to store data that allows the bootloader to make changes to the data. This work is very similar to work that was done on Illumos to enable similar functionality in the FreeBSD bootloader. This patch is different in that the data being stored is a raw grubenv file; this file can store arbitrary variables and values, and the scripting provided by grub is powerful enough that special structures are not required to implement advanced behavior. We repurpose the second padding area in each label to store the grubenv file, protected by an embedded checksum. We add two ioctls to get and set this data, and libzfs_core and libzfs functions to access them more easily. There are no direct command line interfaces to these functions; these will be added directly to the bootloader utilities. Reviewed-by: Pavel Zakharov <pavel.zakharov@delphix.com> Reviewed-by: Matthew Ahrens <mahrens@delphix.com> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Paul Dagnelie <pcd@delphix.com> Closes #10009
This commit is contained in:
parent
a36bad1759
commit
108a454a46
|
@ -20,7 +20,7 @@
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||||
* Copyright (c) 2012 by Delphix. All rights reserved.
|
* Copyright (c) 2012, 2020 by Delphix. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libzfs.h>
|
#include <libzfs.h>
|
||||||
|
@ -388,7 +388,7 @@ translate_device(const char *pool, const char *device, err_type_t label_type,
|
||||||
record->zi_end = record->zi_start + VDEV_PAD_SIZE - 1;
|
record->zi_end = record->zi_start + VDEV_PAD_SIZE - 1;
|
||||||
break;
|
break;
|
||||||
case TYPE_LABEL_PAD2:
|
case TYPE_LABEL_PAD2:
|
||||||
record->zi_start = offsetof(vdev_label_t, vl_pad2);
|
record->zi_start = offsetof(vdev_label_t, vl_be);
|
||||||
record->zi_end = record->zi_start + VDEV_PAD_SIZE - 1;
|
record->zi_end = record->zi_start + VDEV_PAD_SIZE - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -862,6 +862,8 @@ extern int zpool_in_use(libzfs_handle_t *, int, pool_state_t *, char **,
|
||||||
* Label manipulation.
|
* Label manipulation.
|
||||||
*/
|
*/
|
||||||
extern int zpool_clear_label(int);
|
extern int zpool_clear_label(int);
|
||||||
|
extern int zpool_set_bootenv(zpool_handle_t *, const char *);
|
||||||
|
extern int zpool_get_bootenv(zpool_handle_t *, char *, size_t, off_t);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Management interfaces for SMB ACL files
|
* Management interfaces for SMB ACL files
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2018 by Delphix. All rights reserved.
|
* Copyright (c) 2012, 2020 by Delphix. All rights reserved.
|
||||||
* Copyright (c) 2017 Datto Inc.
|
* Copyright (c) 2017 Datto Inc.
|
||||||
* Copyright 2017 RackTop Systems.
|
* Copyright 2017 RackTop Systems.
|
||||||
* Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
|
* Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
|
||||||
|
@ -135,6 +135,8 @@ int lzc_wait(const char *, zpool_wait_activity_t, boolean_t *);
|
||||||
int lzc_wait_tag(const char *, zpool_wait_activity_t, uint64_t, boolean_t *);
|
int lzc_wait_tag(const char *, zpool_wait_activity_t, uint64_t, boolean_t *);
|
||||||
int lzc_wait_fs(const char *, zfs_wait_activity_t, boolean_t *);
|
int lzc_wait_fs(const char *, zfs_wait_activity_t, boolean_t *);
|
||||||
|
|
||||||
|
int lzc_set_bootenv(const char *, const char *);
|
||||||
|
int lzc_get_bootenv(const char *, nvlist_t **);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||||
* Copyright (c) 2011, 2018 by Delphix. All rights reserved.
|
* Copyright (c) 2011, 2020 by Delphix. All rights reserved.
|
||||||
* Copyright 2011 Nexenta Systems, Inc. All rights reserved.
|
* Copyright 2011 Nexenta Systems, Inc. All rights reserved.
|
||||||
* Copyright (c) 2013, 2017 Joyent, Inc. All rights reserved.
|
* Copyright (c) 2013, 2017 Joyent, Inc. All rights reserved.
|
||||||
* Copyright (c) 2014 Integros [integros.com]
|
* Copyright (c) 2014 Integros [integros.com]
|
||||||
|
@ -1290,7 +1290,7 @@ typedef enum zfs_ioc {
|
||||||
ZFS_IOC_WAIT_FS, /* 0x5a54 */
|
ZFS_IOC_WAIT_FS, /* 0x5a54 */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Per-platform (Optional) - 6/128 numbers reserved.
|
* Per-platform (Optional) - 8/128 numbers reserved.
|
||||||
*/
|
*/
|
||||||
ZFS_IOC_PLATFORM = ZFS_IOC_FIRST + 0x80,
|
ZFS_IOC_PLATFORM = ZFS_IOC_FIRST + 0x80,
|
||||||
ZFS_IOC_EVENTS_NEXT, /* 0x81 (Linux) */
|
ZFS_IOC_EVENTS_NEXT, /* 0x81 (Linux) */
|
||||||
|
@ -1299,6 +1299,8 @@ typedef enum zfs_ioc {
|
||||||
ZFS_IOC_NEXTBOOT, /* 0x84 (FreeBSD) */
|
ZFS_IOC_NEXTBOOT, /* 0x84 (FreeBSD) */
|
||||||
ZFS_IOC_JAIL, /* 0x85 (FreeBSD) */
|
ZFS_IOC_JAIL, /* 0x85 (FreeBSD) */
|
||||||
ZFS_IOC_UNJAIL, /* 0x86 (FreeBSD) */
|
ZFS_IOC_UNJAIL, /* 0x86 (FreeBSD) */
|
||||||
|
ZFS_IOC_SET_BOOTENV, /* 0x87 (Linux) */
|
||||||
|
ZFS_IOC_GET_BOOTENV, /* 0x88 (Linux) */
|
||||||
ZFS_IOC_LAST
|
ZFS_IOC_LAST
|
||||||
} zfs_ioc_t;
|
} zfs_ioc_t;
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||||
* Copyright (c) 2011, 2017 by Delphix. All rights reserved.
|
* Copyright (c) 2011, 2020 by Delphix. All rights reserved.
|
||||||
* Copyright (c) 2017, Intel Corporation.
|
* Copyright (c) 2017, Intel Corporation.
|
||||||
* Copyright (c) 2019, Datto Inc. All rights reserved.
|
* Copyright (c) 2019, Datto Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
@ -179,6 +179,8 @@ extern void vdev_uberblock_load(vdev_t *, struct uberblock *, nvlist_t **);
|
||||||
extern void vdev_config_generate_stats(vdev_t *vd, nvlist_t *nv);
|
extern void vdev_config_generate_stats(vdev_t *vd, nvlist_t *nv);
|
||||||
extern void vdev_label_write(zio_t *zio, vdev_t *vd, int l, abd_t *buf, uint64_t
|
extern void vdev_label_write(zio_t *zio, vdev_t *vd, int l, abd_t *buf, uint64_t
|
||||||
offset, uint64_t size, zio_done_func_t *done, void *private, int flags);
|
offset, uint64_t size, zio_done_func_t *done, void *private, int flags);
|
||||||
|
extern int vdev_label_read_bootenv(vdev_t *, nvlist_t *);
|
||||||
|
extern int vdev_label_write_bootenv(vdev_t *, char *);
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
VDEV_LABEL_CREATE, /* create/add a new device */
|
VDEV_LABEL_CREATE, /* create/add a new device */
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||||
* Copyright (c) 2011, 2019 by Delphix. All rights reserved.
|
* Copyright (c) 2011, 2020 by Delphix. All rights reserved.
|
||||||
* Copyright (c) 2017, Intel Corporation.
|
* Copyright (c) 2017, Intel Corporation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -414,7 +414,7 @@ struct vdev {
|
||||||
#define VDEV_RAIDZ_MAXPARITY 3
|
#define VDEV_RAIDZ_MAXPARITY 3
|
||||||
|
|
||||||
#define VDEV_PAD_SIZE (8 << 10)
|
#define VDEV_PAD_SIZE (8 << 10)
|
||||||
/* 2 padding areas (vl_pad1 and vl_pad2) to skip */
|
/* 2 padding areas (vl_pad1 and vl_be) to skip */
|
||||||
#define VDEV_SKIP_SIZE VDEV_PAD_SIZE * 2
|
#define VDEV_SKIP_SIZE VDEV_PAD_SIZE * 2
|
||||||
#define VDEV_PHYS_SIZE (112 << 10)
|
#define VDEV_PHYS_SIZE (112 << 10)
|
||||||
#define VDEV_UBERBLOCK_RING (128 << 10)
|
#define VDEV_UBERBLOCK_RING (128 << 10)
|
||||||
|
@ -441,12 +441,32 @@ typedef struct vdev_phys {
|
||||||
zio_eck_t vp_zbt;
|
zio_eck_t vp_zbt;
|
||||||
} vdev_phys_t;
|
} vdev_phys_t;
|
||||||
|
|
||||||
|
typedef enum vbe_vers {
|
||||||
|
/* The bootenv file is stored as ascii text in the envblock */
|
||||||
|
VB_RAW = 0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The bootenv file is converted to an nvlist and then packed into the
|
||||||
|
* envblock.
|
||||||
|
*/
|
||||||
|
VB_NVLIST = 1
|
||||||
|
} vbe_vers_t;
|
||||||
|
|
||||||
|
typedef struct vdev_boot_envblock {
|
||||||
|
uint64_t vbe_version;
|
||||||
|
char vbe_bootenv[VDEV_PAD_SIZE - sizeof (uint64_t) -
|
||||||
|
sizeof (zio_eck_t)];
|
||||||
|
zio_eck_t vbe_zbt;
|
||||||
|
} vdev_boot_envblock_t;
|
||||||
|
|
||||||
|
CTASSERT_GLOBAL(sizeof (vdev_boot_envblock_t) == VDEV_PAD_SIZE);
|
||||||
|
|
||||||
typedef struct vdev_label {
|
typedef struct vdev_label {
|
||||||
char vl_pad1[VDEV_PAD_SIZE]; /* 8K */
|
char vl_pad1[VDEV_PAD_SIZE]; /* 8K */
|
||||||
char vl_pad2[VDEV_PAD_SIZE]; /* 8K */
|
vdev_boot_envblock_t vl_be; /* 8K */
|
||||||
vdev_phys_t vl_vdev_phys; /* 112K */
|
vdev_phys_t vl_vdev_phys; /* 112K */
|
||||||
char vl_uberblock[VDEV_UBERBLOCK_RING]; /* 128K */
|
char vl_uberblock[VDEV_UBERBLOCK_RING]; /* 128K */
|
||||||
} vdev_label_t; /* 256K total */
|
} vdev_label_t; /* 256K total */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* vdev_dirty() flags
|
* vdev_dirty() flags
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2015 Nexenta Systems, Inc. All rights reserved.
|
* Copyright 2015 Nexenta Systems, Inc. All rights reserved.
|
||||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||||
* Copyright (c) 2011, 2018 by Delphix. All rights reserved.
|
* Copyright (c) 2011, 2020 by Delphix. All rights reserved.
|
||||||
* Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
|
* Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
|
||||||
* Copyright (c) 2018 Datto Inc.
|
* Copyright (c) 2018 Datto Inc.
|
||||||
* Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
|
* Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
|
||||||
|
@ -429,7 +429,7 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf,
|
||||||
* Assuming bootfs is a valid dataset name.
|
* Assuming bootfs is a valid dataset name.
|
||||||
*/
|
*/
|
||||||
static boolean_t
|
static boolean_t
|
||||||
bootfs_name_valid(const char *pool, char *bootfs)
|
bootfs_name_valid(const char *pool, const char *bootfs)
|
||||||
{
|
{
|
||||||
int len = strlen(pool);
|
int len = strlen(pool);
|
||||||
if (bootfs[0] == '\0')
|
if (bootfs[0] == '\0')
|
||||||
|
@ -4459,3 +4459,39 @@ zpool_wait_status(zpool_handle_t *zhp, zpool_wait_activity_t activity,
|
||||||
|
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
zpool_set_bootenv(zpool_handle_t *zhp, const char *envmap)
|
||||||
|
{
|
||||||
|
int error = lzc_set_bootenv(zhp->zpool_name, envmap);
|
||||||
|
if (error != 0) {
|
||||||
|
(void) zpool_standard_error_fmt(zhp->zpool_hdl, error,
|
||||||
|
dgettext(TEXT_DOMAIN,
|
||||||
|
"error setting bootenv in pool '%s'"), zhp->zpool_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
zpool_get_bootenv(zpool_handle_t *zhp, char *outbuf, size_t size, off_t offset)
|
||||||
|
{
|
||||||
|
nvlist_t *nvl = NULL;
|
||||||
|
int error = lzc_get_bootenv(zhp->zpool_name, &nvl);
|
||||||
|
if (error != 0) {
|
||||||
|
(void) zpool_standard_error_fmt(zhp->zpool_hdl, error,
|
||||||
|
dgettext(TEXT_DOMAIN,
|
||||||
|
"error getting bootenv in pool '%s'"), zhp->zpool_name);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
char *envmap = fnvlist_lookup_string(nvl, "envmap");
|
||||||
|
if (offset >= strlen(envmap)) {
|
||||||
|
fnvlist_free(nvl);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
strlcpy(outbuf, envmap + offset, size);
|
||||||
|
int bytes = MIN(strlen(envmap + offset), size);
|
||||||
|
fnvlist_free(nvl);
|
||||||
|
return (bytes);
|
||||||
|
}
|
||||||
|
|
|
@ -1619,3 +1619,25 @@ lzc_wait_fs(const char *fs, zfs_wait_activity_t activity, boolean_t *waited)
|
||||||
|
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the bootenv contents for the given pool.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
lzc_set_bootenv(const char *pool, const char *env)
|
||||||
|
{
|
||||||
|
nvlist_t *args = fnvlist_alloc();
|
||||||
|
fnvlist_add_string(args, "envmap", env);
|
||||||
|
int error = lzc_ioctl(ZFS_IOC_SET_BOOTENV, pool, args, NULL);
|
||||||
|
fnvlist_free(args);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the contents of the bootenv of the given pool.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
lzc_get_bootenv(const char *pool, nvlist_t **outnvl)
|
||||||
|
{
|
||||||
|
return (lzc_ioctl(ZFS_IOC_GET_BOOTENV, pool, NULL, outnvl));
|
||||||
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ vdev_label_write_pad2(vdev_t *vd, const char *buf, size_t size)
|
||||||
retry:
|
retry:
|
||||||
zio = zio_root(spa, NULL, NULL, flags);
|
zio = zio_root(spa, NULL, NULL, flags);
|
||||||
vdev_label_write(zio, vd, 0, pad2,
|
vdev_label_write(zio, vd, 0, pad2,
|
||||||
offsetof(vdev_label_t, vl_pad2),
|
offsetof(vdev_label_t, vl_be),
|
||||||
VDEV_PAD_SIZE, NULL, NULL, flags);
|
VDEV_PAD_SIZE, NULL, NULL, flags);
|
||||||
error = zio_wait(zio);
|
error = zio_wait(zio);
|
||||||
if (error != 0 && !(flags & ZIO_FLAG_TRYHARD)) {
|
if (error != 0 && !(flags & ZIO_FLAG_TRYHARD)) {
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||||
* Copyright (c) 2011, 2018 by Delphix. All rights reserved.
|
* Copyright (c) 2011, 2020 by Delphix. All rights reserved.
|
||||||
* Copyright 2017 Nexenta Systems, Inc.
|
* Copyright 2017 Nexenta Systems, Inc.
|
||||||
* Copyright (c) 2014 Integros [integros.com]
|
* Copyright (c) 2014 Integros [integros.com]
|
||||||
* Copyright 2016 Toomas Soome <tsoome@me.com>
|
* Copyright 2016 Toomas Soome <tsoome@me.com>
|
||||||
|
@ -1554,7 +1554,7 @@ vdev_probe(vdev_t *vd, zio_t *zio)
|
||||||
for (int l = 1; l < VDEV_LABELS; l++) {
|
for (int l = 1; l < VDEV_LABELS; l++) {
|
||||||
zio_nowait(zio_read_phys(pio, vd,
|
zio_nowait(zio_read_phys(pio, vd,
|
||||||
vdev_label_offset(vd->vdev_psize, l,
|
vdev_label_offset(vd->vdev_psize, l,
|
||||||
offsetof(vdev_label_t, vl_pad2)), VDEV_PAD_SIZE,
|
offsetof(vdev_label_t, vl_be)), VDEV_PAD_SIZE,
|
||||||
abd_alloc_for_io(VDEV_PAD_SIZE, B_TRUE),
|
abd_alloc_for_io(VDEV_PAD_SIZE, B_TRUE),
|
||||||
ZIO_CHECKSUM_OFF, vdev_probe_done, vps,
|
ZIO_CHECKSUM_OFF, vdev_probe_done, vps,
|
||||||
ZIO_PRIORITY_SYNC_READ, vps->vps_flags, B_TRUE));
|
ZIO_PRIORITY_SYNC_READ, vps->vps_flags, B_TRUE));
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||||
* Copyright (c) 2012, 2019 by Delphix. All rights reserved.
|
* Copyright (c) 2012, 2020 by Delphix. All rights reserved.
|
||||||
* Copyright (c) 2017, Intel Corporation.
|
* Copyright (c) 2017, Intel Corporation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -957,7 +957,7 @@ vdev_label_init(vdev_t *vd, uint64_t crtxg, vdev_labeltype_t reason)
|
||||||
nvlist_t *label;
|
nvlist_t *label;
|
||||||
vdev_phys_t *vp;
|
vdev_phys_t *vp;
|
||||||
abd_t *vp_abd;
|
abd_t *vp_abd;
|
||||||
abd_t *pad2;
|
abd_t *bootenv;
|
||||||
uberblock_t *ub;
|
uberblock_t *ub;
|
||||||
abd_t *ub_abd;
|
abd_t *ub_abd;
|
||||||
zio_t *zio;
|
zio_t *zio;
|
||||||
|
@ -1118,8 +1118,8 @@ vdev_label_init(vdev_t *vd, uint64_t crtxg, vdev_labeltype_t reason)
|
||||||
ub->ub_txg = 0;
|
ub->ub_txg = 0;
|
||||||
|
|
||||||
/* Initialize the 2nd padding area. */
|
/* Initialize the 2nd padding area. */
|
||||||
pad2 = abd_alloc_for_io(VDEV_PAD_SIZE, B_TRUE);
|
bootenv = abd_alloc_for_io(VDEV_PAD_SIZE, B_TRUE);
|
||||||
abd_zero(pad2, VDEV_PAD_SIZE);
|
abd_zero(bootenv, VDEV_PAD_SIZE);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write everything in parallel.
|
* Write everything in parallel.
|
||||||
|
@ -1138,8 +1138,8 @@ retry:
|
||||||
* Zero out the 2nd padding area where it might have
|
* Zero out the 2nd padding area where it might have
|
||||||
* left over data from previous filesystem format.
|
* left over data from previous filesystem format.
|
||||||
*/
|
*/
|
||||||
vdev_label_write(zio, vd, l, pad2,
|
vdev_label_write(zio, vd, l, bootenv,
|
||||||
offsetof(vdev_label_t, vl_pad2),
|
offsetof(vdev_label_t, vl_be),
|
||||||
VDEV_PAD_SIZE, NULL, NULL, flags);
|
VDEV_PAD_SIZE, NULL, NULL, flags);
|
||||||
|
|
||||||
vdev_label_write(zio, vd, l, ub_abd,
|
vdev_label_write(zio, vd, l, ub_abd,
|
||||||
|
@ -1155,7 +1155,7 @@ retry:
|
||||||
}
|
}
|
||||||
|
|
||||||
nvlist_free(label);
|
nvlist_free(label);
|
||||||
abd_free(pad2);
|
abd_free(bootenv);
|
||||||
abd_free(ub_abd);
|
abd_free(ub_abd);
|
||||||
abd_free(vp_abd);
|
abd_free(vp_abd);
|
||||||
|
|
||||||
|
@ -1178,6 +1178,148 @@ retry:
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Done callback for vdev_label_read_bootenv_impl. If this is the first
|
||||||
|
* callback to finish, store our abd in the callback pointer. Otherwise, we
|
||||||
|
* just free our abd and return.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
vdev_label_read_bootenv_done(zio_t *zio)
|
||||||
|
{
|
||||||
|
zio_t *rio = zio->io_private;
|
||||||
|
abd_t **cbp = rio->io_private;
|
||||||
|
|
||||||
|
ASSERT3U(zio->io_size, ==, VDEV_PAD_SIZE);
|
||||||
|
|
||||||
|
if (zio->io_error == 0) {
|
||||||
|
mutex_enter(&rio->io_lock);
|
||||||
|
if (*cbp == NULL) {
|
||||||
|
/* Will free this buffer in vdev_label_read_bootenv. */
|
||||||
|
*cbp = zio->io_abd;
|
||||||
|
} else {
|
||||||
|
abd_free(zio->io_abd);
|
||||||
|
}
|
||||||
|
mutex_exit(&rio->io_lock);
|
||||||
|
} else {
|
||||||
|
abd_free(zio->io_abd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vdev_label_read_bootenv_impl(zio_t *zio, vdev_t *vd, int flags)
|
||||||
|
{
|
||||||
|
for (int c = 0; c < vd->vdev_children; c++)
|
||||||
|
vdev_label_read_bootenv_impl(zio, vd->vdev_child[c], flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We just use the first label that has a correct checksum; the
|
||||||
|
* 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++) {
|
||||||
|
vdev_label_read(zio, vd, l,
|
||||||
|
abd_alloc_linear(VDEV_PAD_SIZE, B_FALSE),
|
||||||
|
offsetof(vdev_label_t, vl_be), VDEV_PAD_SIZE,
|
||||||
|
vdev_label_read_bootenv_done, zio, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vdev_label_read_bootenv(vdev_t *rvd, nvlist_t *command)
|
||||||
|
{
|
||||||
|
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(spa_config_held(spa, SCL_ALL, RW_WRITER) == SCL_ALL);
|
||||||
|
|
||||||
|
zio_t *zio = zio_root(spa, NULL, &abd, flags);
|
||||||
|
vdev_label_read_bootenv_impl(zio, rvd, flags);
|
||||||
|
int err = zio_wait(zio);
|
||||||
|
|
||||||
|
if (abd != NULL) {
|
||||||
|
vdev_boot_envblock_t *vbe = abd_to_buf(abd);
|
||||||
|
if (vbe->vbe_version != VB_RAW) {
|
||||||
|
abd_free(abd);
|
||||||
|
return (SET_ERROR(ENOTSUP));
|
||||||
|
}
|
||||||
|
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_free(abd);
|
||||||
|
/* If we managed to read any successfully, return success. */
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
return (err);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vdev_label_write_bootenv(vdev_t *vd, char *envmap)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (strlen(envmap) >= sizeof (bootenv->vbe_bootenv)) {
|
||||||
|
return (SET_ERROR(E2BIG));
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(spa_config_held(spa, SCL_ALL, RW_WRITER) == SCL_ALL);
|
||||||
|
|
||||||
|
for (int c = 0; c < vd->vdev_children; c++) {
|
||||||
|
int child_err = vdev_label_write_bootenv(vd->vdev_child[c],
|
||||||
|
envmap);
|
||||||
|
/*
|
||||||
|
* As long as any of the disks managed to write all of their
|
||||||
|
* labels successfully, return success.
|
||||||
|
*/
|
||||||
|
if (child_err == 0)
|
||||||
|
error = child_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vd->vdev_ops->vdev_op_leaf || vdev_is_dead(vd) ||
|
||||||
|
!vdev_writeable(vd)) {
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
retry:
|
||||||
|
zio = zio_root(spa, NULL, NULL, flags);
|
||||||
|
for (int l = 0; l < VDEV_LABELS / 2; l++) {
|
||||||
|
vdev_label_write(zio, vd, l, abd,
|
||||||
|
offsetof(vdev_label_t, vl_be),
|
||||||
|
VDEV_PAD_SIZE, NULL, NULL, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = zio_wait(zio);
|
||||||
|
if (error != 0 && !(flags & ZIO_FLAG_TRYHARD)) {
|
||||||
|
flags |= ZIO_FLAG_TRYHARD;
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
abd_free(abd);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ==========================================================================
|
* ==========================================================================
|
||||||
* uberblock load/sync
|
* uberblock load/sync
|
||||||
|
|
|
@ -3512,6 +3512,58 @@ zfs_ioc_log_history(const char *unused, nvlist_t *innvl, nvlist_t *outnvl)
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This ioctl is used to set the bootenv configuration on the current
|
||||||
|
* pool. This configuration is stored in the second padding area of the label,
|
||||||
|
* and it is used by the GRUB bootloader used on Linux to store the contents
|
||||||
|
* of the grubenv file. The file is stored as raw ASCII, and is protected by
|
||||||
|
* an embedded checksum. By default, GRUB will check if the boot filesystem
|
||||||
|
* supports storing the environment data in a special location, and if so,
|
||||||
|
* will invoke filesystem specific logic to retrieve it. This can be overriden
|
||||||
|
* by a variable, should the user so desire.
|
||||||
|
*/
|
||||||
|
/* ARGSUSED */
|
||||||
|
static const zfs_ioc_key_t zfs_keys_set_bootenv[] = {
|
||||||
|
{"envmap", DATA_TYPE_STRING, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
zfs_ioc_set_bootenv(const char *name, nvlist_t *innvl, nvlist_t *outnvl)
|
||||||
|
{
|
||||||
|
char *envmap;
|
||||||
|
int error;
|
||||||
|
spa_t *spa;
|
||||||
|
|
||||||
|
envmap = fnvlist_lookup_string(innvl, "envmap");
|
||||||
|
if ((error = spa_open(name, &spa, FTAG)) != 0)
|
||||||
|
return (error);
|
||||||
|
spa_vdev_state_enter(spa, SCL_ALL);
|
||||||
|
error = vdev_label_write_bootenv(spa->spa_root_vdev, envmap);
|
||||||
|
(void) spa_vdev_state_exit(spa, NULL, 0);
|
||||||
|
spa_close(spa, FTAG);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const zfs_ioc_key_t zfs_keys_get_bootenv[] = {
|
||||||
|
/* no nvl keys */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ARGSUSED */
|
||||||
|
static int
|
||||||
|
zfs_ioc_get_bootenv(const char *name, nvlist_t *innvl, nvlist_t *outnvl)
|
||||||
|
{
|
||||||
|
spa_t *spa;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if ((error = spa_open(name, &spa, FTAG)) != 0)
|
||||||
|
return (error);
|
||||||
|
spa_vdev_state_enter(spa, SCL_ALL);
|
||||||
|
error = vdev_label_read_bootenv(spa->spa_root_vdev, outnvl);
|
||||||
|
(void) spa_vdev_state_exit(spa, NULL, 0);
|
||||||
|
spa_close(spa, FTAG);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The dp_config_rwlock must not be held when calling this, because the
|
* The dp_config_rwlock must not be held when calling this, because the
|
||||||
* unmount may need to write out data.
|
* unmount may need to write out data.
|
||||||
|
@ -6981,6 +7033,16 @@ zfs_ioctl_init(void)
|
||||||
POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_FALSE, B_FALSE,
|
POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_FALSE, B_FALSE,
|
||||||
zfs_keys_fs_wait, ARRAY_SIZE(zfs_keys_fs_wait));
|
zfs_keys_fs_wait, ARRAY_SIZE(zfs_keys_fs_wait));
|
||||||
|
|
||||||
|
zfs_ioctl_register("set_bootenv", ZFS_IOC_SET_BOOTENV,
|
||||||
|
zfs_ioc_set_bootenv, zfs_secpolicy_config, POOL_NAME,
|
||||||
|
POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_FALSE, B_TRUE,
|
||||||
|
zfs_keys_set_bootenv, ARRAY_SIZE(zfs_keys_set_bootenv));
|
||||||
|
|
||||||
|
zfs_ioctl_register("get_bootenv", ZFS_IOC_GET_BOOTENV,
|
||||||
|
zfs_ioc_get_bootenv, zfs_secpolicy_none, POOL_NAME,
|
||||||
|
POOL_CHECK_SUSPENDED, B_FALSE, B_TRUE,
|
||||||
|
zfs_keys_get_bootenv, ARRAY_SIZE(zfs_keys_get_bootenv));
|
||||||
|
|
||||||
/* IOCTLS that use the legacy function signature */
|
/* IOCTLS that use the legacy function signature */
|
||||||
|
|
||||||
zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze,
|
zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze,
|
||||||
|
|
|
@ -751,6 +751,24 @@ test_wait_fs(const char *dataset)
|
||||||
nvlist_free(required);
|
nvlist_free(required);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_get_bootenv(const char *pool)
|
||||||
|
{
|
||||||
|
IOC_INPUT_TEST(ZFS_IOC_GET_BOOTENV, pool, NULL, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_set_bootenv(const char *pool)
|
||||||
|
{
|
||||||
|
nvlist_t *required = fnvlist_alloc();
|
||||||
|
|
||||||
|
fnvlist_add_string(required, "envmap", "test");
|
||||||
|
|
||||||
|
IOC_INPUT_TEST(ZFS_IOC_SET_BOOTENV, pool, required, NULL, 0);
|
||||||
|
|
||||||
|
nvlist_free(required);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
zfs_ioc_input_tests(const char *pool)
|
zfs_ioc_input_tests(const char *pool)
|
||||||
{
|
{
|
||||||
|
@ -840,6 +858,9 @@ zfs_ioc_input_tests(const char *pool)
|
||||||
test_wait(pool);
|
test_wait(pool);
|
||||||
test_wait_fs(dataset);
|
test_wait_fs(dataset);
|
||||||
|
|
||||||
|
test_set_bootenv(pool);
|
||||||
|
test_get_bootenv(pool);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* cleanup
|
* cleanup
|
||||||
*/
|
*/
|
||||||
|
@ -1000,6 +1021,8 @@ validate_ioc_values(void)
|
||||||
CHECK(ZFS_IOC_PLATFORM_BASE + 4 == ZFS_IOC_NEXTBOOT);
|
CHECK(ZFS_IOC_PLATFORM_BASE + 4 == ZFS_IOC_NEXTBOOT);
|
||||||
CHECK(ZFS_IOC_PLATFORM_BASE + 5 == ZFS_IOC_JAIL);
|
CHECK(ZFS_IOC_PLATFORM_BASE + 5 == ZFS_IOC_JAIL);
|
||||||
CHECK(ZFS_IOC_PLATFORM_BASE + 6 == ZFS_IOC_UNJAIL);
|
CHECK(ZFS_IOC_PLATFORM_BASE + 6 == ZFS_IOC_UNJAIL);
|
||||||
|
CHECK(ZFS_IOC_PLATFORM_BASE + 7 == ZFS_IOC_SET_BOOTENV);
|
||||||
|
CHECK(ZFS_IOC_PLATFORM_BASE + 8 == ZFS_IOC_GET_BOOTENV);
|
||||||
|
|
||||||
#undef CHECK
|
#undef CHECK
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue