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