PSARC 2006/288 zpool history
authorek110237
Mon, 16 Oct 2006 10:35:01 -0700
changeset 2926 acfcfefbc60d
parent 2925 0fdde927d023
child 2927 24b93d531b71
PSARC 2006/288 zpool history 6343741 want to store a command history on disk 6476196 spa_sync_spares() is missing a nvlist_free()
usr/src/cmd/truss/codes.c
usr/src/cmd/zdb/zdb.c
usr/src/cmd/zfs/zfs_main.c
usr/src/cmd/zpool/zpool_main.c
usr/src/lib/libzfs/common/libzfs.h
usr/src/lib/libzfs/common/libzfs_pool.c
usr/src/lib/libzfs/common/libzfs_util.c
usr/src/lib/libzfs/common/mapfile-vers
usr/src/uts/common/Makefile.files
usr/src/uts/common/fs/zfs/dmu.c
usr/src/uts/common/fs/zfs/spa.c
usr/src/uts/common/fs/zfs/spa_history.c
usr/src/uts/common/fs/zfs/spa_misc.c
usr/src/uts/common/fs/zfs/sys/dmu.h
usr/src/uts/common/fs/zfs/sys/spa.h
usr/src/uts/common/fs/zfs/sys/spa_impl.h
usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h
usr/src/uts/common/fs/zfs/zfs_ioctl.c
usr/src/uts/common/sys/fs/zfs.h
--- a/usr/src/cmd/truss/codes.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/cmd/truss/codes.c	Mon Oct 16 10:35:01 2006 -0700
@@ -854,6 +854,10 @@
 		"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",
--- a/usr/src/cmd/zdb/zdb.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/cmd/zdb/zdb.c	Mon Oct 16 10:35:01 2006 -0700
@@ -933,6 +933,8 @@
 	dump_uint64,		/* other uint64[]		*/
 	dump_zap,		/* other ZAP			*/
 	dump_zap,		/* persistent error log		*/
+	dump_uint8,		/* SPA history			*/
+	dump_uint64,		/* SPA history offsets		*/
 };
 
 static void
--- a/usr/src/cmd/zfs/zfs_main.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/cmd/zfs/zfs_main.c	Mon Oct 16 10:35:01 2006 -0700
@@ -380,6 +380,7 @@
 				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 @@
 	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 @@
 	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 @@
 	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 @@
  * 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 @@
 	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 @@
 		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 @@
 
 	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 @@
 
 	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 @@
 	 */
 	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 @@
 typedef struct set_cbdata {
 	char		*cb_propname;
 	char		*cb_value;
+	boolean_t	cb_any_successful;
 } set_cbdata_t;
 
 static int
@@ -1883,6 +1926,7 @@
 		}
 		return (1);
 	}
+	cbp->cb_any_successful = B_TRUE;
 	return (0);
 }
 
@@ -1890,6 +1934,7 @@
 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 @@
 
 	*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 @@
 		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 @@
 	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 @@
 	}
 
 	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);
 }
 
--- a/usr/src/cmd/zpool/zpool_main.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/cmd/zpool/zpool_main.c	Mon Oct 16 10:35:01 2006 -0700
@@ -73,6 +73,8 @@
 
 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 @@
 	HELP_DESTROY,
 	HELP_DETACH,
 	HELP_EXPORT,
+	HELP_HISTORY,
 	HELP_IMPORT,
 	HELP_IOSTAT,
 	HELP_LIST,
@@ -148,7 +151,9 @@
 	{ 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 @@
 		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 @@
 		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 @@
 		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 @@
 					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 @@
 		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 @@
 			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 @@
  */
 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 @@
 	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 @@
 
 			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 @@
 			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_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 @@
 		}
 	}
 
+	log_argc = argc;
+	log_argv = argv;
 	argc -= optind;
 	argv += optind;
 
@@ -2064,6 +2095,11 @@
 
 	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 @@
 
 	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 @@
 		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 @@
 		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 @@
 	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 @@
 
 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 @@
 		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 @@
 		}
 	}
 
+	cb.cb_argc = argc;
+	cb.cb_argv = argv;
 	argc -= optind;
 	argv += optind;
 
@@ -2936,6 +2998,8 @@
 	int	cb_all;
 	int	cb_first;
 	int	cb_newer;
