Add "compatibility" property for zpool feature sets

Property to allow sets of features to be specified; for compatibility
with specific versions / releases / external systems. Influences
the behavior of 'zpool upgrade' and 'zpool create'. Initial man
page changes and test cases included.

Brief synopsis:

zpool create -o compatibility=off|legacy|file[,file...] pool vdev...

compatibility = off : disable compatibility mode (enable all features)
compatibility = legacy : request that no features be enabled
compatibility = file[,file...] : read features from specified files.
Only features present in *all* files will be enabled on the
resulting pool. Filenames may be absolute, or relative to
/etc/zfs/compatibility.d or /usr/share/zfs/compatibility.d (/etc
checked first).

Only affects zpool create, zpool upgrade and zpool status.

ABI changes in libzfs:

* New function "zpool_load_compat" to load and parse compat sets.
* Add "zpool_compat_status_t" typedef for compatibility parse status.
* Add ZPOOL_PROP_COMPATIBILITY to the pool properties enum
* Add ZPOOL_STATUS_COMPATIBILITY_ERR to the pool status enum

An initial set of base compatibility sets are included in
cmd/zpool/compatibility.d, and the Makefile for cmd/zpool is
modified to install these in $pkgdatadir/compatibility.d and to
create symbolic links to a reasonable set of aliases.

