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 <libzutil.h>
53
54extern boolean_t zfeature_checks_disable;
55
56const char cmdname[] = "zhack";
57static importargs_t g_importargs;
58static char *g_pool;
59static boolean_t g_readonly;
60
61static void
62usage(void)
63{
64	(void) fprintf(stderr,
65	    "Usage: %s [-c cachefile] [-d dir] <subcommand> <args> ...\n"
66	    "where <subcommand> <args> is one of the following:\n"
67	    "\n", cmdname);
68
69	(void) fprintf(stderr,
70	    "    feature stat <pool>\n"
71	    "        print information about enabled features\n"
72	    "    feature enable [-d desc] <pool> <feature>\n"
73	    "        add a new enabled feature to the pool\n"
74	    "        -d <desc> sets the feature's description\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	exit(1);
82}
83
84
85static void
86fatal(spa_t *spa, void *tag, const char *fmt, ...)
87{
88	va_list ap;
89
90	if (spa != NULL) {
91		spa_close(spa, tag);
92		(void) spa_export(g_pool, NULL, B_TRUE, B_FALSE);
93	}
94
95	va_start(ap, fmt);
96	(void) fprintf(stderr, "%s: ", cmdname);
97	(void) vfprintf(stderr, fmt, ap);
98	va_end(ap);
99	(void) fprintf(stderr, "\n");
100
101	exit(1);
102}
103
104/* ARGSUSED */
105static int
106space_delta_cb(dmu_object_type_t bonustype, void *data,
107    uint64_t *userp, uint64_t *groupp, uint64_t *projectp)
108{
109	/*
110	 * Is it a valid type of object to track?
111	 */
112	if (bonustype != DMU_OT_ZNODE && bonustype != DMU_OT_SA)
113		return (ENOENT);
114	(void) fprintf(stderr, "modifying object that needs user accounting");
115	abort();
116	/* NOTREACHED */
117}
118
119/*
120 * Target is the dataset whose pool we want to open.
121 */
122static void
123zhack_import(char *target, boolean_t readonly)
124{
125	nvlist_t *config;
126	nvlist_t *props;
127	int error;
128
129	kernel_init(readonly ? FREAD : (FREAD | FWRITE));
130
131	dmu_objset_register_type(DMU_OST_ZFS, space_delta_cb);
132
133	g_readonly = readonly;
134	g_importargs.can_be_active = readonly;
135	g_pool = strdup(target);
136
137	error = zpool_find_config(NULL, target, &config, &g_importargs,
138	    &libzpool_config_ops);
139	if (error)
140		fatal(NULL, FTAG, "cannot import '%s'", target);
141
142	props = NULL;
143	if (readonly) {
144		VERIFY(nvlist_alloc(&props, NV_UNIQUE_NAME, 0) == 0);
145		VERIFY(nvlist_add_uint64(props,
146		    zpool_prop_to_name(ZPOOL_PROP_READONLY), 1) == 0);
147	}
148
149	zfeature_checks_disable = B_TRUE;
150	error = spa_import(target, config, props,
151	    (readonly ?  ZFS_IMPORT_SKIP_MMP : ZFS_IMPORT_NORMAL));
152	zfeature_checks_disable = B_FALSE;
153	if (error == EEXIST)
154		error = 0;
155
156	if (error)
157		fatal(NULL, FTAG, "can't import '%s': %s", target,
158		    strerror(error));
159}
160
161static void
162zhack_spa_open(char *target, boolean_t readonly, void *tag, spa_t **spa)
163{
164	int err;
165
166	zhack_import(target, readonly);
167
168	zfeature_checks_disable = B_TRUE;
169	err = spa_open(target, spa, tag);
170	zfeature_checks_disable = B_FALSE;
171
172	if (err != 0)
173		fatal(*spa, FTAG, "cannot open '%s': %s", target,
174		    strerror(err));
175	if (spa_version(*spa) < SPA_VERSION_FEATURES) {
176		fatal(*spa, FTAG, "'%s' has version %d, features not enabled",
177		    target, (int)spa_version(*spa));
178	}
179}
180
181static void
182dump_obj(objset_t *os, uint64_t obj, const char *name)
183{
184	zap_cursor_t zc;
185	zap_attribute_t za;
186
187	(void) printf("%s_obj:\n", name);
188
189	for (zap_cursor_init(&zc, os, obj);
190	    zap_cursor_retrieve(&zc, &za) == 0;
191	    zap_cursor_advance(&zc)) {
192		if (za.za_integer_length == 8) {
193			ASSERT(za.za_num_integers == 1);
194			(void) printf("\t%s = %llu\n",
195			    za.za_name, (u_longlong_t)za.za_first_integer);
196		} else {
197			ASSERT(za.za_integer_length == 1);
198			char val[1024];
199			VERIFY(zap_lookup(os, obj, za.za_name,
200			    1, sizeof (val), val) == 0);
201			(void) printf("\t%s = %s\n", za.za_name, val);
202		}
203	}
204	zap_cursor_fini(&zc);
205}
206
207static void
208dump_mos(spa_t *spa)
209{
210	nvlist_t *nv = spa->spa_label_features;
211
212	(void) printf("label config:\n");
213	for (nvpair_t *pair = nvlist_next_nvpair(nv, NULL);
214	    pair != NULL;
215	    pair = nvlist_next_nvpair(nv, pair)) {
216		(void) printf("\t%s\n", nvpair_name(pair));
217	}
218}
219
220static void
221zhack_do_feature_stat(int argc, char **argv)
222{
223	spa_t *spa;
224	objset_t *os;
225	char *target;
226
227	argc--;
228	argv++;
229
230	if (argc < 1) {
231		(void) fprintf(stderr, "error: missing pool name\n");
232		usage();
233	}
234	target = argv[0];
235
236	zhack_spa_open(target, B_TRUE, FTAG, &spa);
237	os = spa->spa_meta_objset;
238
239	dump_obj(os, spa->spa_feat_for_read_obj, "for_read");
240	dump_obj(os, spa->spa_feat_for_write_obj, "for_write");
241	dump_obj(os, spa->spa_feat_desc_obj, "descriptions");
242	if (spa_feature_is_active(spa, SPA_FEATURE_ENABLED_TXG)) {
243		dump_obj(os, spa->spa_feat_enabled_txg_obj, "enabled_txg");
244	}
245	dump_mos(spa);
246
247	spa_close(spa, FTAG);
248}
249
250static void
251zhack_feature_enable_sync(void *arg, dmu_tx_t *tx)
252{
253	spa_t *spa = dmu_tx_pool(tx)->dp_spa;
254	zfeature_info_t *feature = arg;
255
256	feature_enable_sync(spa, feature, tx);
257
258	spa_history_log_internal(spa, "zhack enable feature", tx,
259	    "guid=%s flags=%x",
260	    feature->fi_guid, feature->fi_flags);
261}
262
263static void
264zhack_do_feature_enable(int argc, char **argv)
265{
266	char c;
267	char *desc, *target;
268	spa_t *spa;
269	objset_t *mos;
270	zfeature_info_t feature;
271	spa_feature_t nodeps[] = { SPA_FEATURE_NONE };
272
273	/*
274	 * Features are not added to the pool's label until their refcounts
275	 * are incremented, so fi_mos can just be left as false for now.
276	 */
277	desc = NULL;
278	feature.fi_uname = "zhack";
279	feature.fi_flags = 0;
280	feature.fi_depends = nodeps;
281	feature.fi_feature = SPA_FEATURE_NONE;
282
283	optind = 1;
284	while ((c = getopt(argc, argv, "rmd:")) != -1) {
285		switch (c) {
286		case 'r':
287			feature.fi_flags |= ZFEATURE_FLAG_READONLY_COMPAT;
288			break;
289		case 'd':
290			desc = strdup(optarg);
291			break;
292		default:
293			usage();
294			break;
295		}
296	}
297
298	if (desc == NULL)
299		desc = strdup("zhack injected");
300	feature.fi_desc = desc;
301
302	argc -= optind;
303	argv += optind;
304
305	if (argc < 2) {
306		(void) fprintf(stderr, "error: missing feature or pool name\n");
307		usage();
308	}
309	target = argv[0];
310	feature.fi_guid = argv[1];
311
312	if (!zfeature_is_valid_guid(feature.fi_guid))
313		fatal(NULL, FTAG, "invalid feature guid: %s", feature.fi_guid);
314
315	zhack_spa_open(target, B_FALSE, FTAG, &spa);
316	mos = spa->spa_meta_objset;
317
318	if (zfeature_is_supported(feature.fi_guid))
319		fatal(spa, FTAG, "'%s' is a real feature, will not enable");
320	if (0 == zap_contains(mos, spa->spa_feat_desc_obj, feature.fi_guid))
321		fatal(spa, FTAG, "feature already enabled: %s",
322		    feature.fi_guid);
323
324	VERIFY0(dsl_sync_task(spa_name(spa), NULL,
325	    zhack_feature_enable_sync, &feature, 5, ZFS_SPACE_CHECK_NORMAL));
326
327	spa_close(spa, FTAG);
328
329	free(desc);
330}
331
332static void
333feature_incr_sync(void *arg, dmu_tx_t *tx)
334{
335	spa_t *spa = dmu_tx_pool(tx)->dp_spa;
336	zfeature_info_t *feature = arg;
337	uint64_t refcount;
338
339	VERIFY0(feature_get_refcount_from_disk(spa, feature, &refcount));
340	feature_sync(spa, feature, refcount + 1, tx);
341	spa_history_log_internal(spa, "zhack feature incr", tx,
342	    "guid=%s", feature->fi_guid);
343}
344
345static void
346feature_decr_sync(void *arg, dmu_tx_t *tx)
347{
348	spa_t *spa = dmu_tx_pool(tx)->dp_spa;
349	zfeature_info_t *feature = arg;
350	uint64_t refcount;
351
352	VERIFY0(feature_get_refcount_from_disk(spa, feature, &refcount));
353	feature_sync(spa, feature, refcount - 1, tx);
354	spa_history_log_internal(spa, "zhack feature decr", tx,
355	    "guid=%s", feature->fi_guid);
356}
357
358static void
359zhack_do_feature_ref(int argc, char **argv)
360{
361	char c;
362	char *target;
363	boolean_t decr = B_FALSE;
364	spa_t *spa;
365	objset_t *mos;
366	zfeature_info_t feature;
367	spa_feature_t nodeps[] = { SPA_FEATURE_NONE };
368
369	/*
370	 * fi_desc does not matter here because it was written to disk
371	 * when the feature was enabled, but we need to properly set the
372	 * feature for read or write based on the information we read off
373	 * disk later.
374	 */
375	feature.fi_uname = "zhack";
376	feature.fi_flags = 0;
377	feature.fi_desc = NULL;
378	feature.fi_depends = nodeps;
379	feature.fi_feature = SPA_FEATURE_NONE;
380
381	optind = 1;
382	while ((c = getopt(argc, argv, "md")) != -1) {
383		switch (c) {
384		case 'm':
385			feature.fi_flags |= ZFEATURE_FLAG_MOS;
386			break;
387		case 'd':
388			decr = B_TRUE;
389			break;
390		default:
391			usage();
392			break;
393		}
394	}
395	argc -= optind;
396	argv += optind;
397
398	if (argc < 2) {
399		(void) fprintf(stderr, "error: missing feature or pool name\n");
400		usage();
401	}
402	target = argv[0];
403	feature.fi_guid = argv[1];
404
405	if (!zfeature_is_valid_guid(feature.fi_guid))
406		fatal(NULL, FTAG, "invalid feature guid: %s", feature.fi_guid);
407
408	zhack_spa_open(target, B_FALSE, FTAG, &spa);
409	mos = spa->spa_meta_objset;
410
411	if (zfeature_is_supported(feature.fi_guid)) {
412		fatal(spa, FTAG,
413		    "'%s' is a real feature, will not change refcount");
414	}
415
416	if (0 == zap_contains(mos, spa->spa_feat_for_read_obj,
417	    feature.fi_guid)) {
418		feature.fi_flags &= ~ZFEATURE_FLAG_READONLY_COMPAT;
419	} else if (0 == zap_contains(mos, spa->spa_feat_for_write_obj,
420	    feature.fi_guid)) {
421		feature.fi_flags |= ZFEATURE_FLAG_READONLY_COMPAT;
422	} else {
423		fatal(spa, FTAG, "feature is not enabled: %s", feature.fi_guid);
424	}
425
426	if (decr) {
427		uint64_t count;
428		if (feature_get_refcount_from_disk(spa, &feature,
429		    &count) == 0 && count != 0) {
430			fatal(spa, FTAG, "feature refcount already 0: %s",
431			    feature.fi_guid);
432		}
433	}
434
435	VERIFY0(dsl_sync_task(spa_name(spa), NULL,
436	    decr ? feature_decr_sync : feature_incr_sync, &feature,
437	    5, ZFS_SPACE_CHECK_NORMAL));
438
439	spa_close(spa, FTAG);
440}
441
442static int
443zhack_do_feature(int argc, char **argv)
444{
445	char *subcommand;
446
447	argc--;
448	argv++;
449	if (argc == 0) {
450		(void) fprintf(stderr,
451		    "error: no feature operation specified\n");
452		usage();
453	}
454
455	subcommand = argv[0];
456	if (strcmp(subcommand, "stat") == 0) {
457		zhack_do_feature_stat(argc, argv);
458	} else if (strcmp(subcommand, "enable") == 0) {
459		zhack_do_feature_enable(argc, argv);
460	} else if (strcmp(subcommand, "ref") == 0) {
461		zhack_do_feature_ref(argc, argv);
462	} else {
463		(void) fprintf(stderr, "error: unknown subcommand: %s\n",
464		    subcommand);
465		usage();
466	}
467
468	return (0);
469}
470
471#define	MAX_NUM_PATHS 1024
472
473int
474main(int argc, char **argv)
475{
476	extern void zfs_prop_init(void);
477
478	char *path[MAX_NUM_PATHS];
479	const char *subcommand;
480	int rv = 0;
481	char c;
482
483	g_importargs.path = path;
484
485	dprintf_setup(&argc, argv);
486	zfs_prop_init();
487
488	while ((c = getopt(argc, argv, "c:d:")) != -1) {
489		switch (c) {
490		case 'c':
491			g_importargs.cachefile = optarg;
492			break;
493		case 'd':
494			assert(g_importargs.paths < MAX_NUM_PATHS);
495			g_importargs.path[g_importargs.paths++] = optarg;
496			break;
497		default:
498			usage();
499			break;
500		}
501	}
502
503	argc -= optind;
504	argv += optind;
505	optind = 1;
506
507	if (argc == 0) {
508		(void) fprintf(stderr, "error: no command specified\n");
509		usage();
510	}
511
512	subcommand = argv[0];
513
514	if (strcmp(subcommand, "feature") == 0) {
515		rv = zhack_do_feature(argc, argv);
516	} else {
517		(void) fprintf(stderr, "error: unknown subcommand: %s\n",
518		    subcommand);
519		usage();
520	}
521
522	if (!g_readonly && spa_export(g_pool, NULL, B_TRUE, B_FALSE) != 0) {
523		fatal(NULL, FTAG, "pool export failed; "
524		    "changes may not be committed to disk\n");
525	}
526
527	kernel_fini();
528
529	return (rv);
530}
531