diff options
author | ek110237 <none@none> | 2006-10-16 10:35:01 -0700 |
---|---|---|
committer | ek110237 <none@none> | 2006-10-16 10:35:01 -0700 |
commit | 06eeb2ad640ce72d394ac521094bed7681044408 (patch) | |
tree | d955b15196c4d84d42b931f476fe38efa1db115c /usr | |
parent | 4730c9c455fdcb7ffe43b6f08727468d499ca837 (diff) | |
download | illumos-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.c | 4 | ||||
-rw-r--r-- | usr/src/cmd/zdb/zdb.c | 2 | ||||
-rw-r--r-- | usr/src/cmd/zfs/zfs_main.c | 92 | ||||
-rw-r--r-- | usr/src/cmd/zpool/zpool_main.c | 159 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs.h | 4 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_pool.c | 175 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_util.c | 2 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/mapfile-vers | 2 | ||||
-rw-r--r-- | usr/src/uts/common/Makefile.files | 1 | ||||
-rw-r--r-- | usr/src/uts/common/fs/zfs/dmu.c | 2 | ||||
-rw-r--r-- | usr/src/uts/common/fs/zfs/spa.c | 21 | ||||
-rw-r--r-- | usr/src/uts/common/fs/zfs/spa_history.c | 354 | ||||
-rw-r--r-- | usr/src/uts/common/fs/zfs/spa_misc.c | 1 | ||||
-rw-r--r-- | usr/src/uts/common/fs/zfs/sys/dmu.h | 3 | ||||
-rw-r--r-- | usr/src/uts/common/fs/zfs/sys/spa.h | 7 | ||||
-rw-r--r-- | usr/src/uts/common/fs/zfs/sys/spa_impl.h | 10 | ||||
-rw-r--r-- | usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h | 3 | ||||
-rw-r--r-- | usr/src/uts/common/fs/zfs/zfs_ioctl.c | 95 | ||||
-rw-r--r-- | usr/src/uts/common/sys/fs/zfs.h | 12 |
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 |