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