translate.c revision 21bf64a78855d076f09716ea1c06175d954e934c
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 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28#include <libzfs.h>
29
30#undef verify	/* both libzfs.h and zfs_context.h want to define this */
31
32#include <sys/zfs_context.h>
33
34#include <errno.h>
35#include <fcntl.h>
36#include <stdarg.h>
37#include <stddef.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <strings.h>
41#include <sys/file.h>
42#include <sys/mntent.h>
43#include <sys/mnttab.h>
44#include <sys/param.h>
45#include <sys/stat.h>
46
47#include <sys/dmu.h>
48#include <sys/dmu_objset.h>
49#include <sys/dnode.h>
50#include <sys/vdev_impl.h>
51
52#include <sys/mkdev.h>
53
54#include "zinject.h"
55
56extern void kernel_init(int);
57extern void kernel_fini(void);
58
59static int debug;
60
61static void
62ziprintf(const char *fmt, ...)
63{
64	va_list ap;
65
66	if (!debug)
67		return;
68
69	va_start(ap, fmt);
70	(void) vprintf(fmt, ap);
71	va_end(ap);
72}
73
74/*
75 * Given a full path to a file, translate into a dataset name and a relative
76 * path within the dataset.  'dataset' must be at least MAXNAMELEN characters,
77 * and 'relpath' must be at least MAXPATHLEN characters.  We also pass a stat64
78 * buffer, which we need later to get the object ID.
79 */
80static int
81parse_pathname(const char *fullpath, char *dataset, char *relpath,
82    struct stat64 *statbuf)
83{
84	struct extmnttab mp;
85	FILE *fp;
86	int match;
87	const char *rel;
88
89	if (fullpath[0] != '/') {
90		(void) fprintf(stderr, "invalid object '%s': must be full "
91		    "path\n", fullpath);
92		usage();
93		return (-1);
94	}
95
96	if (strlen(fullpath) >= MAXPATHLEN) {
97		(void) fprintf(stderr, "invalid object; pathname too long\n");
98		return (-1);
99	}
100
101	if (stat64(fullpath, statbuf) != 0) {
102		(void) fprintf(stderr, "cannot open '%s': %s\n",
103		    fullpath, strerror(errno));
104		return (-1);
105	}
106
107	if ((fp = fopen(MNTTAB, "r")) == NULL) {
108		(void) fprintf(stderr, "cannot open /etc/mnttab\n");
109		return (-1);
110	}
111
112	match = 0;
113	while (getextmntent(fp, &mp, sizeof (mp)) == 0) {
114		if (makedev(mp.mnt_major, mp.mnt_minor) == statbuf->st_dev) {
115			match = 1;
116			break;
117		}
118	}
119
120	if (!match) {
121		(void) fprintf(stderr, "cannot find mountpoint for '%s'\n",
122		    fullpath);
123		return (-1);
124	}
125
126	if (strcmp(mp.mnt_fstype, MNTTYPE_ZFS) != 0) {
127		(void) fprintf(stderr, "invalid path '%s': not a ZFS "
128		    "filesystem\n", fullpath);
129		return (-1);
130	}
131
132	if (strncmp(fullpath, mp.mnt_mountp, strlen(mp.mnt_mountp)) != 0) {
133		(void) fprintf(stderr, "invalid path '%s': mountpoint "
134		    "doesn't match path\n", fullpath);
135		return (-1);
136	}
137
138	(void) strcpy(dataset, mp.mnt_special);
139
140	rel = fullpath + strlen(mp.mnt_mountp);
141	if (rel[0] == '/')
142		rel++;
143	(void) strcpy(relpath, rel);
144
145	return (0);
146}
147
148/*
149 * Convert from a (dataset, path) pair into a (objset, object) pair.  Note that
150 * we grab the object number from the inode number, since looking this up via
151 * libzpool is a real pain.
152 */
153/* ARGSUSED */
154static int
155object_from_path(const char *dataset, const char *path, struct stat64 *statbuf,
156    zinject_record_t *record)
157{
158	objset_t *os;
159	int err;
160
161	/*
162	 * Before doing any libzpool operations, call sync() to ensure that the
163	 * on-disk state is consistent with the in-core state.
164	 */
165	sync();
166
167	if ((err = dmu_objset_open(dataset, DMU_OST_ZFS,
168	    DS_MODE_STANDARD | DS_MODE_READONLY, &os)) != 0) {
169		(void) fprintf(stderr, "cannot open dataset '%s': %s\n",
170		    dataset, strerror(err));
171		return (-1);
172	}
173
174	record->zi_objset = dmu_objset_id(os);
175	record->zi_object = statbuf->st_ino;
176
177	dmu_objset_close(os);
178
179	return (0);
180}
181
182/*
183 * Calculate the real range based on the type, level, and range given.
184 */
185static int
186calculate_range(const char *dataset, err_type_t type, int level, char *range,
187    zinject_record_t *record)
188{
189	objset_t *os = NULL;
190	dnode_t *dn = NULL;
191	int err;
192	int ret = -1;
193
194	/*
195	 * Determine the numeric range from the string.
196	 */
197	if (range == NULL) {
198		/*
199		 * If range is unspecified, set the range to [0,-1], which
200		 * indicates that the whole object should be treated as an
201		 * error.
202		 */
203		record->zi_start = 0;
204		record->zi_end = -1ULL;
205	} else {
206		char *end;
207
208		/* XXX add support for suffixes */
209		record->zi_start = strtoull(range, &end, 10);
210
211
212		if (*end == '\0')
213			record->zi_end = record->zi_start + 1;
214		else if (*end == ',')
215			record->zi_end = strtoull(end + 1, &end, 10);
216
217		if (*end != '\0') {
218			(void) fprintf(stderr, "invalid range '%s': must be "
219			    "a numeric range of the form 'start[,end]'\n",
220			    range);
221			goto out;
222		}
223	}
224
225	switch (type) {
226	case TYPE_DATA:
227		break;
228
229	case TYPE_DNODE:
230		/*
231		 * If this is a request to inject faults into the dnode, then we
232		 * must translate the current (objset,object) pair into an
233		 * offset within the metadnode for the objset.  Specifying any
234		 * kind of range with type 'dnode' is illegal.
235		 */
236		if (range != NULL) {
237			(void) fprintf(stderr, "range cannot be specified when "
238			    "type is 'dnode'\n");
239			goto out;
240		}
241
242		record->zi_start = record->zi_object * sizeof (dnode_phys_t);
243		record->zi_end = record->zi_start + sizeof (dnode_phys_t);
244		record->zi_object = 0;
245		break;
246	}
247
248	/*
249	 * Get the dnode associated with object, so we can calculate the block
250	 * size.
251	 */
252	if ((err = dmu_objset_open(dataset, DMU_OST_ANY,
253	    DS_MODE_STANDARD | DS_MODE_READONLY, &os)) != 0) {
254		(void) fprintf(stderr, "cannot open dataset '%s': %s\n",
255		    dataset, strerror(err));
256		goto out;
257	}
258
259	if (record->zi_object == 0) {
260		dn = os->os->os_meta_dnode;
261	} else {
262		err = dnode_hold(os->os, record->zi_object, FTAG, &dn);
263		if (err != 0) {
264			(void) fprintf(stderr, "failed to hold dnode "
265			    "for object %llu\n",
266			    (u_longlong_t)record->zi_object);
267			goto out;
268		}
269	}
270
271
272	ziprintf("data shift: %d\n", (int)dn->dn_datablkshift);
273	ziprintf(" ind shift: %d\n", (int)dn->dn_indblkshift);
274
275	/*
276	 * Translate range into block IDs.
277	 */
278	if (record->zi_start != 0 || record->zi_end != -1ULL) {
279		record->zi_start >>= dn->dn_datablkshift;
280		record->zi_end >>= dn->dn_datablkshift;
281	}
282
283	/*
284	 * Check level, and then translate level 0 blkids into ranges
285	 * appropriate for level of indirection.
286	 */
287	record->zi_level = level;
288	if (level > 0) {
289		ziprintf("level 0 blkid range: [%llu, %llu]\n",
290		    record->zi_start, record->zi_end);
291
292		if (level >= dn->dn_nlevels) {
293			(void) fprintf(stderr, "level %d exceeds max level "
294			    "of object (%d)\n", level, dn->dn_nlevels - 1);
295			goto out;
296		}
297
298		if (record->zi_start != 0 || record->zi_end != 0) {
299			int shift = dn->dn_indblkshift - SPA_BLKPTRSHIFT;
300
301			for (; level > 0; level--) {
302				record->zi_start >>= shift;
303				record->zi_end >>= shift;
304			}
305		}
306	}
307
308	ret = 0;
309out:
310	if (dn) {
311		if (dn != os->os->os_meta_dnode)
312			dnode_rele(dn, FTAG);
313	}
314	if (os)
315		dmu_objset_close(os);
316
317	return (ret);
318}
319
320int
321translate_record(err_type_t type, const char *object, const char *range,
322    int level, zinject_record_t *record, char *poolname, char *dataset)
323{
324	char path[MAXPATHLEN];
325	char *slash;
326	struct stat64 statbuf;
327	int ret = -1;
328
329	kernel_init(FREAD);
330
331	debug = (getenv("ZINJECT_DEBUG") != NULL);
332
333	ziprintf("translating: %s\n", object);
334
335	if (MOS_TYPE(type)) {
336		/*
337		 * MOS objects are treated specially.
338		 */
339		switch (type) {
340		case TYPE_MOS:
341			record->zi_type = 0;
342			break;
343		case TYPE_MOSDIR:
344			record->zi_type = DMU_OT_OBJECT_DIRECTORY;
345			break;
346		case TYPE_METASLAB:
347			record->zi_type = DMU_OT_OBJECT_ARRAY;
348			break;
349		case TYPE_CONFIG:
350			record->zi_type = DMU_OT_PACKED_NVLIST;
351			break;
352		case TYPE_BPLIST:
353			record->zi_type = DMU_OT_BPLIST;
354			break;
355		case TYPE_SPACEMAP:
356			record->zi_type = DMU_OT_SPACE_MAP;
357			break;
358		case TYPE_ERRLOG:
359			record->zi_type = DMU_OT_ERROR_LOG;
360			break;
361		}
362
363		dataset[0] = '\0';
364		(void) strcpy(poolname, object);
365		return (0);
366	}
367
368	/*
369	 * Convert a full path into a (dataset, file) pair.
370	 */
371	if (parse_pathname(object, dataset, path, &statbuf) != 0)
372		goto err;
373
374	ziprintf("   dataset: %s\n", dataset);
375	ziprintf("      path: %s\n", path);
376
377	/*
378	 * Convert (dataset, file) into (objset, object)
379	 */
380	if (object_from_path(dataset, path, &statbuf, record) != 0)
381		goto err;
382
383	ziprintf("raw objset: %llu\n", record->zi_objset);
384	ziprintf("raw object: %llu\n", record->zi_object);
385
386	/*
387	 * For the given object, calculate the real (type, level, range)
388	 */
389	if (calculate_range(dataset, type, level, (char *)range, record) != 0)
390		goto err;
391
392	ziprintf("    objset: %llu\n", record->zi_objset);
393	ziprintf("    object: %llu\n", record->zi_object);
394	if (record->zi_start == 0 &&
395	    record->zi_end == -1ULL)
396		ziprintf("     range: all\n");
397	else
398		ziprintf("     range: [%llu, %llu]\n", record->zi_start,
399		    record->zi_end);
400
401	/*
402	 * Copy the pool name
403	 */
404	(void) strcpy(poolname, dataset);
405	if ((slash = strchr(poolname, '/')) != NULL)
406		*slash = '\0';
407
408	ret = 0;
409
410err:
411	kernel_fini();
412	return (ret);
413}
414
415int
416translate_raw(const char *str, zinject_record_t *record)
417{
418	/*
419	 * A raw bookmark of the form objset:object:level:blkid, where each
420	 * number is a hexidecimal value.
421	 */
422	if (sscanf(str, "%llx:%llx:%x:%llx", (u_longlong_t *)&record->zi_objset,
423	    (u_longlong_t *)&record->zi_object, &record->zi_level,
424	    (u_longlong_t *)&record->zi_start) != 4) {
425		(void) fprintf(stderr, "bad raw spec '%s': must be of the form "
426		    "'objset:object:level:blkid'\n", str);
427		return (-1);
428	}
429
430	record->zi_end = record->zi_start;
431
432	return (0);
433}
434
435int
436translate_device(const char *pool, const char *device, err_type_t label_type,
437    zinject_record_t *record)
438{
439	char *end;
440	zpool_handle_t *zhp;
441	nvlist_t *tgt;
442	boolean_t isspare, iscache;
443
444	/*
445	 * Given a device name or GUID, create an appropriate injection record
446	 * with zi_guid set.
447	 */
448	if ((zhp = zpool_open(g_zfs, pool)) == NULL)
449		return (-1);
450
451	record->zi_guid = strtoull(device, &end, 16);
452	if (record->zi_guid == 0 || *end != '\0') {
453		tgt = zpool_find_vdev(zhp, device, &isspare, &iscache);
454
455		if (tgt == NULL) {
456			(void) fprintf(stderr, "cannot find device '%s' in "
457			    "pool '%s'\n", device, pool);
458			return (-1);
459		}
460
461		verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID,
462		    &record->zi_guid) == 0);
463	}
464
465	switch (label_type) {
466	case TYPE_LABEL_UBERBLOCK:
467		record->zi_start = offsetof(vdev_label_t, vl_uberblock[0]);
468		record->zi_end = record->zi_start + VDEV_UBERBLOCK_RING - 1;
469		break;
470	case TYPE_LABEL_NVLIST:
471		record->zi_start = offsetof(vdev_label_t, vl_vdev_phys);
472		record->zi_end = record->zi_start + VDEV_PHYS_SIZE - 1;
473		break;
474	}
475	return (0);
476}
477