Reviewed-by: ericloewe
Reviewed-by: Matthew Ahrens <mahrens@delphix.com>
Reviewed-by: Richard Laager <rlaager@wiktel.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Colm Buckley <colm@tuatha.org>
Closes #11468
This commit is contained in:
Colm
2021-02-18 05:30:45 +00:00
committed by GitHub
parent 35ec51796f
commit 658fb8020f
48 changed files with 5621 additions and 2954 deletions
+4279 -2910
View File
File diff suppressed because it is too large Load Diff
+228
View File
@@ -28,6 +28,7 @@
* Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
* Copyright (c) 2017, Intel Corporation.
* Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>
* Copyright (c) 2021, Colm Buckley <colm@tuatha.org>
*/
#include <errno.h>
@@ -44,8 +45,12 @@
#include <sys/zfs_ioctl.h>
#include <sys/zfs_sysfs.h>
#include <sys/vdev_disk.h>
#include <sys/types.h>
#include <dlfcn.h>
#include <libzutil.h>
#include <fcntl.h>
#include <unistd.h>
#include "zfs_namecheck.h"
#include "zfs_prop.h"
#include "libzfs_impl.h"
@@ -302,6 +307,7 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf,
case ZPOOL_PROP_ALTROOT:
case ZPOOL_PROP_CACHEFILE:
case ZPOOL_PROP_COMMENT:
case ZPOOL_PROP_COMPATIBILITY:
if (zhp->zpool_props != NULL ||
zpool_get_all_props(zhp) == 0) {
(void) strlcpy(buf,
@@ -462,6 +468,8 @@ zpool_valid_proplist(libzfs_handle_t *hdl, const char *poolname,
char *slash, *check;
struct stat64 statbuf;
zpool_handle_t *zhp;
char badword[ZFS_MAXPROPLEN];
char badfile[MAXPATHLEN];
if (nvlist_alloc(&retprops, NV_UNIQUE_NAME, 0) != 0) {
(void) no_memory(hdl);
@@ -671,6 +679,39 @@ zpool_valid_proplist(libzfs_handle_t *hdl, const char *poolname,
*slash = '/';
break;
case ZPOOL_PROP_COMPATIBILITY:
switch (zpool_load_compat(strval, NULL,
badword, badfile)) {
case ZPOOL_COMPATIBILITY_OK:
break;
case ZPOOL_COMPATIBILITY_READERR:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"error reading feature file '%s'"),
badfile);
(void) zfs_error(hdl, EZFS_BADPROP, errbuf);
goto error;
case ZPOOL_COMPATIBILITY_BADFILE:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"feature file '%s' too large or not "
"newline-terminated"),
badfile);
(void) zfs_error(hdl, EZFS_BADPROP, errbuf);
goto error;
case ZPOOL_COMPATIBILITY_BADWORD:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"unknown feature '%s' in feature "
"file '%s'"),
badword, badfile);
(void) zfs_error(hdl, EZFS_BADPROP, errbuf);
goto error;
case ZPOOL_COMPATIBILITY_NOFILES:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"no feature files specified"));
(void) zfs_error(hdl, EZFS_BADPROP, errbuf);
goto error;
}
break;
case ZPOOL_PROP_COMMENT:
for (check = strval; *check != '\0'; check++) {
if (!isprint(*check)) {
@@ -4663,3 +4704,190 @@ zpool_get_bootenv(zpool_handle_t *zhp, nvlist_t **nvlp)
return (error);
}
/*
* Attempt to read and parse feature file(s) (from "compatibility" property).
* Files contain zpool feature names, comma or whitespace-separated.
* Comments (# character to next newline) are discarded.
*
* Arguments:
* compatibility : string containing feature filenames
* features : either NULL or pointer to array of boolean
* badtoken : either NULL or pointer to char[ZFS_MAXPROPLEN]
* badfile : either NULL or pointer to char[MAXPATHLEN]
*
* compatibility is NULL (unset), "", "off", "legacy", or list of
* comma-separated filenames. filenames should either be absolute,
* or relative to:
* 1) ZPOOL_SYSCONF_COMPAT_D (eg: /etc/zfs/compatibility.d) or
* 2) ZPOOL_DATA_COMPAT_D (eg: /usr/share/zfs/compatibility.d).
* (Unset), "" or "off" => enable all features
* "legacy" => disable all features
* Any feature names read from files which match unames in spa_feature_table
* will have the corresponding boolean set in the features array (if non-NULL).
* If more than one feature set specified, only features present in *all* of
* them will be set.
*
* An unreadable filename will be strlcpy'd to badfile (if non-NULL).
* An unrecognized feature will be strlcpy'd to badtoken (if non-NULL).
*
* Return values:
* ZPOOL_COMPATIBILITY_OK : files read and parsed ok
* ZPOOL_COMPATIBILITY_READERR : file could not be opened / mmap'd
* ZPOOL_COMPATIBILITY_BADFILE : file too big or not a text file
* ZPOOL_COMPATIBILITY_BADWORD : file contains invalid feature name
* ZPOOL_COMPATIBILITY_NOFILES : no file names found
*/
zpool_compat_status_t
zpool_load_compat(const char *compatibility,
boolean_t *features, char *badtoken, char *badfile)
{
int sdirfd, ddirfd, featfd;
int i;
struct stat fs;
char *fc; /* mmap of file */
char *ps, *ls, *ws; /* strtok state */
char *file, *line, *word;
char filenames[ZFS_MAXPROPLEN];
int filecount = 0;
/* special cases (unset), "" and "off" => enable all features */
if (compatibility == NULL || compatibility[0] == '\0' ||
strcmp(compatibility, ZPOOL_COMPAT_OFF) == 0) {
if (features != NULL)
for (i = 0; i < SPA_FEATURES; i++)
features[i] = B_TRUE;
return (ZPOOL_COMPATIBILITY_OK);
}
/* Final special case "legacy" => disable all features */
if (strcmp(compatibility, ZPOOL_COMPAT_LEGACY) == 0) {
if (features != NULL)
for (i = 0; i < SPA_FEATURES; i++)
features[i] = B_FALSE;
return (ZPOOL_COMPATIBILITY_OK);
}
/*
* Start with all true; will be ANDed with results from each file
*/
if (features != NULL)
for (i = 0; i < SPA_FEATURES; i++)
features[i] = B_TRUE;
/*
* We ignore errors from the directory open()
* as they're only needed if the filename is relative
* which will be checked during the openat().
*/
#ifdef O_PATH
sdirfd = open(ZPOOL_SYSCONF_COMPAT_D, O_DIRECTORY | O_PATH);
ddirfd = open(ZPOOL_DATA_COMPAT_D, O_DIRECTORY | O_PATH);
#else
sdirfd = open(ZPOOL_SYSCONF_COMPAT_D, O_DIRECTORY | O_RDONLY);
ddirfd = open(ZPOOL_DATA_COMPAT_D, O_DIRECTORY | O_RDONLY);
#endif
(void) strlcpy(filenames, compatibility, ZFS_MAXPROPLEN);
file = strtok_r(filenames, ",", &ps);
while (file != NULL) {
boolean_t features_local[SPA_FEATURES];
/* try sysconfdir first, then datadir */
if ((featfd = openat(sdirfd, file, 0, O_RDONLY)) < 0)
featfd = openat(ddirfd, file, 0, O_RDONLY);
if (featfd < 0 || fstat(featfd, &fs) < 0) {
(void) close(featfd);
(void) close(sdirfd);
(void) close(ddirfd);
if (badfile != NULL)
(void) strlcpy(badfile, file, MAXPATHLEN);
return (ZPOOL_COMPATIBILITY_READERR);
}
/* Too big or too small */
if (fs.st_size < 1 || fs.st_size > ZPOOL_COMPAT_MAXSIZE) {
(void) close(featfd);
(void) close(sdirfd);
(void) close(ddirfd);
if (badfile != NULL)
(void) strlcpy(badfile, file, MAXPATHLEN);
return (ZPOOL_COMPATIBILITY_BADFILE);
}
/* private mmap() so we can strtok safely */
fc = (char *)mmap(NULL, fs.st_size,
PROT_READ|PROT_WRITE, MAP_PRIVATE, featfd, 0);
(void) close(featfd);
if (fc < 0) {
(void) close(sdirfd);
(void) close(ddirfd);
if (badfile != NULL)
(void) strlcpy(badfile, file, MAXPATHLEN);
return (ZPOOL_COMPATIBILITY_READERR);
}
/* Text file sanity check - last char should be newline */
if (fc[fs.st_size - 1] != '\n') {
(void) munmap((void *) fc, fs.st_size);
(void) close(sdirfd);
(void) close(ddirfd);
if (badfile != NULL)
(void) strlcpy(badfile, file, MAXPATHLEN);
return (ZPOOL_COMPATIBILITY_BADFILE);
}
/* replace with NUL to ensure we have a delimiter */
fc[fs.st_size - 1] = '\0';
for (i = 0; i < SPA_FEATURES; i++)
features_local[i] = B_FALSE;
line = strtok_r(fc, "\n", &ls);
while (line != NULL) {
/* discard comments */
*(strchrnul(line, '#')) = '\0';
word = strtok_r(line, ", \t", &ws);
while (word != NULL) {
/* Find matching feature name */
for (i = 0; i < SPA_FEATURES; i++) {
zfeature_info_t *fi =
&spa_feature_table[i];
if (strcmp(word, fi->fi_uname) == 0) {
features_local[i] = B_TRUE;
break;
}
}
if (i == SPA_FEATURES) {
if (badtoken != NULL)
(void) strlcpy(badtoken, word,
ZFS_MAXPROPLEN);
if (badfile != NULL)
(void) strlcpy(badfile, file,
MAXPATHLEN);
(void) munmap((void *) fc, fs.st_size);
(void) close(sdirfd);
(void) close(ddirfd);
return (ZPOOL_COMPATIBILITY_BADWORD);
}
word = strtok_r(NULL, ", \t", &ws);
}
line = strtok_r(NULL, "\n", &ls);
}
(void) munmap((void *) fc, fs.st_size);
if (features != NULL) {
for (i = 0; i < SPA_FEATURES; i++)
features[i] &= features_local[i];
}
filecount++;
file = strtok_r(NULL, ",", &ps);
}
(void) close(sdirfd);
(void) close(ddirfd);
if (filecount == 0)
return (ZPOOL_COMPATIBILITY_NOFILES);
return (ZPOOL_COMPATIBILITY_OK);
}
+25 -4
View File
@@ -23,6 +23,7 @@
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012 by Delphix. All rights reserved.
* Copyright (c) 2013 Steven Hartland. All rights reserved.
* Copyright (c) 2021, Colm Buckley <colm@tuatha.org>
*/
/*
@@ -87,6 +88,7 @@ static char *zfs_msgid_table[] = {
* ZPOOL_STATUS_REMOVED_DEV
* ZPOOL_STATUS_REBUILDING
* ZPOOL_STATUS_REBUILD_SCRUB
* ZPOOL_STATUS_COMPATIBILITY_ERR
* ZPOOL_STATUS_OK
*/
};
@@ -218,7 +220,8 @@ find_vdev_problem(nvlist_t *vdev, int (*func)(vdev_stat_t *, uint_t),
* only picks the most damaging of all the current errors to report.
*/
static zpool_status_t
check_status(nvlist_t *config, boolean_t isimport, zpool_errata_t *erratap)
check_status(nvlist_t *config, boolean_t isimport,
zpool_errata_t *erratap, const char *compat)
{
nvlist_t *nvroot;
vdev_stat_t *vs;
@@ -471,9 +474,16 @@ check_status(nvlist_t *config, boolean_t isimport, zpool_errata_t *erratap)
ZPOOL_CONFIG_FEATURE_STATS);
}
/* check against all features, or limited set? */
boolean_t pool_features[SPA_FEATURES];
if (zpool_load_compat(compat, pool_features, NULL, NULL) !=
ZPOOL_COMPATIBILITY_OK)
return (ZPOOL_STATUS_COMPATIBILITY_ERR);
for (i = 0; i < SPA_FEATURES; i++) {
zfeature_info_t *fi = &spa_feature_table[i];
if (!nvlist_exists(feat, fi->fi_guid))
if (pool_features[i] &&
!nvlist_exists(feat, fi->fi_guid))
return (ZPOOL_STATUS_FEAT_DISABLED);
}
}
@@ -484,7 +494,18 @@ check_status(nvlist_t *config, boolean_t isimport, zpool_errata_t *erratap)
zpool_status_t
zpool_get_status(zpool_handle_t *zhp, char **msgid, zpool_errata_t *errata)
{
zpool_status_t ret = check_status(zhp->zpool_config, B_FALSE, errata);
/*
* pass in the desired feature set, as
* it affects check for disabled features
*/
char compatibility[ZFS_MAXPROPLEN];
if (zpool_get_prop(zhp, ZPOOL_PROP_COMPATIBILITY, compatibility,
ZFS_MAXPROPLEN, NULL, B_FALSE) != 0)
compatibility[0] = '\0';
zpool_status_t ret = check_status(zhp->zpool_config, B_FALSE, errata,
compatibility);
if (msgid != NULL) {
if (ret >= NMSGID)
*msgid = NULL;
@@ -497,7 +518,7 @@ zpool_get_status(zpool_handle_t *zhp, char **msgid, zpool_errata_t *errata)
zpool_status_t
zpool_import_status(nvlist_t *config, char **msgid, zpool_errata_t *errata)
{
zpool_status_t ret = check_status(config, B_TRUE, errata);
zpool_status_t ret = check_status(config, B_TRUE, errata, NULL);
if (ret >= NMSGID)
*msgid = NULL;
+10
View File
@@ -24,6 +24,7 @@
* Copyright (c) 2012, 2018 by Delphix. All rights reserved.
* Copyright 2015 RackTop Systems.
* Copyright (c) 2016, Intel Corporation.
* Copyright (c) 2021, Colm Buckley <colm@tuatha.org>
*/
/*
@@ -552,12 +553,14 @@ get_configs(libpc_handle_t *hdl, pool_list_t *pl, boolean_t active_ok,
* pool guid
* name
* comment (if available)
* compatibility features (if available)
* pool state
* hostid (if available)
* hostname (if available)
*/
uint64_t state, version;
char *comment = NULL;
char *compatibility = NULL;
version = fnvlist_lookup_uint64(tmp,
ZPOOL_CONFIG_VERSION);
@@ -577,6 +580,13 @@ get_configs(libpc_handle_t *hdl, pool_list_t *pl, boolean_t active_ok,
fnvlist_add_string(config,
ZPOOL_CONFIG_COMMENT, comment);
if (nvlist_lookup_string(tmp,
ZPOOL_CONFIG_COMPATIBILITY,
&compatibility) == 0)
fnvlist_add_string(config,
ZPOOL_CONFIG_COMPATIBILITY,
compatibility);
state = fnvlist_lookup_uint64(tmp,
ZPOOL_CONFIG_POOL_STATE);
fnvlist_add_uint64(config,