summaryrefslogtreecommitdiff
path: root/usr
diff options
context:
space:
mode:
authorek110237 <none@none>2006-10-16 10:35:01 -0700
committerek110237 <none@none>2006-10-16 10:35:01 -0700
commit06eeb2ad640ce72d394ac521094bed7681044408 (patch)
treed955b15196c4d84d42b931f476fe38efa1db115c /usr
parent4730c9c455fdcb7ffe43b6f08727468d499ca837 (diff)
downloadillumos-gate-06eeb2ad640ce72d394ac521094bed7681044408.tar.gz
PSARC 2006/288 zpool history
6343741 want to store a command history on disk 6476196 spa_sync_spares() is missing a nvlist_free()
Diffstat (limited to 'usr')
-rw-r--r--usr/src/cmd/truss/codes.c4
-rw-r--r--usr/src/cmd/zdb/zdb.c2
-rw-r--r--usr/src/cmd/zfs/zfs_main.c92
-rw-r--r--usr/src/cmd/zpool/zpool_main.c159
-rw-r--r--usr/src/lib/libzfs/common/libzfs.h4
-rw-r--r--usr/src/lib/libzfs/common/libzfs_pool.c175
-rw-r--r--usr/src/lib/libzfs/common/libzfs_util.c2
-rw-r--r--usr/src/lib/libzfs/common/mapfile-vers2
-rw-r--r--usr/src/uts/common/Makefile.files1
-rw-r--r--usr/src/uts/common/fs/zfs/dmu.c2
-rw-r--r--usr/src/uts/common/fs/zfs/spa.c21
-rw-r--r--usr/src/uts/common/fs/zfs/spa_history.c354
-rw-r--r--usr/src/uts/common/fs/zfs/spa_misc.c1
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dmu.h3
-rw-r--r--usr/src/uts/common/fs/zfs/sys/spa.h7
-rw-r--r--usr/src/uts/common/fs/zfs/sys/spa_impl.h10
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h3
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_ioctl.c95
-rw-r--r--usr/src/uts/common/sys/fs/zfs.h12
19 files changed, 908 insertions, 41 deletions
diff --git a/usr/src/cmd/truss/codes.c b/usr/src/cmd/truss/codes.c
index c83f33cd71..452d52c9ed 100644
--- a/usr/src/cmd/truss/codes.c
+++ b/usr/src/cmd/truss/codes.c
@@ -854,6 +854,10 @@ const struct ioc {
"zfs_cmd_t" },
{ (uint_t)ZFS_IOC_POOL_UPGRADE, "ZFS_IOC_POOL_UPGRADE",
"zfs_cmd_t" },
+ { (uint_t)ZFS_IOC_POOL_GET_HISTORY, "ZFS_IOC_POOL_GET_HISTORY",
+ "zfs_cmd_t" },
+ { (uint_t)ZFS_IOC_POOL_LOG_HISTORY, "ZFS_IOC_POOL_LOG_HISTORY",
+ "zfs_cmd_t" },
{ (uint_t)ZFS_IOC_VDEV_ADD, "ZFS_IOC_VDEV_ADD",
"zfs_cmd_t" },
{ (uint_t)ZFS_IOC_VDEV_REMOVE, "ZFS_IOC_VDEV_REMOVE",
diff --git a/usr/src/cmd/zdb/zdb.c b/usr/src/cmd/zdb/zdb.c
index 6fcba4721f..31e7146952 100644
--- a/usr/src/cmd/zdb/zdb.c
+++ b/usr/src/cmd/zdb/zdb.c
@@ -933,6 +933,8 @@ static object_viewer_t *object_viewer[DMU_OT_NUMTYPES] = {
dump_uint64, /* other uint64[] */
dump_zap, /* other ZAP */
dump_zap, /* persistent error log */
+ dump_uint8, /* SPA history */
+ dump_uint64, /* SPA history offsets */
};
static void
diff --git a/usr/src/cmd/zfs/zfs_main.c b/usr/src/cmd/zfs/zfs_main.c
index 80817aa9a1..f74b15aa2d 100644
--- a/usr/src/cmd/zfs/zfs_main.c
+++ b/usr/src/cmd/zfs/zfs_main.c
@@ -380,6 +380,7 @@ zfs_do_clone(int argc, char **argv)
ret = zfs_share(clone);
zfs_close(clone);
}
+ zpool_log_history(g_zfs, argc, argv, argv[2], B_FALSE, B_FALSE);
}
zfs_close(zhp);
@@ -411,7 +412,8 @@ zfs_do_create(int argc, char **argv)
nvlist_t *props = NULL;
uint64_t intval;
char *propname;
- char *propval, *strval;
+ char *propval = NULL;
+ char *strval;
if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) {
(void) fprintf(stderr, gettext("internal error: "
@@ -529,6 +531,11 @@ zfs_do_create(int argc, char **argv)
if (zfs_create(g_zfs, argv[0], type, props) != 0)
goto error;
+ if (propval != NULL)
+ *(propval - 1) = '=';
+ zpool_log_history(g_zfs, argc + optind, argv - optind, argv[0],
+ B_FALSE, B_FALSE);
+
if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_ANY)) == NULL)
goto error;
@@ -818,6 +825,9 @@ zfs_do_destroy(int argc, char **argv)
if (destroy_callback(zhp, &cb) != 0)
return (1);
+ zpool_log_history(g_zfs, argc + optind, argv - optind, argv[0],
+ B_FALSE, B_FALSE);
+
return (0);
}
@@ -1274,10 +1284,21 @@ zfs_do_get(int argc, char **argv)
* useful for setting a property on a hierarchy-wide basis, regardless of any
* local modifications for each dataset.
*/
+typedef struct inherit_cbdata {
+ char *cb_propname;
+ boolean_t cb_any_successful;
+} inherit_cbdata_t;
+
static int
inherit_callback(zfs_handle_t *zhp, void *data)
{
- return (zfs_prop_inherit(zhp, data) != 0);
+ inherit_cbdata_t *cbp = data;
+ int ret;
+
+ ret = zfs_prop_inherit(zhp, cbp->cb_propname);
+ if (ret == 0)
+ cbp->cb_any_successful = B_TRUE;
+ return (ret != 0);
}
static int
@@ -1286,7 +1307,8 @@ zfs_do_inherit(int argc, char **argv)
boolean_t recurse = B_FALSE;
int c;
zfs_prop_t prop;
- char *propname;
+ inherit_cbdata_t cb;
+ int ret;
/* check options */
while ((c = getopt(argc, argv, "r")) != -1) {
@@ -1315,36 +1337,45 @@ zfs_do_inherit(int argc, char **argv)
usage(B_FALSE);
}
- propname = argv[0];
+ cb.cb_propname = argv[0];
argc--;
argv++;
- if ((prop = zfs_name_to_prop(propname)) != ZFS_PROP_INVAL) {
+ if ((prop = zfs_name_to_prop(cb.cb_propname)) != ZFS_PROP_INVAL) {
if (zfs_prop_readonly(prop)) {
(void) fprintf(stderr, gettext(
"%s property is read-only\n"),
- propname);
+ cb.cb_propname);
return (1);
}
if (!zfs_prop_inheritable(prop)) {
(void) fprintf(stderr, gettext("'%s' property cannot "
- "be inherited\n"), propname);
+ "be inherited\n"), cb.cb_propname);
if (prop == ZFS_PROP_QUOTA ||
prop == ZFS_PROP_RESERVATION)
(void) fprintf(stderr, gettext("use 'zfs set "
- "%s=none' to clear\n"), propname);
+ "%s=none' to clear\n"), cb.cb_propname);
return (1);
}
- } else if (!zfs_prop_user(propname)) {
+ } else if (!zfs_prop_user(cb.cb_propname)) {
(void) fprintf(stderr, gettext(
"invalid property '%s'\n"),
- propname);
+ cb.cb_propname);
usage(B_FALSE);
}
- return (zfs_for_each(argc, argv, recurse,
+ cb.cb_any_successful = B_FALSE;
+
+ ret = zfs_for_each(argc, argv, recurse,
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, NULL, NULL,
- inherit_callback, propname));
+ inherit_callback, &cb);
+
+ if (cb.cb_any_successful) {
+ zpool_log_history(g_zfs, argc + optind + 1, argv - optind - 1,
+ argv[0], B_FALSE, B_FALSE);
+ }
+
+ return (ret);
}
/*
@@ -1638,6 +1669,9 @@ zfs_do_rename(int argc, char **argv)
ret = (zfs_rename(zhp, argv[2]) != 0);
+ if (!ret)
+ zpool_log_history(g_zfs, argc, argv, argv[2], B_FALSE, B_FALSE);
+
zfs_close(zhp);
return (ret);
}
@@ -1678,6 +1712,9 @@ zfs_do_promote(int argc, char **argv)
ret = (zfs_promote(zhp) != 0);
+ if (!ret)
+ zpool_log_history(g_zfs, argc, argv, argv[1], B_FALSE, B_FALSE);
+
zfs_close(zhp);
return (ret);
}
@@ -1845,6 +1882,11 @@ zfs_do_rollback(int argc, char **argv)
*/
ret = zfs_rollback(zhp, snap, force);
+ if (!ret) {
+ zpool_log_history(g_zfs, argc + optind, argv - optind, argv[0],
+ B_FALSE, B_FALSE);
+ }
+
out:
zfs_close(snap);
zfs_close(zhp);
@@ -1863,6 +1905,7 @@ out:
typedef struct set_cbdata {
char *cb_propname;
char *cb_value;
+ boolean_t cb_any_successful;
} set_cbdata_t;
static int
@@ -1883,6 +1926,7 @@ set_callback(zfs_handle_t *zhp, void *data)
}
return (1);
}
+ cbp->cb_any_successful = B_TRUE;
return (0);
}
@@ -1890,6 +1934,7 @@ static int
zfs_do_set(int argc, char **argv)
{
set_cbdata_t cb;
+ int ret;
/* check for options */
if (argc > 1 && argv[1][0] == '-') {
@@ -1919,6 +1964,7 @@ zfs_do_set(int argc, char **argv)
*cb.cb_value = '\0';
cb.cb_value++;
+ cb.cb_any_successful = B_FALSE;
if (*cb.cb_propname == '\0') {
(void) fprintf(stderr,
@@ -1926,8 +1972,15 @@ zfs_do_set(int argc, char **argv)
usage(B_FALSE);
}
- return (zfs_for_each(argc - 2, argv + 2, B_FALSE,
- ZFS_TYPE_ANY, NULL, NULL, set_callback, &cb));
+ ret = zfs_for_each(argc - 2, argv + 2, B_FALSE,
+ ZFS_TYPE_ANY, NULL, NULL, set_callback, &cb);
+
+ if (cb.cb_any_successful) {
+ *(cb.cb_value - 1) = '=';
+ zpool_log_history(g_zfs, argc, argv, argv[2], B_FALSE, B_FALSE);
+ }
+
+ return (ret);
}
/*
@@ -1972,8 +2025,11 @@ zfs_do_snapshot(int argc, char **argv)
ret = zfs_snapshot(g_zfs, argv[0], recursive);
if (ret && recursive)
(void) fprintf(stderr, gettext("no snapshots were created\n"));
+ if (!ret) {
+ zpool_log_history(g_zfs, argc + optind, argv - optind, argv[0],
+ B_FALSE, B_FALSE);
+ }
return (ret != 0);
-
}
/*
@@ -2121,6 +2177,12 @@ zfs_do_receive(int argc, char **argv)
}
err = zfs_receive(g_zfs, argv[0], isprefix, verbose, dryrun, force);
+
+ if (!err) {
+ zpool_log_history(g_zfs, argc + optind, argv - optind, argv[0],
+ B_FALSE, B_FALSE);
+ }
+
return (err != 0);
}
diff --git a/usr/src/cmd/zpool/zpool_main.c b/usr/src/cmd/zpool/zpool_main.c
index 6ac0ce051e..487d16080b 100644
--- a/usr/src/cmd/zpool/zpool_main.c
+++ b/usr/src/cmd/zpool/zpool_main.c
@@ -73,6 +73,8 @@ static int zpool_do_export(int, char **);
static int zpool_do_upgrade(int, char **);
+static int zpool_do_history(int, char **);
+
/*
* These libumem hooks provide a reasonable set of defaults for the allocator's
* debugging facilities.
@@ -97,6 +99,7 @@ typedef enum {
HELP_DESTROY,
HELP_DETACH,
HELP_EXPORT,
+ HELP_HISTORY,
HELP_IMPORT,
HELP_IOSTAT,
HELP_LIST,
@@ -148,7 +151,9 @@ static zpool_command_t command_table[] = {
{ NULL },
{ "import", zpool_do_import, HELP_IMPORT },
{ "export", zpool_do_export, HELP_EXPORT },
- { "upgrade", zpool_do_upgrade, HELP_UPGRADE }
+ { "upgrade", zpool_do_upgrade, HELP_UPGRADE },
+ { NULL },
+ { "history", zpool_do_history, HELP_HISTORY }
};
#define NCOMMAND (sizeof (command_table) / sizeof (command_table[0]))
@@ -174,6 +179,8 @@ get_usage(zpool_help_t idx) {
return (gettext("\tdetach <pool> <device>\n"));
case HELP_EXPORT:
return (gettext("\texport [-f] <pool> ...\n"));
+ case HELP_HISTORY:
+ return (gettext("\thistory [<pool>]\n"));
case HELP_IMPORT:
return (gettext("\timport [-d dir] [-D]\n"
"\timport [-d dir] [-D] [-f] [-o opts] [-R root] -a\n"
@@ -433,6 +440,10 @@ zpool_do_add(int argc, char **argv)
ret = 0;
} else {
ret = (zpool_add(zhp, nvroot) != 0);
+ if (!ret) {
+ zpool_log_history(g_zfs, argc + 1 + optind,
+ argv - 1 - optind, poolname, B_TRUE, B_FALSE);
+ }
}
nvlist_free(nvroot);
@@ -474,6 +485,10 @@ zpool_do_remove(int argc, char **argv)
return (1);
ret = (zpool_vdev_remove(zhp, argv[1]) != 0);
+ if (!ret) {
+ zpool_log_history(g_zfs, ++argc, --argv, poolname, B_TRUE,
+ B_FALSE);
+ }
return (ret);
}
@@ -666,6 +681,8 @@ zpool_do_create(int argc, char **argv)
ret = zfs_share(pool);
zfs_close(pool);
}
+ zpool_log_history(g_zfs, argc + optind, argv - optind,
+ poolname, B_TRUE, B_TRUE);
} else if (libzfs_errno(g_zfs) == EZFS_INVALIDNAME) {
(void) fprintf(stderr, gettext("pool name may have "
"been omitted\n"));
@@ -738,6 +755,9 @@ zpool_do_destroy(int argc, char **argv)
return (1);
}
+ zpool_log_history(g_zfs, argc + optind, argv - optind, pool, B_TRUE,
+ B_FALSE);
+
ret = (zpool_destroy(zhp) != 0);
zpool_close(zhp);
@@ -798,6 +818,9 @@ zpool_do_export(int argc, char **argv)
continue;
}
+ zpool_log_history(g_zfs, argc + optind, argv - optind, argv[i],
+ B_TRUE, B_FALSE);
+
if (zpool_export(zhp) != 0)
ret = 1;
@@ -1076,7 +1099,7 @@ show_import(nvlist_t *config)
*/
static int
do_import(nvlist_t *config, const char *newname, const char *mntopts,
- const char *altroot, int force)
+ const char *altroot, int force, int argc, char **argv)
{
zpool_handle_t *zhp;
char *name;
@@ -1107,6 +1130,8 @@ do_import(nvlist_t *config, const char *newname, const char *mntopts,
if (newname != NULL)
name = (char *)newname;
+ zpool_log_history(g_zfs, argc, argv, name, B_TRUE, B_FALSE);
+
verify((zhp = zpool_open(g_zfs, name)) != NULL);
if (zpool_mount_datasets(zhp, mntopts, 0) != 0) {
@@ -1291,7 +1316,8 @@ zpool_do_import(int argc, char **argv)
if (do_all)
err |= do_import(config, NULL, mntopts,
- altroot, do_force);
+ altroot, do_force, argc + optind,
+ argv - optind);
else
show_import(config);
} else if (searchname != NULL) {
@@ -1339,7 +1365,8 @@ zpool_do_import(int argc, char **argv)
err = B_TRUE;
} else {
err |= do_import(found_config, argc == 1 ? NULL :
- argv[1], mntopts, altroot, do_force);
+ argv[1], mntopts, altroot, do_force, argc + optind,
+ argv - optind);
}
}
@@ -1993,6 +2020,8 @@ zpool_do_attach_or_replace(int argc, char **argv, int replacing)
zpool_handle_t *zhp;
nvlist_t *config;
int ret;
+ int log_argc;
+ char **log_argv;
/* check options */
while ((c = getopt(argc, argv, "f")) != -1) {
@@ -2007,6 +2036,8 @@ zpool_do_attach_or_replace(int argc, char **argv, int replacing)
}
}
+ log_argc = argc;
+ log_argv = argv;
argc -= optind;
argv += optind;
@@ -2064,6 +2095,11 @@ zpool_do_attach_or_replace(int argc, char **argv, int replacing)
ret = zpool_vdev_attach(zhp, old_disk, new_disk, nvroot, replacing);
+ if (!ret) {
+ zpool_log_history(g_zfs, log_argc, log_argv, poolname, B_TRUE,
+ B_FALSE);
+ }
+
nvlist_free(nvroot);
zpool_close(zhp);
@@ -2153,6 +2189,10 @@ zpool_do_detach(int argc, char **argv)
ret = zpool_vdev_detach(zhp, path);
+ if (!ret) {
+ zpool_log_history(g_zfs, argc + optind, argv - optind, poolname,
+ B_TRUE, B_FALSE);
+ }
zpool_close(zhp);
return (ret);
@@ -2206,6 +2246,10 @@ zpool_do_online(int argc, char **argv)
else
ret = 1;
+ if (!ret) {
+ zpool_log_history(g_zfs, argc + optind, argv - optind, poolname,
+ B_TRUE, B_FALSE);
+ }
zpool_close(zhp);
return (ret);
@@ -2270,6 +2314,10 @@ zpool_do_offline(int argc, char **argv)
else
ret = 1;
+ if (!ret) {
+ zpool_log_history(g_zfs, argc + optind, argv - optind, poolname,
+ B_TRUE, B_FALSE);
+ }
zpool_close(zhp);
return (ret);
@@ -2306,6 +2354,8 @@ zpool_do_clear(int argc, char **argv)
if (zpool_clear(zhp, device) != 0)
ret = 1;
+ if (!ret)
+ zpool_log_history(g_zfs, argc, argv, pool, B_TRUE, B_FALSE);
zpool_close(zhp);
return (ret);
@@ -2313,12 +2363,15 @@ zpool_do_clear(int argc, char **argv)
typedef struct scrub_cbdata {
int cb_type;
+ int cb_argc;
+ char **cb_argv;
} scrub_cbdata_t;
int
scrub_callback(zpool_handle_t *zhp, void *data)
{
scrub_cbdata_t *cb = data;
+ int err;
/*
* Ignore faulted pools.
@@ -2329,7 +2382,14 @@ scrub_callback(zpool_handle_t *zhp, void *data)
return (1);
}
- return (zpool_scrub(zhp, cb->cb_type) != 0);
+ err = zpool_scrub(zhp, cb->cb_type);
+
+ if (!err) {
+ zpool_log_history(g_zfs, cb->cb_argc, cb->cb_argv,
+ zpool_get_name(zhp), B_TRUE, B_FALSE);
+ }
+
+ return (err != 0);
}
/*
@@ -2358,6 +2418,8 @@ zpool_do_scrub(int argc, char **argv)
}
}
+ cb.cb_argc = argc;
+ cb.cb_argv = argv;
argc -= optind;
argv += optind;
@@ -2936,6 +2998,8 @@ typedef struct upgrade_cbdata {
int cb_all;
int cb_first;
int cb_newer;
+ int cb_argc;
+ char **cb_argv;
} upgrade_cbdata_t;
static int
@@ -2968,9 +3032,13 @@ upgrade_cb(zpool_handle_t *zhp, void *arg)
} else {
cbp->cb_first = B_FALSE;
ret = zpool_upgrade(zhp);
- if (ret == 0)
+ if (!ret) {
+ zpool_log_history(g_zfs, cbp->cb_argc,
+ cbp->cb_argv, zpool_get_name(zhp), B_TRUE,
+ B_FALSE);
(void) printf(gettext("Successfully upgraded "
"'%s'\n"), zpool_get_name(zhp));
+ }
}
} else if (cbp->cb_newer && version > ZFS_VERSION) {
assert(!cbp->cb_all);
@@ -2994,11 +3062,12 @@ upgrade_cb(zpool_handle_t *zhp, void *arg)
/* ARGSUSED */
static int
-upgrade_one(zpool_handle_t *zhp, void *unused)
+upgrade_one(zpool_handle_t *zhp, void *data)
{
nvlist_t *config;
uint64_t version;
int ret;
+ upgrade_cbdata_t *cbp = data;
config = zpool_get_config(zhp, NULL);
verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_VERSION,
@@ -3011,10 +3080,14 @@ upgrade_one(zpool_handle_t *zhp, void *unused)
}
ret = zpool_upgrade(zhp);
- if (ret == 0)
+
+ if (!ret) {
+ zpool_log_history(g_zfs, cbp->cb_argc, cbp->cb_argv,
+ zpool_get_name(zhp), B_TRUE, B_FALSE);
(void) printf(gettext("Successfully upgraded '%s' "
"from version %llu to version %llu\n"), zpool_get_name(zhp),
(u_longlong_t)version, (u_longlong_t)ZFS_VERSION);
+ }
return (ret != 0);
}
@@ -3052,6 +3125,8 @@ zpool_do_upgrade(int argc, char **argv)
}
}
+ cb.cb_argc = argc;
+ cb.cb_argv = argv;
argc -= optind;
argv += optind;
@@ -3116,7 +3191,73 @@ zpool_do_upgrade(int argc, char **argv)
"their associated\nfeatures.\n"));
}
} else {
- ret = for_each_pool(argc, argv, B_FALSE, upgrade_one, NULL);
+ ret = for_each_pool(argc, argv, B_FALSE, upgrade_one, &cb);
+ }
+
+ return (ret);
+}
+
+/*
+ * Print out the command history for a specific pool.
+ */
+static int
+get_history_one(zpool_handle_t *zhp, void *data)
+{
+ nvlist_t *nvhis;
+ nvlist_t **records;
+ uint_t numrecords;
+ char *cmdstr;
+ uint64_t dst_time;
+ time_t tsec;
+ struct tm t;
+ char tbuf[30];
+ int ret, i;
+
+ *(boolean_t *)data = B_FALSE;
+
+ (void) printf(gettext("History for '%s':\n"), zpool_get_name(zhp));
+
+ if ((ret = zpool_get_history(zhp, &nvhis)) != 0)
+ return (ret);
+
+ verify(nvlist_lookup_nvlist_array(nvhis, ZPOOL_HIST_RECORD,
+ &records, &numrecords) == 0);
+ for (i = 0; i < numrecords; i++) {
+ if (nvlist_lookup_uint64(records[i], ZPOOL_HIST_TIME,
+ &dst_time) == 0) {
+ verify(nvlist_lookup_string(records[i], ZPOOL_HIST_CMD,
+ &cmdstr) == 0);
+ tsec = dst_time;
+ (void) localtime_r(&tsec, &t);
+ (void) strftime(tbuf, sizeof (tbuf), "%F.%T", &t);
+ (void) printf("%s %s\n", tbuf, cmdstr);
+ }
+ }
+ (void) printf("\n");
+ nvlist_free(nvhis);
+
+ return (ret);
+}
+
+/*
+ * zpool history <pool>
+ *
+ * Displays the history of commands that modified pools.
+ */
+int
+zpool_do_history(int argc, char **argv)
+{
+ boolean_t first = B_TRUE;
+ int ret;
+
+ argc -= optind;
+ argv += optind;
+
+ ret = for_each_pool(argc, argv, B_FALSE, get_history_one, &first);
+
+ if (argc == 0 && first == B_TRUE) {
+ (void) printf(gettext("no pools available\n"));
+ return (0);
}
return (ret);
diff --git a/usr/src/lib/libzfs/common/libzfs.h b/usr/src/lib/libzfs/common/libzfs.h
index 64ebcb8e0c..1611547985 100644
--- a/usr/src/lib/libzfs/common/libzfs.h
+++ b/usr/src/lib/libzfs/common/libzfs.h
@@ -90,6 +90,7 @@ enum {
EZFS_ISSPARE, /* device is a hot spare */
EZFS_INVALCONFIG, /* invalid vdev configuration */
EZFS_RECURSIVE, /* recursive dependency */
+ EZFS_NOHISTORY, /* no history object */
EZFS_UNKNOWN /* unknown error */
};
@@ -218,6 +219,9 @@ extern nvlist_t *zpool_find_import(libzfs_handle_t *, int, char **);
*/
extern char *zpool_vdev_name(libzfs_handle_t *, zpool_handle_t *, nvlist_t *);
extern int zpool_upgrade(zpool_handle_t *);
+extern int zpool_get_history(zpool_handle_t *, nvlist_t **);
+extern void zpool_log_history(libzfs_handle_t *, int, char **, const char *,
+ boolean_t, boolean_t);
/*
* Basic handle manipulations. These functions do not create or destroy the
diff --git a/usr/src/lib/libzfs/common/libzfs_pool.c b/usr/src/lib/libzfs/common/libzfs_pool.c
index 83a618559c..812feef6ae 100644
--- a/usr/src/lib/libzfs/common/libzfs_pool.c
+++ b/usr/src/lib/libzfs/common/libzfs_pool.c
@@ -38,6 +38,7 @@
#include <unistd.h>
#include <sys/zfs_ioctl.h>
#include <sys/zio.h>
+#include <strings.h>
#include "zfs_namecheck.h"
#include "libzfs_impl.h"
@@ -1664,3 +1665,177 @@ zpool_upgrade(zpool_handle_t *zhp)
return (0);
}
+
+/*
+ * Log command history.
+ *
+ * 'pool' is B_TRUE if we are logging a command for 'zpool'; B_FALSE
+ * otherwise ('zfs'). 'pool_create' is B_TRUE if we are logging the creation
+ * of the pool; B_FALSE otherwise. 'path' is the pathanme containing the
+ * poolname. 'argc' and 'argv' are used to construct the command string.
+ */
+void
+zpool_log_history(libzfs_handle_t *hdl, int argc, char **argv, const char *path,
+ boolean_t pool, boolean_t pool_create)
+{
+ char cmd_buf[HIS_MAX_RECORD_LEN];
+ char *dspath;
+ zfs_cmd_t zc = { 0 };
+ int i;
+
+ /* construct the command string */
+ (void) strcpy(cmd_buf, pool ? "zpool" : "zfs");
+ for (i = 0; i < argc; i++) {
+ if (strlen(cmd_buf) + 1 + strlen(argv[i]) > HIS_MAX_RECORD_LEN)
+ break;
+ (void) strcat(cmd_buf, " ");
+ (void) strcat(cmd_buf, argv[i]);
+ }
+
+ /* figure out the poolname */
+ dspath = strpbrk(path, "/@");
+ if (dspath == NULL) {
+ (void) strcpy(zc.zc_name, path);
+ } else {
+ (void) strncpy(zc.zc_name, path, dspath - path);
+ zc.zc_name[dspath-path] = '\0';
+ }
+
+ zc.zc_history = (uint64_t)(uintptr_t)cmd_buf;
+ zc.zc_history_len = strlen(cmd_buf);
+
+ /* overloading zc_history_offset */
+ zc.zc_history_offset = pool_create;
+
+ (void) ioctl(hdl->libzfs_fd, ZFS_IOC_POOL_LOG_HISTORY, &zc);
+}
+
+/*
+ * Perform ioctl to get some command history of a pool.
+ *
+ * 'buf' is the buffer to fill up to 'len' bytes. 'off' is the
+ * logical offset of the history buffer to start reading from.
+ *
+ * Upon return, 'off' is the next logical offset to read from and
+ * 'len' is the actual amount of bytes read into 'buf'.
+ */
+static int
+get_history(zpool_handle_t *zhp, char *buf, uint64_t *off, uint64_t *len)
+{
+ zfs_cmd_t zc = { 0 };
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+
+ zc.zc_history = (uint64_t)(uintptr_t)buf;
+ zc.zc_history_len = *len;
+ zc.zc_history_offset = *off;
+
+ if (ioctl(hdl->libzfs_fd, ZFS_IOC_POOL_GET_HISTORY, &zc) != 0) {
+ switch (errno) {
+ case EPERM:
+ return (zfs_error(hdl, EZFS_PERM, dgettext(TEXT_DOMAIN,
+ "cannot show history for pool '%s'"),
+ zhp->zpool_name));
+ case ENOENT:
+ return (zfs_error(hdl, EZFS_NOHISTORY,
+ dgettext(TEXT_DOMAIN, "cannot get history for pool "
+ "'%s'"), zhp->zpool_name));
+ default:
+ return (zpool_standard_error(hdl, errno,
+ dgettext(TEXT_DOMAIN,
+ "cannot get history for '%s'"), zhp->zpool_name));
+ }
+ }
+
+ *len = zc.zc_history_len;
+ *off = zc.zc_history_offset;
+
+ return (0);
+}
+
+/*
+ * Process the buffer of nvlists, unpacking and storing each nvlist record
+ * into 'records'. 'leftover' is set to the number of bytes that weren't
+ * processed as there wasn't a complete record.
+ */
+static int
+zpool_history_unpack(char *buf, uint64_t bytes_read, uint64_t *leftover,
+ nvlist_t ***records, uint_t *numrecords)
+{
+ uint64_t reclen;
+ nvlist_t *nv;
+ int i;
+
+ while (bytes_read > sizeof (reclen)) {
+
+ /* get length of packed record (stored as little endian) */
+ for (i = 0, reclen = 0; i < sizeof (reclen); i++)
+ reclen += (uint64_t)(((uchar_t *)buf)[i]) << (8*i);
+
+ if (bytes_read < sizeof (reclen) + reclen)
+ break;
+
+ /* unpack record */
+ if (nvlist_unpack(buf + sizeof (reclen), reclen, &nv, 0) != 0)
+ return (ENOMEM);
+ bytes_read -= sizeof (reclen) + reclen;
+ buf += sizeof (reclen) + reclen;
+
+ /* add record to nvlist array */
+ (*numrecords)++;
+ if (ISP2(*numrecords + 1)) {
+ *records = realloc(*records,
+ *numrecords * 2 * sizeof (nvlist_t *));
+ }
+ (*records)[*numrecords - 1] = nv;
+ }
+
+ *leftover = bytes_read;
+ return (0);
+}
+
+#define HIS_BUF_LEN (128*1024)
+
+/*
+ * Retrieve the command history of a pool.
+ */
+int
+zpool_get_history(zpool_handle_t *zhp, nvlist_t **nvhisp)
+{
+ char buf[HIS_BUF_LEN];
+ uint64_t off = 0;
+ nvlist_t **records = NULL;
+ uint_t numrecords = 0;
+ int err, i;
+
+ do {
+ uint64_t bytes_read = sizeof (buf);
+ uint64_t leftover;
+
+ if ((err = get_history(zhp, buf, &off, &bytes_read)) != 0)
+ break;
+
+ /* if nothing else was read in, we're at EOF, just return */
+ if (!bytes_read)
+ break;
+
+ if ((err = zpool_history_unpack(buf, bytes_read,
+ &leftover, &records, &numrecords)) != 0)
+ break;
+ off -= leftover;
+
+ /* CONSTCOND */
+ } while (1);
+
+ if (!err) {
+ verify(nvlist_alloc(nvhisp, NV_UNIQUE_NAME, 0) == 0);
+ verify(nvlist_add_nvlist_array(*nvhisp, ZPOOL_HIST_RECORD,
+ records, numrecords) == 0);
+ }
+ for (i = 0; i < numrecords; i++)
+ nvlist_free(records[i]);
+ free(records);
+
+ return (err);
+}
diff --git a/usr/src/lib/libzfs/common/libzfs_util.c b/usr/src/lib/libzfs/common/libzfs_util.c
index f2ae354427..d000f2cacc 100644
--- a/usr/src/lib/libzfs/common/libzfs_util.c
+++ b/usr/src/lib/libzfs/common/libzfs_util.c
@@ -148,6 +148,8 @@ libzfs_error_description(libzfs_handle_t *hdl)
return (dgettext(TEXT_DOMAIN, "invalid vdev configuration"));
case EZFS_RECURSIVE:
return (dgettext(TEXT_DOMAIN, "recursive dataset dependency"));
+ case EZFS_NOHISTORY:
+ return (dgettext(TEXT_DOMAIN, "no history available"));
case EZFS_UNKNOWN:
return (dgettext(TEXT_DOMAIN, "unknown error"));
default:
diff --git a/usr/src/lib/libzfs/common/mapfile-vers b/usr/src/lib/libzfs/common/mapfile-vers
index 11474a8bee..a811dae7f1 100644
--- a/usr/src/lib/libzfs/common/mapfile-vers
+++ b/usr/src/lib/libzfs/common/mapfile-vers
@@ -101,6 +101,7 @@ SUNWprivate_1.1 {
zpool_get_errlog;
zpool_get_guid;
zpool_get_handle;
+ zpool_get_history;
zpool_get_name;
zpool_get_root;
zpool_get_space_total;
@@ -112,6 +113,7 @@ SUNWprivate_1.1 {
zpool_import_status;
zpool_in_use;
zpool_iter;
+ zpool_log_history;
zpool_mount_datasets;
zpool_open;
zpool_open_canfail;
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index bf13c494df..70b93c0e10 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -893,6 +893,7 @@ ZFS_COMMON_OBJS += \
spa.o \
spa_config.o \
spa_errlog.o \
+ spa_history.o \
spa_misc.o \
space_map.o \
txg.o \
diff --git a/usr/src/uts/common/fs/zfs/dmu.c b/usr/src/uts/common/fs/zfs/dmu.c
index 25440c75b0..bdb50e6280 100644
--- a/usr/src/uts/common/fs/zfs/dmu.c
+++ b/usr/src/uts/common/fs/zfs/dmu.c
@@ -76,6 +76,8 @@ const dmu_object_type_info_t dmu_ot[DMU_OT_NUMTYPES] = {
{ byteswap_uint64_array, FALSE, "other uint64[]" },
{ zap_byteswap, TRUE, "other ZAP" },
{ zap_byteswap, TRUE, "persistent error log" },
+ { byteswap_uint8_array, TRUE, "SPA history" },
+ { byteswap_uint64_array, TRUE, "SPA history offsets" },
};
int
diff --git a/usr/src/uts/common/fs/zfs/spa.c b/usr/src/uts/common/fs/zfs/spa.c
index 646b591979..bdd6e7adf5 100644
--- a/usr/src/uts/common/fs/zfs/spa.c
+++ b/usr/src/uts/common/fs/zfs/spa.c
@@ -131,6 +131,7 @@ spa_activate(spa_t *spa)
mutex_init(&spa->spa_errlist_lock, NULL, MUTEX_DEFAULT, NULL);
mutex_init(&spa->spa_config_lock.scl_lock, NULL, MUTEX_DEFAULT, NULL);
mutex_init(&spa->spa_sync_bplist.bpl_lock, NULL, MUTEX_DEFAULT, NULL);
+ mutex_init(&spa->spa_history_lock, NULL, MUTEX_DEFAULT, NULL);
list_create(&spa->spa_dirty_list, sizeof (vdev_t),
offsetof(vdev_t, vdev_dirty_node));
@@ -598,6 +599,20 @@ spa_load(spa_t *spa, nvlist_t *config, spa_load_state_t state, int mosconfig)
}
/*
+ * Load the history object. If we have an older pool, this
+ * will not be present.
+ */
+ error = zap_lookup(spa->spa_meta_objset,
+ DMU_POOL_DIRECTORY_OBJECT, DMU_POOL_HISTORY,
+ sizeof (uint64_t), 1, &spa->spa_history);
+ if (error != 0 && error != ENOENT) {
+ vdev_set_state(rvd, B_TRUE, VDEV_STATE_CANT_OPEN,
+ VDEV_AUX_CORRUPT_DATA);
+ error = EIO;
+ goto out;
+ }
+
+ /*
* Load any hot spares for this pool.
*/
error = zap_lookup(spa->spa_meta_objset, DMU_POOL_DIRECTORY_OBJECT,
@@ -1106,6 +1121,11 @@ spa_create(const char *pool, nvlist_t *nvroot, const char *altroot)
cmn_err(CE_PANIC, "failed to add bplist");
}
+ /*
+ * Create the pool's history object.
+ */
+ spa_history_create_obj(spa, tx);
+
dmu_tx_commit(tx);
spa->spa_sync_on = B_TRUE;
@@ -2714,6 +2734,7 @@ spa_sync_spares(spa_t *spa, dmu_tx_t *tx)
}
spa_sync_nvlist(spa, spa->spa_spares_object, nvroot, tx);
+ nvlist_free(nvroot);
spa->spa_sync_spares = B_FALSE;
}
diff --git a/usr/src/uts/common/fs/zfs/spa_history.c b/usr/src/uts/common/fs/zfs/spa_history.c
new file mode 100644
index 0000000000..68b79f83d6
--- /dev/null
+++ b/usr/src/uts/common/fs/zfs/spa_history.c
@@ -0,0 +1,354 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/spa_impl.h>
+#include <sys/zap.h>
+#include <sys/dsl_synctask.h>
+
+/*
+ * Routines to manage the on-disk history log.
+ *
+ * The history log is stored as a dmu object containing
+ * <packed record length, record nvlist> tuples.
+ *
+ * Where "record nvlist" is a nvlist containing uint64_ts and strings, and
+ * "packed record length" is the packed length of the "record nvlist" stored
+ * as a little endian uint64_t.
+ *
+ * The log is implemented as a ring buffer, though the original creation
+ * of the pool ('zpool create') is never overwritten.
+ *
+ * The history log is tracked as object 'spa_t::spa_history'. The bonus buffer
+ * of 'spa_history' stores the offsets for logging/retrieving history as
+ * 'spa_history_phys_t'. 'sh_pool_create_len' is the ending offset in bytes of
+ * where the 'zpool create' record is stored. This allows us to never
+ * overwrite the original creation of the pool. 'sh_phys_max_off' is the
+ * physical ending offset in bytes of the log. This tells you the length of
+ * the buffer. 'sh_eof' is the logical EOF (in bytes). Whenever a record
+ * is added, 'sh_eof' is incremented by the the size of the record.
+ * 'sh_eof' is never decremented. 'sh_bof' is the logical BOF (in bytes).
+ * This is where the consumer should start reading from after reading in
+ * the 'zpool create' portion of the log.
+ *
+ * 'sh_records_lost' keeps track of how many records have been overwritten
+ * and permanently lost.
+ */
+
+typedef enum history_log_type {
+ LOG_CMD_CREATE,
+ LOG_CMD_NO_CREATE
+} history_log_type_t;
+
+typedef struct history_arg {
+ const char *ha_history_str;
+ history_log_type_t ha_log_type;
+} history_arg_t;
+
+/* convert a logical offset to physical */
+static uint64_t
+spa_history_log_to_phys(uint64_t log_off, spa_history_phys_t *shpp)
+{
+ uint64_t phys_len;
+
+ phys_len = shpp->sh_phys_max_off - shpp->sh_pool_create_len;
+ return ((log_off - shpp->sh_pool_create_len) % phys_len
+ + shpp->sh_pool_create_len);
+}
+
+void
+spa_history_create_obj(spa_t *spa, dmu_tx_t *tx)
+{
+ dmu_buf_t *dbp;
+ spa_history_phys_t *shpp;
+ objset_t *mos = spa->spa_meta_objset;
+
+ ASSERT(spa->spa_history == 0);
+ spa->spa_history = dmu_object_alloc(mos, DMU_OT_SPA_HISTORY,
+ SPA_MAXBLOCKSIZE, DMU_OT_SPA_HISTORY_OFFSETS,
+ sizeof (spa_history_phys_t), tx);
+
+ VERIFY(zap_add(mos, DMU_POOL_DIRECTORY_OBJECT,
+ DMU_POOL_HISTORY, sizeof (uint64_t), 1,
+ &spa->spa_history, tx) == 0);
+
+ VERIFY(0 == dmu_bonus_hold(mos, spa->spa_history, FTAG, &dbp));
+ ASSERT(dbp->db_size >= sizeof (spa_history_phys_t));
+
+ shpp = dbp->db_data;
+ dmu_buf_will_dirty(dbp, tx);
+
+ /*
+ * Figure out maximum size of history log. We set it at
+ * 1% of pool size, with a max of 32MB and min of 128KB.
+ */
+ shpp->sh_phys_max_off = spa_get_dspace(spa) / 100;
+ shpp->sh_phys_max_off = MIN(shpp->sh_phys_max_off, 32<<20);
+ shpp->sh_phys_max_off = MAX(shpp->sh_phys_max_off, 128<<10);
+
+ dmu_buf_rele(dbp, FTAG);
+}
+
+/*
+ * Change 'sh_bof' to the beginning of the next record.
+ */
+static int
+spa_history_advance_bof(spa_t *spa, spa_history_phys_t *shpp)
+{
+ objset_t *mos = spa->spa_meta_objset;
+ uint64_t firstread, reclen, phys_bof;
+ char buf[sizeof (reclen)];
+ int err;
+
+ phys_bof = spa_history_log_to_phys(shpp->sh_bof, shpp);
+ firstread = MIN(sizeof (reclen), shpp->sh_phys_max_off - phys_bof);
+
+ if ((err = dmu_read(mos, spa->spa_history, phys_bof, firstread,
+ buf)) != 0)
+ return (err);
+ if (firstread != sizeof (reclen)) {
+ if ((err = dmu_read(mos, spa->spa_history,
+ shpp->sh_pool_create_len, sizeof (reclen) - firstread,
+ buf + firstread)) != 0)
+ return (err);
+ }
+
+ reclen = LE_64(*((uint64_t *)buf));
+ shpp->sh_bof += reclen + sizeof (reclen);
+ shpp->sh_records_lost++;
+ return (0);
+}
+
+static int
+spa_history_write(spa_t *spa, void *buf, uint64_t len, spa_history_phys_t *shpp,
+ dmu_tx_t *tx)
+{
+ uint64_t firstwrite, phys_eof;
+ objset_t *mos = spa->spa_meta_objset;
+ int err;
+
+ ASSERT(MUTEX_HELD(&spa->spa_history_lock));
+
+ /* see if we need to reset logical BOF */
+ while (shpp->sh_phys_max_off - shpp->sh_pool_create_len -
+ (shpp->sh_eof - shpp->sh_bof) <= len) {
+ if ((err = spa_history_advance_bof(spa, shpp)) != 0)
+ return (err);
+ }
+
+ phys_eof = spa_history_log_to_phys(shpp->sh_eof, shpp);
+ firstwrite = MIN(len, shpp->sh_phys_max_off - phys_eof);
+ shpp->sh_eof += len;
+ dmu_write(mos, spa->spa_history, phys_eof, firstwrite, buf, tx);
+
+ len -= firstwrite;
+ if (len > 0) {
+ /* write out the rest at the beginning of physical file */
+ dmu_write(mos, spa->spa_history, shpp->sh_pool_create_len,
+ len, (char *)buf + firstwrite, tx);
+ }
+
+ return (0);
+}
+
+/*
+ * Write out a history event.
+ */
+void
+spa_history_log_sync(void *arg1, void *arg2, dmu_tx_t *tx)
+{
+ spa_t *spa = arg1;
+ history_arg_t *hap = arg2;
+ const char *history_str = hap->ha_history_str;
+ objset_t *mos = spa->spa_meta_objset;
+ dmu_buf_t *dbp;
+ spa_history_phys_t *shpp;
+ size_t reclen;
+ uint64_t le_len;
+ nvlist_t *nvrecord;
+ char *record_packed = NULL;
+ int ret;
+
+ if (history_str == NULL)
+ return;
+
+ /*
+ * If we have an older pool that doesn't have a command
+ * history object, create it now.
+ */
+ mutex_enter(&spa->spa_history_lock);
+ if (!spa->spa_history)
+ spa_history_create_obj(spa, tx);
+ mutex_exit(&spa->spa_history_lock);
+
+ /*
+ * Get the offset of where we need to write via the bonus buffer.
+ * Update the offset when the write completes.
+ */
+ VERIFY(0 == dmu_bonus_hold(mos, spa->spa_history, FTAG, &dbp));
+ shpp = dbp->db_data;
+
+ dmu_buf_will_dirty(dbp, tx);
+
+#ifdef ZFS_DEBUG
+ {
+ dmu_object_info_t doi;
+ dmu_object_info_from_db(dbp, &doi);
+ ASSERT3U(doi.doi_bonus_type, ==, DMU_OT_SPA_HISTORY_OFFSETS);
+ }
+#endif
+
+ /* construct a nvlist of the current time and cmd string */
+ VERIFY(nvlist_alloc(&nvrecord, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+ VERIFY(nvlist_add_uint64(nvrecord, ZPOOL_HIST_TIME,
+ gethrestime_sec()) == 0);
+ VERIFY(nvlist_add_string(nvrecord, ZPOOL_HIST_CMD, history_str) == 0);
+ VERIFY(nvlist_pack(nvrecord, &record_packed, &reclen,
+ NV_ENCODE_XDR, KM_SLEEP) == 0);
+
+ mutex_enter(&spa->spa_history_lock);
+ if (hap->ha_log_type == LOG_CMD_CREATE)
+ VERIFY(shpp->sh_eof == shpp->sh_pool_create_len);
+
+ /* write out the packed length as little endian */
+ le_len = LE_64(reclen);
+ ret = spa_history_write(spa, &le_len, sizeof (le_len), shpp, tx);
+ if (!ret)
+ ret = spa_history_write(spa, record_packed, reclen, shpp, tx);
+
+ if (!ret && hap->ha_log_type == LOG_CMD_CREATE) {
+ shpp->sh_pool_create_len += sizeof (le_len) + reclen;
+ shpp->sh_bof = shpp->sh_pool_create_len;
+ }
+
+ mutex_exit(&spa->spa_history_lock);
+ nvlist_free(nvrecord);
+ kmem_free(record_packed, reclen);
+ dmu_buf_rele(dbp, FTAG);
+}
+
+/*
+ * Write out a history event.
+ */
+int
+spa_history_log(spa_t *spa, const char *history_str, uint64_t pool_create)
+{
+ history_arg_t ha;
+
+ ha.ha_history_str = history_str;
+ ha.ha_log_type = pool_create ? LOG_CMD_CREATE : LOG_CMD_NO_CREATE;
+ return (dsl_sync_task_do(spa_get_dsl(spa), NULL, spa_history_log_sync,
+ spa, &ha, 0));
+}
+
+/*
+ * Read out the command history.
+ */
+int
+spa_history_get(spa_t *spa, uint64_t *offp, uint64_t *len, char *buf)
+{
+ objset_t *mos = spa->spa_meta_objset;
+ dmu_buf_t *dbp;
+ uint64_t read_len, phys_read_off, phys_eof;
+ uint64_t leftover = 0;
+ spa_history_phys_t *shpp;
+ int err;
+
+ /*
+ * If the command history doesn't exist (older pool),
+ * that's ok, just return ENOENT.
+ */
+ if (!spa->spa_history)
+ return (ENOENT);
+
+ if ((err = dmu_bonus_hold(mos, spa->spa_history, FTAG, &dbp)) != 0)
+ return (err);
+ shpp = dbp->db_data;
+
+#ifdef ZFS_DEBUG
+ {
+ dmu_object_info_t doi;
+ dmu_object_info_from_db(dbp, &doi);
+ ASSERT3U(doi.doi_bonus_type, ==, DMU_OT_SPA_HISTORY_OFFSETS);
+ }
+#endif
+
+ mutex_enter(&spa->spa_history_lock);
+ phys_eof = spa_history_log_to_phys(shpp->sh_eof, shpp);
+
+ if (*offp < shpp->sh_pool_create_len) {
+ /* read in just the zpool create history */
+ phys_read_off = *offp;
+ read_len = MIN(*len, shpp->sh_pool_create_len -
+ phys_read_off);
+ } else {
+ /*
+ * Need to reset passed in offset to BOF if the passed in
+ * offset has since been overwritten.
+ */
+ *offp = MAX(*offp, shpp->sh_bof);
+ phys_read_off = spa_history_log_to_phys(*offp, shpp);
+
+ /*
+ * Read up to the minimum of what the user passed down or
+ * the EOF (physical or logical). If we hit physical EOF,
+ * use 'leftover' to read from the physical BOF.
+ */
+ if (phys_read_off <= phys_eof) {
+ read_len = MIN(*len, phys_eof - phys_read_off);
+ } else {
+ read_len = MIN(*len,
+ shpp->sh_phys_max_off - phys_read_off);
+ if (phys_read_off + *len > shpp->sh_phys_max_off) {
+ leftover = MIN(*len - read_len,
+ phys_eof - shpp->sh_pool_create_len);
+ }
+ }
+ }
+
+ /* offset for consumer to use next */
+ *offp += read_len + leftover;
+
+ /* tell the consumer how much you actually read */
+ *len = read_len + leftover;
+
+ if (read_len == 0) {
+ mutex_exit(&spa->spa_history_lock);
+ dmu_buf_rele(dbp, FTAG);
+ return (0);
+ }
+
+ err = dmu_read(mos, spa->spa_history, phys_read_off, read_len, buf);
+ if (leftover && err == 0) {
+ err = dmu_read(mos, spa->spa_history, shpp->sh_pool_create_len,
+ leftover, buf + read_len);
+ }
+ mutex_exit(&spa->spa_history_lock);
+
+ dmu_buf_rele(dbp, FTAG);
+ return (err);
+}
diff --git a/usr/src/uts/common/fs/zfs/spa_misc.c b/usr/src/uts/common/fs/zfs/spa_misc.c
index 5af23c0f13..b36a18e687 100644
--- a/usr/src/uts/common/fs/zfs/spa_misc.c
+++ b/usr/src/uts/common/fs/zfs/spa_misc.c
@@ -283,6 +283,7 @@ spa_remove(spa_t *spa)
mutex_destroy(&spa->spa_scrub_lock);
mutex_destroy(&spa->spa_config_cache_lock);
mutex_destroy(&spa->spa_async_lock);
+ mutex_destroy(&spa->spa_history_lock);
kmem_free(spa, sizeof (spa_t));
}
diff --git a/usr/src/uts/common/fs/zfs/sys/dmu.h b/usr/src/uts/common/fs/zfs/sys/dmu.h
index 8f025eea66..24989aef04 100644
--- a/usr/src/uts/common/fs/zfs/sys/dmu.h
+++ b/usr/src/uts/common/fs/zfs/sys/dmu.h
@@ -104,6 +104,8 @@ typedef enum dmu_object_type {
DMU_OT_ZAP_OTHER, /* ZAP */
/* new object types: */
DMU_OT_ERROR_LOG, /* ZAP */
+ DMU_OT_SPA_HISTORY, /* UINT8 */
+ DMU_OT_SPA_HISTORY_OFFSETS, /* spa_his_phys_t */
DMU_OT_NUMTYPES
} dmu_object_type_t;
@@ -190,6 +192,7 @@ typedef void dmu_byteswap_func_t(void *buf, size_t size);
#define DMU_POOL_ERRLOG_LAST "errlog_last"
#define DMU_POOL_SPARES "spares"
#define DMU_POOL_DEFLATE "deflate"
+#define DMU_POOL_HISTORY "history"
/*
* Allocate an object from this objset. The range of object numbers
diff --git a/usr/src/uts/common/fs/zfs/sys/spa.h b/usr/src/uts/common/fs/zfs/sys/spa.h
index a6c05e6d3c..f92dc03da6 100644
--- a/usr/src/uts/common/fs/zfs/sys/spa.h
+++ b/usr/src/uts/common/fs/zfs/sys/spa.h
@@ -427,6 +427,13 @@ extern vdev_t *spa_lookup_by_guid(spa_t *spa, uint64_t guid);
extern boolean_t spa_has_spare(spa_t *, uint64_t guid);
extern uint64_t bp_get_dasize(spa_t *spa, const blkptr_t *bp);
+/* history logging */
+extern void spa_history_create_obj(spa_t *spa, dmu_tx_t *tx);
+extern int spa_history_get(spa_t *spa, uint64_t *offset, uint64_t *len_read,
+ char *his_buf);
+extern int spa_history_log(spa_t *spa, const char *his_buf,
+ uint64_t pool_create);
+
/* error handling */
struct zbookmark;
struct zio;
diff --git a/usr/src/uts/common/fs/zfs/sys/spa_impl.h b/usr/src/uts/common/fs/zfs/sys/spa_impl.h
index 64d3be978f..285c8cc7c4 100644
--- a/usr/src/uts/common/fs/zfs/sys/spa_impl.h
+++ b/usr/src/uts/common/fs/zfs/sys/spa_impl.h
@@ -56,6 +56,14 @@ typedef struct spa_error_entry {
avl_node_t se_avl;
} spa_error_entry_t;
+typedef struct spa_history_phys {
+ uint64_t sh_pool_create_len; /* ending offset of zpool create */
+ uint64_t sh_phys_max_off; /* physical EOF */
+ uint64_t sh_bof; /* logical BOF */
+ uint64_t sh_eof; /* logical EOF */
+ uint64_t sh_records_lost; /* num of records overwritten */
+} spa_history_phys_t;
+
struct spa {
/*
* Fields protected by spa_namespace_lock.
@@ -128,6 +136,8 @@ struct spa {
avl_tree_t spa_errlist_last; /* last error list */
avl_tree_t spa_errlist_scrub; /* scrub error list */
uint64_t spa_deflate; /* should we deflate? */
+ uint64_t spa_history; /* history object */
+ kmutex_t spa_history_lock; /* history lock */
/*
* spa_refcnt must be the last element because it changes size based on
* compilation options. In order for the MDB module to function
diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h
index 37c2fc55e8..92cd0e59ef 100644
--- a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h
+++ b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h
@@ -127,6 +127,9 @@ typedef struct zfs_cmd {
uint64_t zc_cred;
uint64_t zc_dev;
uint64_t zc_objset_type;
+ uint64_t zc_history; /* really (char *) */
+ uint64_t zc_history_len;
+ uint64_t zc_history_offset;
dmu_objset_stats_t zc_objset_stats;
struct drr_begin zc_begin_record;
zinject_record_t zc_inject_record;
diff --git a/usr/src/uts/common/fs/zfs/zfs_ioctl.c b/usr/src/uts/common/fs/zfs/zfs_ioctl.c
index 14635320a7..8926563a4c 100644
--- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c
+++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c
@@ -434,11 +434,13 @@ zfs_ioc_pool_scrub(zfs_cmd_t *zc)
spa_t *spa;
int error;
- error = spa_open(zc->zc_name, &spa, FTAG);
- if (error == 0) {
- error = spa_scrub(spa, zc->zc_cookie, B_FALSE);
- spa_close(spa, FTAG);
- }
+ if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0)
+ return (error);
+
+ error = spa_scrub(spa, zc->zc_cookie, B_FALSE);
+
+ spa_close(spa, FTAG);
+
return (error);
}
@@ -462,11 +464,73 @@ zfs_ioc_pool_upgrade(zfs_cmd_t *zc)
spa_t *spa;
int error;
- error = spa_open(zc->zc_name, &spa, FTAG);
- if (error == 0) {
- spa_upgrade(spa);
+ if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0)
+ return (error);
+
+ spa_upgrade(spa);
+
+ spa_close(spa, FTAG);
+
+ return (error);
+}
+
+static int
+zfs_ioc_pool_get_history(zfs_cmd_t *zc)
+{
+ spa_t *spa;
+ char *hist_buf;
+ uint64_t size;
+ int error;
+
+ if ((size = zc->zc_history_len) == 0)
+ return (EINVAL);
+
+ if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0)
+ return (error);
+
+ hist_buf = kmem_alloc(size, KM_SLEEP);
+ if ((error = spa_history_get(spa, &zc->zc_history_offset,
+ &zc->zc_history_len, hist_buf)) == 0) {
+ error = xcopyout(hist_buf, (char *)(uintptr_t)zc->zc_history,
+ zc->zc_history_len);
+ }
+
+ spa_close(spa, FTAG);
+ kmem_free(hist_buf, size);
+ return (error);
+}
+
+static int
+zfs_ioc_pool_log_history(zfs_cmd_t *zc)
+{
+ spa_t *spa;
+ char *history_str = NULL;
+ size_t size;
+ int error;
+
+ size = zc->zc_history_len;
+ if (size == 0 || size > HIS_MAX_RECORD_LEN)
+ return (EINVAL);
+
+ if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0)
+ return (error);
+
+ /* add one for the NULL delimiter */
+ size++;
+ history_str = kmem_alloc(size, KM_SLEEP);
+ if ((error = xcopyin((void *)(uintptr_t)zc->zc_history, history_str,
+ size)) != 0) {
spa_close(spa, FTAG);
+ kmem_free(history_str, size);
+ return (error);
}
+ history_str[size - 1] = '\0';
+
+ error = spa_history_log(spa, history_str, zc->zc_history_offset);
+
+ spa_close(spa, FTAG);
+ kmem_free(history_str, size);
+
return (error);
}
@@ -510,8 +574,7 @@ zfs_ioc_vdev_online(zfs_cmd_t *zc)
spa_t *spa;
int error;
- error = spa_open(zc->zc_name, &spa, FTAG);
- if (error != 0)
+ if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0)
return (error);
error = vdev_online(spa, zc->zc_guid);
spa_close(spa, FTAG);
@@ -525,8 +588,7 @@ zfs_ioc_vdev_offline(zfs_cmd_t *zc)
int istmp = zc->zc_cookie;
int error;
- error = spa_open(zc->zc_name, &spa, FTAG);
- if (error != 0)
+ if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0)
return (error);
error = vdev_offline(spa, zc->zc_guid, istmp);
spa_close(spa, FTAG);
@@ -541,8 +603,7 @@ zfs_ioc_vdev_attach(zfs_cmd_t *zc)
nvlist_t *config;
int error;
- error = spa_open(zc->zc_name, &spa, FTAG);
- if (error != 0)
+ if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0)
return (error);
if ((error = get_nvlist(zc, &config)) == 0) {
@@ -560,8 +621,7 @@ zfs_ioc_vdev_detach(zfs_cmd_t *zc)
spa_t *spa;
int error;
- error = spa_open(zc->zc_name, &spa, FTAG);
- if (error != 0)
+ if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0)
return (error);
error = spa_vdev_detach(spa, zc->zc_guid, B_FALSE);
@@ -583,7 +643,6 @@ zfs_ioc_vdev_setpath(zfs_cmd_t *zc)
return (error);
error = spa_vdev_setpath(spa, guid, path);
-
spa_close(spa, FTAG);
return (error);
}
@@ -1358,6 +1417,8 @@ static zfs_ioc_vec_t zfs_ioc_vec[] = {
{ zfs_ioc_pool_scrub, zfs_secpolicy_config, pool_name },
{ zfs_ioc_pool_freeze, zfs_secpolicy_config, no_name },
{ zfs_ioc_pool_upgrade, zfs_secpolicy_config, pool_name },
+ { zfs_ioc_pool_get_history, zfs_secpolicy_config, pool_name },
+ { zfs_ioc_pool_log_history, zfs_secpolicy_config, pool_name },
{ zfs_ioc_vdev_add, zfs_secpolicy_config, pool_name },
{ zfs_ioc_vdev_remove, zfs_secpolicy_config, pool_name },
{ zfs_ioc_vdev_online, zfs_secpolicy_config, pool_name },
diff --git a/usr/src/uts/common/sys/fs/zfs.h b/usr/src/uts/common/sys/fs/zfs.h
index 06acdede7a..9305192a81 100644
--- a/usr/src/uts/common/sys/fs/zfs.h
+++ b/usr/src/uts/common/sys/fs/zfs.h
@@ -327,6 +327,8 @@ typedef enum zfs_ioc {
ZFS_IOC_POOL_SCRUB,
ZFS_IOC_POOL_FREEZE,
ZFS_IOC_POOL_UPGRADE,
+ ZFS_IOC_POOL_GET_HISTORY,
+ ZFS_IOC_POOL_LOG_HISTORY,
ZFS_IOC_VDEV_ADD,
ZFS_IOC_VDEV_REMOVE,
ZFS_IOC_VDEV_ONLINE,
@@ -374,6 +376,16 @@ typedef enum {
#define ZPOOL_ERR_OBJECT "object"
#define ZPOOL_ERR_RANGE "range"
+#define HIS_MAX_RECORD_LEN (MAXPATHLEN + MAXPATHLEN + 1)
+
+/*
+ * The following are names used in the nvlist describing
+ * the pool's history log.
+ */
+#define ZPOOL_HIST_RECORD "history record"
+#define ZPOOL_HIST_TIME "history time"
+#define ZPOOL_HIST_CMD "history command"
+
#ifdef __cplusplus
}
#endif