+	int	cb_argc;
+	char	**cb_argv;
 } upgrade_cbdata_t;
 
 static int
@@ -2968,9 +3032,13 @@
 		} 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 @@
 
 /* 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 @@
 	}
 
 	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 @@
 		}
 	}
 
+	cb.cb_argc = argc;
+	cb.cb_argv = argv;
 	argc -= optind;
 	argv += optind;
 
@@ -3116,7 +3191,73 @@
 				    "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);
--- a/usr/src/lib/libzfs/common/libzfs.h	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/lib/libzfs/common/libzfs.h	Mon Oct 16 10:35:01 2006 -0700
@@ -90,6 +90,7 @@
 	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 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
--- a/usr/src/lib/libzfs/common/libzfs_pool.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/lib/libzfs/common/libzfs_pool.c	Mon Oct 16 10:35:01 2006 -0700
@@ -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 @@
 
 	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);
+}
--- a/usr/src/lib/libzfs/common/libzfs_util.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/lib/libzfs/common/libzfs_util.c	Mon Oct 16 10:35:01 2006 -0700
@@ -148,6 +148,8 @@
 		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:
--- a/usr/src/lib/libzfs/common/mapfile-vers	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/lib/libzfs/common/mapfile-vers	Mon Oct 16 10:35:01 2006 -0700
@@ -101,6 +101,7 @@
 	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 @@
 	zpool_import_status;
 	zpool_in_use;
 	zpool_iter;
+	zpool_log_history;
 	zpool_mount_datasets;
 	zpool_open;
 	zpool_open_canfail;
--- a/usr/src/uts/common/Makefile.files	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/Makefile.files	Mon Oct 16 10:35:01 2006 -0700
@@ -893,6 +893,7 @@
 	spa.o			\
 	spa_config.o		\
 	spa_errlog.o		\
+	spa_history.o		\
 	spa_misc.o		\
 	space_map.o		\
 	txg.o			\
--- a/usr/src/uts/common/fs/zfs/dmu.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/fs/zfs/dmu.c	Mon Oct 16 10:35:01 2006 -0700
@@ -76,6 +76,8 @@
 	{	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
--- a/usr/src/uts/common/fs/zfs/spa.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/fs/zfs/spa.c	Mon Oct 16 10:35:01 2006 -0700
@@ -131,6 +131,7 @@
 	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 @@
 	}
 
 	/*
+	 * 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 @@
 		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_nvlist(spa, spa->spa_spares_object, nvroot, tx);
+	nvlist_free(nvroot);
 
 	spa->spa_sync_spares = B_FALSE;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/fs/zfs/spa_history.c	Mon Oct 16 10:35:01 2006 -0700
@@ -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);
+}
--- a/usr/src/uts/common/fs/zfs/spa_misc.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/fs/zfs/spa_misc.c	Mon Oct 16 10:35:01 2006 -0700
@@ -283,6 +283,7 @@
 	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));
 }
--- a/usr/src/uts/common/fs/zfs/sys/dmu.h	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dmu.h	Mon Oct 16 10:35:01 2006 -0700
@@ -104,6 +104,8 @@
 	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 @@
 #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
--- a/usr/src/uts/common/fs/zfs/sys/spa.h	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/spa.h	Mon Oct 16 10:35:01 2006 -0700
@@ -427,6 +427,13 @@
 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;
--- a/usr/src/uts/common/fs/zfs/sys/spa_impl.h	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/spa_impl.h	Mon Oct 16 10:35:01 2006 -0700
@@ -56,6 +56,14 @@
 	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 @@
 	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
--- a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h	Mon Oct 16 10:35:01 2006 -0700
@@ -127,6 +127,9 @@
 	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;
--- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Mon Oct 16 10:35:01 2006 -0700
@@ -434,11 +434,13 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 		return (error);
 
 	error = spa_vdev_setpath(spa, guid, path);
-
 	spa_close(spa, FTAG);
 	return (error);
 }
@@ -1358,6 +1417,8 @@
 	{ 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 },
--- a/usr/src/uts/common/sys/fs/zfs.h	Mon Oct 16 10:09:13 2006 -0700
+++ b/usr/src/uts/common/sys/fs/zfs.h	Mon Oct 16 10:35:01 2006 -0700
@@ -327,6 +327,8 @@
 	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 @@
 #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