xref: /freebsd/sys/contrib/openzfs/cmd/zhack.c (revision 38a52bd3)
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 https://opensource.org/licenses/CDDL-1.0.
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/stat.h>
37 #include <sys/zfs_context.h>
38 #include <sys/spa.h>
39 #include <sys/spa_impl.h>
40 #include <sys/dmu.h>
41 #include <sys/zap.h>
42 #include <sys/zfs_znode.h>
43 #include <sys/dsl_synctask.h>
44 #include <sys/vdev.h>
45 #include <sys/vdev_impl.h>
46 #include <sys/fs/zfs.h>
47 #include <sys/dmu_objset.h>
48 #include <sys/dsl_pool.h>
49 #include <sys/zio_checksum.h>
50 #include <sys/zio_compress.h>
51 #include <sys/zfeature.h>
52 #include <sys/dmu_tx.h>
53 #include <zfeature_common.h>
54 #include <libzutil.h>
55 
56 static importargs_t g_importargs;
57 static char *g_pool;
58 static boolean_t g_readonly;
59 
60 static __attribute__((noreturn)) void
61 usage(void)
62 {
63 	(void) fprintf(stderr,
64 	    "Usage: zhack [-c cachefile] [-d dir] <subcommand> <args> ...\n"
65 	    "where <subcommand> <args> is one of the following:\n"
66 	    "\n");
67 
68 	(void) fprintf(stderr,
69 	    "    feature stat <pool>\n"
70 	    "        print information about enabled features\n"
71 	    "    feature enable [-r] [-d desc] <pool> <feature>\n"
72 	    "        add a new enabled feature to the pool\n"
73 	    "        -d <desc> sets the feature's description\n"
74 	    "        -r set read-only compatible flag for feature\n"
75 	    "    feature ref [-md] <pool> <feature>\n"
76 	    "        change the refcount on the given feature\n"
77 	    "        -d decrease instead of increase the refcount\n"
78 	    "        -m add the feature to the label if increasing refcount\n"
79 	    "\n"
80 	    "    <feature> : should be a feature guid\n"
81 	    "\n"
82 	    "    label repair <device>\n"
83 	    "        repair corrupted label checksums\n"
84 	    "\n"
85 	    "    <device> : path to vdev\n");
86 	exit(1);
87 }
88 
89 
90 static __attribute__((format(printf, 3, 4))) __attribute__((noreturn)) void
91 fatal(spa_t *spa, const void *tag, const char *fmt, ...)
92 {
93 	va_list ap;
94 
95 	if (spa != NULL) {
96 		spa_close(spa, tag);
97 		(void) spa_export(g_pool, NULL, B_TRUE, B_FALSE);
98 	}
99 
100 	va_start(ap, fmt);
101 	(void) fputs("zhack: ", stderr);
102 	(void) vfprintf(stderr, fmt, ap);
103 	va_end(ap);
104 	(void) fputc('\n', stderr);
105 
106 	exit(1);
107 }
108 
109 static int
110 space_delta_cb(dmu_object_type_t bonustype, const void *data,
111     zfs_file_info_t *zoi)
112 {
113 	(void) data, (void) zoi;
114 
115 	/*
116 	 * Is it a valid type of object to track?
117 	 */
118 	if (bonustype != DMU_OT_ZNODE && bonustype != DMU_OT_SA)
119 		return (ENOENT);
120 	(void) fprintf(stderr, "modifying object that needs user accounting");
121 	abort();
122 }
123 
124 /*
125  * Target is the dataset whose pool we want to open.
126  */
127 static void
128 zhack_import(char *target, boolean_t readonly)
129 {
130 	nvlist_t *config;
131 	nvlist_t *props;
132 	int error;
133 
134 	kernel_init(readonly ? SPA_MODE_READ :
135 	    (SPA_MODE_READ | SPA_MODE_WRITE));
136 
137 	dmu_objset_register_type(DMU_OST_ZFS, space_delta_cb);
138 
139 	g_readonly = readonly;
140 	g_importargs.can_be_active = readonly;
141 	g_pool = strdup(target);
142 
143 	error = zpool_find_config(NULL, target, &config, &g_importargs,
144 	    &libzpool_config_ops);
145 	if (error)
146 		fatal(NULL, FTAG, "cannot import '%s'", target);
147 
148 	props = NULL;
149 	if (readonly) {
150 		VERIFY(nvlist_alloc(&props, NV_UNIQUE_NAME, 0) == 0);
151 		VERIFY(nvlist_add_uint64(props,
152 		    zpool_prop_to_name(ZPOOL_PROP_READONLY), 1) == 0);
153 	}
154 
155 	zfeature_checks_disable = B_TRUE;
156 	error = spa_import(target, config, props,
157 	    (readonly ?  ZFS_IMPORT_SKIP_MMP : ZFS_IMPORT_NORMAL));
158 	fnvlist_free(config);
159 	zfeature_checks_disable = B_FALSE;
160 	if (error == EEXIST)
161 		error = 0;
162 
163 	if (error)
164 		fatal(NULL, FTAG, "can't import '%s': %s", target,
165 		    strerror(error));
166 }
167 
168 static void
169 zhack_spa_open(char *target, boolean_t readonly, const void *tag, spa_t **spa)
170 {
171 	int err;
172 
173 	zhack_import(target, readonly);
174 
175 	zfeature_checks_disable = B_TRUE;
176 	err = spa_open(target, spa, tag);
177 	zfeature_checks_disable = B_FALSE;
178 
179 	if (err != 0)
180 		fatal(*spa, FTAG, "cannot open '%s': %s", target,
181 		    strerror(err));
182 	if (spa_version(*spa) < SPA_VERSION_FEATURES) {
183 		fatal(*spa, FTAG, "'%s' has version %d, features not enabled",
184 		    target, (int)spa_version(*spa));
185 	}
186 }
187 
188 static void
189 dump_obj(objset_t *os, uint64_t obj, const char *name)
190 {
191 	zap_cursor_t zc;
192 	zap_attribute_t za;
193 
194 	(void) printf("%s_obj:\n", name);
195 
196 	for (zap_cursor_init(&zc, os, obj);
197 	    zap_cursor_retrieve(&zc, &za) == 0;
198 	    zap_cursor_advance(&zc)) {
199 		if (za.za_integer_length == 8) {
200 			ASSERT(za.za_num_integers == 1);
201 			(void) printf("\t%s = %llu\n",
202 			    za.za_name, (u_longlong_t)za.za_first_integer);
203 		} else {
204 			ASSERT(za.za_integer_length == 1);
205 			char val[1024];
206 			VERIFY(zap_lookup(os, obj, za.za_name,
207 			    1, sizeof (val), val) == 0);
208 			(void) printf("\t%s = %s\n", za.za_name, val);
209 		}
210 	}
211 	zap_cursor_fini(&zc);
212 }
213 
214 static void
215 dump_mos(spa_t *spa)
216 {
217 	nvlist_t *nv = spa->spa_label_features;
218 	nvpair_t *pair;
219 
220 	(void) printf("label config:\n");
221 	for (pair = nvlist_next_nvpair(nv, NULL);
222 	    pair != NULL;
223 	    pair = nvlist_next_nvpair(nv, pair)) {
224 		(void) printf("\t%s\n", nvpair_name(pair));
225 	}
226 }
227 
228 static void
229 zhack_do_feature_stat(int argc, char **argv)
230 {
231 	spa_t *spa;
232 	objset_t *os;
233 	char *target;
234 
235 	argc--;
236 	argv++;
237 
238 	if (argc < 1) {
239 		(void) fprintf(stderr, "error: missing pool name\n");
240 		usage();
241 	}
242 	target = argv[0];
243 
244 	zhack_spa_open(target, B_TRUE, FTAG, &spa);
245 	os = spa->spa_meta_objset;
246 
247 	dump_obj(os, spa->spa_feat_for_read_obj, "for_read");
248 	dump_obj(os, spa->spa_feat_for_write_obj, "for_write");
249 	dump_obj(os, spa->spa_feat_desc_obj, "descriptions");
250 	if (spa_feature_is_active(spa, SPA_FEATURE_ENABLED_TXG)) {
251 		dump_obj(os, spa->spa_feat_enabled_txg_obj, "enabled_txg");
252 	}
253 	dump_mos(spa);
254 
255 	spa_close(spa, FTAG);
256 }
257 
258 static void
259 zhack_feature_enable_sync(void *arg, dmu_tx_t *tx)
260 {
261 	spa_t *spa = dmu_tx_pool(tx)->dp_spa;
262 	zfeature_info_t *feature = arg;
263 
264 	feature_enable_sync(spa, feature, tx);
265 
266 	spa_history_log_internal(spa, "zhack enable feature", tx,
267 	    "name=%s flags=%u",
268 	    feature->fi_guid, feature->fi_flags);
269 }
270 
271 static void
272 zhack_do_feature_enable(int argc, char **argv)
273 {
274 	int c;
275 	char *desc, *target;
276 	spa_t *spa;
277 	objset_t *mos;
278 	zfeature_info_t feature;
279 	const spa_feature_t nodeps[] = { SPA_FEATURE_NONE };
280 
281 	/*
282 	 * Features are not added to the pool's label until their refcounts
283 	 * are incremented, so fi_mos can just be left as false for now.
284 	 */
285 	desc = NULL;
286 	feature.fi_uname = "zhack";
287 	feature.fi_flags = 0;
288 	feature.fi_depends = nodeps;
289 	feature.fi_feature = SPA_FEATURE_NONE;
290 
291 	optind = 1;
292 	while ((c = getopt(argc, argv, "+rd:")) != -1) {
293 		switch (c) {
294 		case 'r':
295 			feature.fi_flags |= ZFEATURE_FLAG_READONLY_COMPAT;
296 			break;
297 		case 'd':
298 			if (desc != NULL)
299 				free(desc);
300 			desc = strdup(optarg);
301 			break;
302 		default:
303 			usage();
304 			break;
305 		}
306 	}
307 
308 	if (desc == NULL)
309 		desc = strdup("zhack injected");
310 	feature.fi_desc = desc;
311 
312 	argc -= optind;
313 	argv += optind;
314 
315 	if (argc < 2) {
316 		(void) fprintf(stderr, "error: missing feature or pool name\n");
317 		usage();
318 	}
319 	target = argv[0];
320 	feature.fi_guid = argv[1];
321 
322 	if (!zfeature_is_valid_guid(feature.fi_guid))
323 		fatal(NULL, FTAG, "invalid feature guid: %s", feature.fi_guid);
324 
325 	zhack_spa_open(target, B_FALSE, FTAG, &spa);
326 	mos = spa->spa_meta_objset;
327 
328 	if (zfeature_is_supported(feature.fi_guid))
329 		fatal(spa, FTAG, "'%s' is a real feature, will not enable",
330 		    feature.fi_guid);
331 	if (0 == zap_contains(mos, spa->spa_feat_desc_obj, feature.fi_guid))
332 		fatal(spa, FTAG, "feature already enabled: %s",
333 		    feature.fi_guid);
334 
335 	VERIFY0(dsl_sync_task(spa_name(spa), NULL,
336 	    zhack_feature_enable_sync, &feature, 5, ZFS_SPACE_CHECK_NORMAL));
337 
338 	spa_close(spa, FTAG);
339 
340 	free(desc);
341 }
342 
343 static void
344 feature_incr_sync(void *arg, dmu_tx_t *tx)
345 {
346 	spa_t *spa = dmu_tx_pool(tx)->dp_spa;
347 	zfeature_info_t *feature = arg;
348 	uint64_t refcount;
349 
350 	VERIFY0(feature_get_refcount_from_disk(spa, feature, &refcount));
351 	feature_sync(spa, feature, refcount + 1, tx);
352 	spa_history_log_internal(spa, "zhack feature incr", tx,
353 	    "name=%s", feature->fi_guid);
354 }
355 
356 static void
357 feature_decr_sync(void *arg, dmu_tx_t *tx)
358 {
359 	spa_t *spa = dmu_tx_pool(tx)->dp_spa;
360 	zfeature_info_t *feature = arg;
361 	uint64_t refcount;
362 
363 	VERIFY0(feature_get_refcount_from_disk(spa, feature, &refcount));
364 	feature_sync(spa, feature, refcount - 1, tx);
365 	spa_history_log_internal(spa, "zhack feature decr", tx,
366 	    "name=%s", feature->fi_guid);
367 }
368 
369 static void
370 zhack_do_feature_ref(int argc, char **argv)
371 {
372 	int c;
373 	char *target;
374 	boolean_t decr = B_FALSE;
375 	spa_t *spa;
376 	objset_t *mos;
377 	zfeature_info_t feature;
378 	const spa_feature_t nodeps[] = { SPA_FEATURE_NONE };
379 
380 	/*
381 	 * fi_desc does not matter here because it was written to disk
382 	 * when the feature was enabled, but we need to properly set the
383 	 * feature for read or write based on the information we read off
384 	 * disk later.
385 	 */
386 	feature.fi_uname = "zhack";
387 	feature.fi_flags = 0;
388 	feature.fi_desc = NULL;
389 	feature.fi_depends = nodeps;
390 	feature.fi_feature = SPA_FEATURE_NONE;
391 
392 	optind = 1;
393 	while ((c = getopt(argc, argv, "+md")) != -1) {
394 		switch (c) {
395 		case 'm':
396 			feature.fi_flags |= ZFEATURE_FLAG_MOS;
397 			break;
398 		case 'd':
399 			decr = B_TRUE;
400 			break;
401 		default:
402 			usage();
403 			break;
404 		}
405 	}
406 	argc -= optind;
407 	argv += optind;
408 
409 	if (argc < 2) {
410 		(void) fprintf(stderr, "error: missing feature or pool name\n");
411 		usage();
412 	}
413 	target = argv[0];
414 	feature.fi_guid = argv[1];
415 
416 	if (!zfeature_is_valid_guid(feature.fi_guid))
417 		fatal(NULL, FTAG, "invalid feature guid: %s", feature.fi_guid);
418 
419 	zhack_spa_open(target, B_FALSE, FTAG, &spa);
420 	mos = spa->spa_meta_objset;
421 
422 	if (zfeature_is_supported(feature.fi_guid)) {
423 		fatal(spa, FTAG,
424 		    "'%s' is a real feature, will not change refcount",
425 		    feature.fi_guid);
426 	}
427 
428 	if (0 == zap_contains(mos, spa->spa_feat_for_read_obj,
429 	    feature.fi_guid)) {
430 		feature.fi_flags &= ~ZFEATURE_FLAG_READONLY_COMPAT;
431 	} else if (0 == zap_contains(mos, spa->spa_feat_for_write_obj,
432 	    feature.fi_guid)) {
433 		feature.fi_flags |= ZFEATURE_FLAG_READONLY_COMPAT;
434 	} else {
435 		fatal(spa, FTAG, "feature is not enabled: %s", feature.fi_guid);
436 	}
437 
438 	if (decr) {
439 		uint64_t count;
440 		if (feature_get_refcount_from_disk(spa, &feature,
441 		    &count) == 0 && count == 0) {
442 			fatal(spa, FTAG, "feature refcount already 0: %s",
443 			    feature.fi_guid);
444 		}
445 	}
446 
447 	VERIFY0(dsl_sync_task(spa_name(spa), NULL,
448 	    decr ? feature_decr_sync : feature_incr_sync, &feature,
449 	    5, ZFS_SPACE_CHECK_NORMAL));
450 
451 	spa_close(spa, FTAG);
452 }
453 
454 static int
455 zhack_do_feature(int argc, char **argv)
456 {
457 	char *subcommand;
458 
459 	argc--;
460 	argv++;
461 	if (argc == 0) {
462 		(void) fprintf(stderr,
463 		    "error: no feature operation specified\n");
464 		usage();
465 	}
466 
467 	subcommand = argv[0];
468 	if (strcmp(subcommand, "stat") == 0) {
469 		zhack_do_feature_stat(argc, argv);
470 	} else if (strcmp(subcommand, "enable") == 0) {
471 		zhack_do_feature_enable(argc, argv);
472 	} else if (strcmp(subcommand, "ref") == 0) {
473 		zhack_do_feature_ref(argc, argv);
474 	} else {
475 		(void) fprintf(stderr, "error: unknown subcommand: %s\n",
476 		    subcommand);
477 		usage();
478 	}
479 
480 	return (0);
481 }
482 
483 static int
484 zhack_repair_label_cksum(int argc, char **argv)
485 {
486 	zio_checksum_info_t *ci = &zio_checksum_table[ZIO_CHECKSUM_LABEL];
487 	const char *cfg_keys[] = { ZPOOL_CONFIG_VERSION,
488 	    ZPOOL_CONFIG_POOL_STATE, ZPOOL_CONFIG_GUID };
489 	boolean_t labels_repaired[VDEV_LABELS] = {0};
490 	boolean_t repaired = B_FALSE;
491 	vdev_label_t labels[VDEV_LABELS] = {{{0}}};
492 	struct stat st;
493 	int fd;
494 
495 	abd_init();
496 
497 	argc -= 1;
498 	argv += 1;
499 
500 	if (argc < 1) {
501 		(void) fprintf(stderr, "error: missing device\n");
502 		usage();
503 	}
504 
505 	if ((fd = open(argv[0], O_RDWR)) == -1)
506 		fatal(NULL, FTAG, "cannot open '%s': %s", argv[0],
507 		    strerror(errno));
508 
509 	if (stat(argv[0], &st) != 0)
510 		fatal(NULL, FTAG, "cannot stat '%s': %s", argv[0],
511 		    strerror(errno));
512 
513 	for (int l = 0; l < VDEV_LABELS; l++) {
514 		uint64_t label_offset, offset;
515 		zio_cksum_t expected_cksum;
516 		zio_cksum_t actual_cksum;
517 		zio_cksum_t verifier;
518 		zio_eck_t *eck;
519 		nvlist_t *cfg;
520 		int byteswap;
521 		uint64_t val;
522 		ssize_t err;
523 
524 		vdev_label_t *vl = &labels[l];
525 
526 		label_offset = vdev_label_offset(st.st_size, l, 0);
527 		err = pread64(fd, vl, sizeof (vdev_label_t), label_offset);
528 		if (err == -1) {
529 			(void) fprintf(stderr, "error: cannot read "
530 			    "label %d: %s\n", l, strerror(errno));
531 			continue;
532 		} else if (err != sizeof (vdev_label_t)) {
533 			(void) fprintf(stderr, "error: bad label %d read size "
534 			    "\n", l);
535 			continue;
536 		}
537 
538 		err = nvlist_unpack(vl->vl_vdev_phys.vp_nvlist,
539 		    VDEV_PHYS_SIZE - sizeof (zio_eck_t), &cfg, 0);
540 		if (err) {
541 			(void) fprintf(stderr, "error: cannot unpack nvlist "
542 			    "label %d\n", l);
543 			continue;
544 		}
545 
546 		for (int i = 0; i < ARRAY_SIZE(cfg_keys); i++) {
547 			err = nvlist_lookup_uint64(cfg, cfg_keys[i], &val);
548 			if (err) {
549 				(void) fprintf(stderr, "error: label %d: "
550 				    "cannot find nvlist key %s\n",
551 				    l, cfg_keys[i]);
552 				continue;
553 			}
554 		}
555 
556 		void *data = (char *)vl + offsetof(vdev_label_t, vl_vdev_phys);
557 		eck = (zio_eck_t *)((char *)(data) + VDEV_PHYS_SIZE) - 1;
558 
559 		offset = label_offset + offsetof(vdev_label_t, vl_vdev_phys);
560 		ZIO_SET_CHECKSUM(&verifier, offset, 0, 0, 0);
561 
562 		byteswap = (eck->zec_magic == BSWAP_64(ZEC_MAGIC));
563 		if (byteswap)
564 			byteswap_uint64_array(&verifier, sizeof (zio_cksum_t));
565 
566 		expected_cksum = eck->zec_cksum;
567 		eck->zec_cksum = verifier;
568 
569 		abd_t *abd = abd_get_from_buf(data, VDEV_PHYS_SIZE);
570 		ci->ci_func[byteswap](abd, VDEV_PHYS_SIZE, NULL, &actual_cksum);
571 		abd_free(abd);
572 
573 		if (byteswap)
574 			byteswap_uint64_array(&expected_cksum,
575 			    sizeof (zio_cksum_t));
576 
577 		if (ZIO_CHECKSUM_EQUAL(actual_cksum, expected_cksum))
578 			continue;
579 
580 		eck->zec_cksum = actual_cksum;
581 
582 		err = pwrite64(fd, data, VDEV_PHYS_SIZE, offset);
583 		if (err == -1) {
584 			(void) fprintf(stderr, "error: cannot write "
585 			    "label %d: %s\n", l, strerror(errno));
586 			continue;
587 		} else if (err != VDEV_PHYS_SIZE) {
588 			(void) fprintf(stderr, "error: bad write size "
589 			    "label %d\n", l);
590 			continue;
591 		}
592 
593 		fsync(fd);
594 
595 		labels_repaired[l] = B_TRUE;
596 	}
597 
598 	close(fd);
599 
600 	abd_fini();
601 
602 	for (int l = 0; l < VDEV_LABELS; l++) {
603 		(void) printf("label %d: %s\n", l,
604 		    labels_repaired[l] ? "repaired" : "skipped");
605 		repaired |= labels_repaired[l];
606 	}
607 
608 	if (repaired)
609 		return (0);
610 
611 	return (1);
612 }
613 
614 static int
615 zhack_do_label(int argc, char **argv)
616 {
617 	char *subcommand;
618 	int err;
619 
620 	argc--;
621 	argv++;
622 	if (argc == 0) {
623 		(void) fprintf(stderr,
624 		    "error: no label operation specified\n");
625 		usage();
626 	}
627 
628 	subcommand = argv[0];
629 	if (strcmp(subcommand, "repair") == 0) {
630 		err = zhack_repair_label_cksum(argc, argv);
631 	} else {
632 		(void) fprintf(stderr, "error: unknown subcommand: %s\n",
633 		    subcommand);
634 		usage();
635 	}
636 
637 	return (err);
638 }
639 
640 #define	MAX_NUM_PATHS 1024
641 
642 int
643 main(int argc, char **argv)
644 {
645 	extern void zfs_prop_init(void);
646 
647 	char *path[MAX_NUM_PATHS];
648 	const char *subcommand;
649 	int rv = 0;
650 	int c;
651 
652 	g_importargs.path = path;
653 
654 	dprintf_setup(&argc, argv);
655 	zfs_prop_init();
656 
657 	while ((c = getopt(argc, argv, "+c:d:")) != -1) {
658 		switch (c) {
659 		case 'c':
660 			g_importargs.cachefile = optarg;
661 			break;
662 		case 'd':
663 			assert(g_importargs.paths < MAX_NUM_PATHS);
664 			g_importargs.path[g_importargs.paths++] = optarg;
665 			break;
666 		default:
667 			usage();
668 			break;
669 		}
670 	}
671 
672 	argc -= optind;
673 	argv += optind;
674 	optind = 1;
675 
676 	if (argc == 0) {
677 		(void) fprintf(stderr, "error: no command specified\n");
678 		usage();
679 	}
680 
681 	subcommand = argv[0];
682 
683 	if (strcmp(subcommand, "feature") == 0) {
684 		rv = zhack_do_feature(argc, argv);
685 	} else if (strcmp(subcommand, "label") == 0) {
686 		return (zhack_do_label(argc, argv));
687 	} else {
688 		(void) fprintf(stderr, "error: unknown subcommand: %s\n",
689 		    subcommand);
690 		usage();
691 	}
692 
693 	if (!g_readonly && spa_export(g_pool, NULL, B_TRUE, B_FALSE) != 0) {
694 		fatal(NULL, FTAG, "pool export failed; "
695 		    "changes may not be committed to disk\n");
696 	}
697 
698 	kernel_fini();
699 
700 	return (rv);
701 }
702