zhack.c revision 307122
1/* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22/* 23 * Copyright (c) 2011, 2015 by Delphix. All rights reserved. 24 * Copyright (c) 2013 Steven Hartland. All rights reserved. 25 */ 26 27/* 28 * zhack is a debugging tool that can write changes to ZFS pool using libzpool 29 * for testing purposes. Altering pools with zhack is unsupported and may 30 * result in corrupted pools. 31 */ 32 33#include <stdio.h> 34#include <stdlib.h> 35#include <ctype.h> 36#include <sys/zfs_context.h> 37#include <sys/spa.h> 38#include <sys/spa_impl.h> 39#include <sys/dmu.h> 40#include <sys/zap.h> 41#include <sys/zfs_znode.h> 42#include <sys/dsl_synctask.h> 43#include <sys/vdev.h> 44#include <sys/fs/zfs.h> 45#include <sys/dmu_objset.h> 46#include <sys/dsl_pool.h> 47#include <sys/zio_checksum.h> 48#include <sys/zio_compress.h> 49#include <sys/zfeature.h> 50#include <sys/dmu_tx.h> 51#undef verify 52#include <libzfs.h> 53 54extern boolean_t zfeature_checks_disable; 55 56const char cmdname[] = "zhack"; 57libzfs_handle_t *g_zfs; 58static importargs_t g_importargs; 59static char *g_pool; 60static boolean_t g_readonly; 61 62static void 63usage(void) 64{ 65 (void) fprintf(stderr, 66 "Usage: %s [-c cachefile] [-d dir] <subcommand> <args> ...\n" 67 "where <subcommand> <args> is one of the following:\n" 68 "\n", cmdname); 69 70 (void) fprintf(stderr, 71 " feature stat <pool>\n" 72 " print information about enabled features\n" 73 " feature enable [-d desc] <pool> <feature>\n" 74 " add a new enabled feature to the pool\n" 75 " -d <desc> sets the feature's description\n" 76 " feature ref [-md] <pool> <feature>\n" 77 " change the refcount on the given feature\n" 78 " -d decrease instead of increase the refcount\n" 79 " -m add the feature to the label if increasing refcount\n" 80 "\n" 81 " <feature> : should be a feature guid\n"); 82 exit(1); 83} 84 85 86static void 87fatal(spa_t *spa, void *tag, const char *fmt, ...) 88{ 89 va_list ap; 90 91 if (spa != NULL) { 92 spa_close(spa, tag); 93 (void) spa_export(g_pool, NULL, B_TRUE, B_FALSE); 94 } 95 96 va_start(ap, fmt); 97 (void) fprintf(stderr, "%s: ", cmdname); 98 (void) vfprintf(stderr, fmt, ap); 99 va_end(ap); 100 (void) fprintf(stderr, "\n"); 101 102 exit(1); 103} 104 105/* ARGSUSED */ 106static int 107space_delta_cb(dmu_object_type_t bonustype, void *data, 108 uint64_t *userp, uint64_t *groupp) 109{ 110 /* 111 * Is it a valid type of object to track? 112 */ 113 if (bonustype != DMU_OT_ZNODE && bonustype != DMU_OT_SA) 114 return (ENOENT); 115 (void) fprintf(stderr, "modifying object that needs user accounting"); 116 abort(); 117 /* NOTREACHED */ 118} 119 120/* 121 * Target is the dataset whose pool we want to open. 122 */ 123static void 124import_pool(const char *target, boolean_t readonly) 125{ 126 nvlist_t *config; 127 nvlist_t *pools; 128 int error; 129 char *sepp; 130 spa_t *spa; 131 nvpair_t *elem; 132 nvlist_t *props; 133 const char *name; 134 135 kernel_init(readonly ? FREAD : (FREAD | FWRITE)); 136 g_zfs = libzfs_init(); 137 ASSERT(g_zfs != NULL); 138 139 dmu_objset_register_type(DMU_OST_ZFS, space_delta_cb); 140 141 g_readonly = readonly; 142 143 /* 144 * If we only want readonly access, it's OK if we find 145 * a potentially-active (ie, imported into the kernel) pool from the 146 * default cachefile. 147 */ 148 if (readonly && spa_open(target, &spa, FTAG) == 0) { 149 spa_close(spa, FTAG); 150 return; 151 } 152 153 g_importargs.unique = B_TRUE; 154 g_importargs.can_be_active = readonly; 155 g_pool = strdup(target); 156 if ((sepp = strpbrk(g_pool, "/@")) != NULL) 157 *sepp = '\0'; 158 g_importargs.poolname = g_pool; 159 pools = zpool_search_import(g_zfs, &g_importargs); 160 161 if (nvlist_empty(pools)) { 162 if (!g_importargs.can_be_active) { 163 g_importargs.can_be_active = B_TRUE; 164 if (zpool_search_import(g_zfs, &g_importargs) != NULL || 165 spa_open(target, &spa, FTAG) == 0) { 166 fatal(spa, FTAG, "cannot import '%s': pool is " 167 "active; run " "\"zpool export %s\" " 168 "first\n", g_pool, g_pool); 169 } 170 } 171 172 fatal(NULL, FTAG, "cannot import '%s': no such pool " 173 "available\n", g_pool); 174 } 175 176 elem = nvlist_next_nvpair(pools, NULL); 177 name = nvpair_name(elem); 178 verify(nvpair_value_nvlist(elem, &config) == 0); 179 180 props = NULL; 181 if (readonly) { 182 verify(nvlist_alloc(&props, NV_UNIQUE_NAME, 0) == 0); 183 verify(nvlist_add_uint64(props, 184 zpool_prop_to_name(ZPOOL_PROP_READONLY), 1) == 0); 185 } 186 187 zfeature_checks_disable = B_TRUE; 188 error = spa_import(name, config, props, ZFS_IMPORT_NORMAL); 189 zfeature_checks_disable = B_FALSE; 190 if (error == EEXIST) 191 error = 0; 192 193 if (error) 194 fatal(NULL, FTAG, "can't import '%s': %s", name, 195 strerror(error)); 196} 197 198static void 199zhack_spa_open(const char *target, boolean_t readonly, void *tag, spa_t **spa) 200{ 201 int err; 202 203 import_pool(target, readonly); 204 205 zfeature_checks_disable = B_TRUE; 206 err = spa_open(target, spa, tag); 207 zfeature_checks_disable = B_FALSE; 208 209 if (err != 0) 210 fatal(*spa, FTAG, "cannot open '%s': %s", target, 211 strerror(err)); 212 if (spa_version(*spa) < SPA_VERSION_FEATURES) { 213 fatal(*spa, FTAG, "'%s' has version %d, features not enabled", 214 target, (int)spa_version(*spa)); 215 } 216} 217 218static void 219dump_obj(objset_t *os, uint64_t obj, const char *name) 220{ 221 zap_cursor_t zc; 222 zap_attribute_t za; 223 224 (void) printf("%s_obj:\n", name); 225 226 for (zap_cursor_init(&zc, os, obj); 227 zap_cursor_retrieve(&zc, &za) == 0; 228 zap_cursor_advance(&zc)) { 229 if (za.za_integer_length == 8) { 230 ASSERT(za.za_num_integers == 1); 231 (void) printf("\t%s = %llu\n", 232 za.za_name, (u_longlong_t)za.za_first_integer); 233 } else { 234 ASSERT(za.za_integer_length == 1); 235 char val[1024]; 236 VERIFY(zap_lookup(os, obj, za.za_name, 237 1, sizeof (val), val) == 0); 238 (void) printf("\t%s = %s\n", za.za_name, val); 239 } 240 } 241 zap_cursor_fini(&zc); 242} 243 244static void 245dump_mos(spa_t *spa) 246{ 247 nvlist_t *nv = spa->spa_label_features; 248 249 (void) printf("label config:\n"); 250 for (nvpair_t *pair = nvlist_next_nvpair(nv, NULL); 251 pair != NULL; 252 pair = nvlist_next_nvpair(nv, pair)) { 253 (void) printf("\t%s\n", nvpair_name(pair)); 254 } 255} 256 257static void 258zhack_do_feature_stat(int argc, char **argv) 259{ 260 spa_t *spa; 261 objset_t *os; 262 char *target; 263 264 argc--; 265 argv++; 266 267 if (argc < 1) { 268 (void) fprintf(stderr, "error: missing pool name\n"); 269 usage(); 270 } 271 target = argv[0]; 272 273 zhack_spa_open(target, B_TRUE, FTAG, &spa); 274 os = spa->spa_meta_objset; 275 276 dump_obj(os, spa->spa_feat_for_read_obj, "for_read"); 277 dump_obj(os, spa->spa_feat_for_write_obj, "for_write"); 278 dump_obj(os, spa->spa_feat_desc_obj, "descriptions"); 279 if (spa_feature_is_active(spa, SPA_FEATURE_ENABLED_TXG)) { 280 dump_obj(os, spa->spa_feat_enabled_txg_obj, "enabled_txg"); 281 } 282 dump_mos(spa); 283 284 spa_close(spa, FTAG); 285} 286 287static void 288zhack_feature_enable_sync(void *arg, dmu_tx_t *tx) 289{ 290 spa_t *spa = dmu_tx_pool(tx)->dp_spa; 291 zfeature_info_t *feature = arg; 292 293 feature_enable_sync(spa, feature, tx); 294 295 spa_history_log_internal(spa, "zhack enable feature", tx, 296 "guid=%s flags=%x", 297 feature->fi_guid, feature->fi_flags); 298} 299 300static void 301zhack_do_feature_enable(int argc, char **argv) 302{ 303 char c; 304 char *desc, *target; 305 spa_t *spa; 306 objset_t *mos; 307 zfeature_info_t feature; 308 spa_feature_t nodeps[] = { SPA_FEATURE_NONE }; 309 310 /* 311 * Features are not added to the pool's label until their refcounts 312 * are incremented, so fi_mos can just be left as false for now. 313 */ 314 desc = NULL; 315 feature.fi_uname = "zhack"; 316 feature.fi_flags = 0; 317 feature.fi_depends = nodeps; 318 feature.fi_feature = SPA_FEATURE_NONE; 319 320 optind = 1; 321 while ((c = getopt(argc, argv, "rmd:")) != -1) { 322 switch (c) { 323 case 'r': 324 feature.fi_flags |= ZFEATURE_FLAG_READONLY_COMPAT; 325 break; 326 case 'd': 327 desc = strdup(optarg); 328 break; 329 default: 330 usage(); 331 break; 332 } 333 } 334 335 if (desc == NULL) 336 desc = strdup("zhack injected"); 337 feature.fi_desc = desc; 338 339 argc -= optind; 340 argv += optind; 341 342 if (argc < 2) { 343 (void) fprintf(stderr, "error: missing feature or pool name\n"); 344 usage(); 345 } 346 target = argv[0]; 347 feature.fi_guid = argv[1]; 348 349 if (!zfeature_is_valid_guid(feature.fi_guid)) 350 fatal(NULL, FTAG, "invalid feature guid: %s", feature.fi_guid); 351 352 zhack_spa_open(target, B_FALSE, FTAG, &spa); 353 mos = spa->spa_meta_objset; 354 355 if (zfeature_is_supported(feature.fi_guid)) 356 fatal(spa, FTAG, "'%s' is a real feature, will not enable"); 357 if (0 == zap_contains(mos, spa->spa_feat_desc_obj, feature.fi_guid)) 358 fatal(spa, FTAG, "feature already enabled: %s", 359 feature.fi_guid); 360 361 VERIFY0(dsl_sync_task(spa_name(spa), NULL, 362 zhack_feature_enable_sync, &feature, 5, ZFS_SPACE_CHECK_NORMAL)); 363 364 spa_close(spa, FTAG); 365 366 free(desc); 367} 368 369static void 370feature_incr_sync(void *arg, dmu_tx_t *tx) 371{ 372 spa_t *spa = dmu_tx_pool(tx)->dp_spa; 373 zfeature_info_t *feature = arg; 374 uint64_t refcount; 375 376 VERIFY0(feature_get_refcount_from_disk(spa, feature, &refcount)); 377 feature_sync(spa, feature, refcount + 1, tx); 378 spa_history_log_internal(spa, "zhack feature incr", tx, 379 "name=%s", feature->fi_guid); 380} 381 382static void 383feature_decr_sync(void *arg, dmu_tx_t *tx) 384{ 385 spa_t *spa = dmu_tx_pool(tx)->dp_spa; 386 zfeature_info_t *feature = arg; 387 uint64_t refcount; 388 389 VERIFY0(feature_get_refcount_from_disk(spa, feature, &refcount)); 390 feature_sync(spa, feature, refcount - 1, tx); 391 spa_history_log_internal(spa, "zhack feature decr", tx, 392 "name=%s", feature->fi_guid); 393} 394 395static void 396zhack_do_feature_ref(int argc, char **argv) 397{ 398 char c; 399 char *target; 400 boolean_t decr = B_FALSE; 401 spa_t *spa; 402 objset_t *mos; 403 zfeature_info_t feature; 404 spa_feature_t nodeps[] = { SPA_FEATURE_NONE }; 405 406 /* 407 * fi_desc does not matter here because it was written to disk 408 * when the feature was enabled, but we need to properly set the 409 * feature for read or write based on the information we read off 410 * disk later. 411 */ 412 feature.fi_uname = "zhack"; 413 feature.fi_flags = 0; 414 feature.fi_desc = NULL; 415 feature.fi_depends = nodeps; 416 feature.fi_feature = SPA_FEATURE_NONE; 417 418 optind = 1; 419 while ((c = getopt(argc, argv, "md")) != -1) { 420 switch (c) { 421 case 'm': 422 feature.fi_flags |= ZFEATURE_FLAG_MOS; 423 break; 424 case 'd': 425 decr = B_TRUE; 426 break; 427 default: 428 usage(); 429 break; 430 } 431 } 432 argc -= optind; 433 argv += optind; 434 435 if (argc < 2) { 436 (void) fprintf(stderr, "error: missing feature or pool name\n"); 437 usage(); 438 } 439 target = argv[0]; 440 feature.fi_guid = argv[1]; 441 442 if (!zfeature_is_valid_guid(feature.fi_guid)) 443 fatal(NULL, FTAG, "invalid feature guid: %s", feature.fi_guid); 444 445 zhack_spa_open(target, B_FALSE, FTAG, &spa); 446 mos = spa->spa_meta_objset; 447 448 if (zfeature_is_supported(feature.fi_guid)) { 449 fatal(spa, FTAG, 450 "'%s' is a real feature, will not change refcount"); 451 } 452 453 if (0 == zap_contains(mos, spa->spa_feat_for_read_obj, 454 feature.fi_guid)) { 455 feature.fi_flags &= ~ZFEATURE_FLAG_READONLY_COMPAT; 456 } else if (0 == zap_contains(mos, spa->spa_feat_for_write_obj, 457 feature.fi_guid)) { 458 feature.fi_flags |= ZFEATURE_FLAG_READONLY_COMPAT; 459 } else { 460 fatal(spa, FTAG, "feature is not enabled: %s", feature.fi_guid); 461 } 462 463 if (decr) { 464 uint64_t count; 465 if (feature_get_refcount_from_disk(spa, &feature, 466 &count) == 0 && count != 0) { 467 fatal(spa, FTAG, "feature refcount already 0: %s", 468 feature.fi_guid); 469 } 470 } 471 472 VERIFY0(dsl_sync_task(spa_name(spa), NULL, 473 decr ? feature_decr_sync : feature_incr_sync, &feature, 474 5, ZFS_SPACE_CHECK_NORMAL)); 475 476 spa_close(spa, FTAG); 477} 478 479static int 480zhack_do_feature(int argc, char **argv) 481{ 482 char *subcommand; 483 484 argc--; 485 argv++; 486 if (argc == 0) { 487 (void) fprintf(stderr, 488 "error: no feature operation specified\n"); 489 usage(); 490 } 491 492 subcommand = argv[0]; 493 if (strcmp(subcommand, "stat") == 0) { 494 zhack_do_feature_stat(argc, argv); 495 } else if (strcmp(subcommand, "enable") == 0) { 496 zhack_do_feature_enable(argc, argv); 497 } else if (strcmp(subcommand, "ref") == 0) { 498 zhack_do_feature_ref(argc, argv); 499 } else { 500 (void) fprintf(stderr, "error: unknown subcommand: %s\n", 501 subcommand); 502 usage(); 503 } 504 505 return (0); 506} 507 508#define MAX_NUM_PATHS 1024 509 510int 511main(int argc, char **argv) 512{ 513 extern void zfs_prop_init(void); 514 515 char *path[MAX_NUM_PATHS]; 516 const char *subcommand; 517 int rv = 0; 518 char c; 519 520 g_importargs.path = path; 521 522 dprintf_setup(&argc, argv); 523 zfs_prop_init(); 524 525 while ((c = getopt(argc, argv, "c:d:")) != -1) { 526 switch (c) { 527 case 'c': 528 g_importargs.cachefile = optarg; 529 break; 530 case 'd': 531 assert(g_importargs.paths < MAX_NUM_PATHS); 532 g_importargs.path[g_importargs.paths++] = optarg; 533 break; 534 default: 535 usage(); 536 break; 537 } 538 } 539 540 argc -= optind; 541 argv += optind; 542 optind = 1; 543 544 if (argc == 0) { 545 (void) fprintf(stderr, "error: no command specified\n"); 546 usage(); 547 } 548 549 subcommand = argv[0]; 550 551 if (strcmp(subcommand, "feature") == 0) { 552 rv = zhack_do_feature(argc, argv); 553 } else { 554 (void) fprintf(stderr, "error: unknown subcommand: %s\n", 555 subcommand); 556 usage(); 557 } 558 559 if (!g_readonly && spa_export(g_pool, NULL, B_TRUE, B_FALSE) != 0) { 560 fatal(NULL, FTAG, "pool export failed; " 561 "changes may not be committed to disk\n"); 562 } 563 564 libzfs_fini(g_zfs); 565 kernel_fini(); 566 567 return (rv); 568} 569