xref: /freebsd/sys/contrib/openzfs/cmd/zinject/translate.c (revision cfd6422a5217410fbd66f7a7a8a64d9d85e61229)
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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright (c) 2012, 2020 by Delphix. All rights reserved.
24  */
25 
26 #include <libzfs.h>
27 
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <stdarg.h>
31 #include <stddef.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <strings.h>
35 #include <sys/file.h>
36 #include <sys/mntent.h>
37 #include <sys/mnttab.h>
38 #include <sys/param.h>
39 #include <sys/stat.h>
40 
41 #include <sys/dmu.h>
42 #include <sys/dmu_objset.h>
43 #include <sys/dnode.h>
44 #include <sys/vdev_impl.h>
45 
46 #include <sys/mkdev.h>
47 
48 #include "zinject.h"
49 
50 static int debug;
51 
52 static void
53 ziprintf(const char *fmt, ...)
54 {
55 	va_list ap;
56 
57 	if (!debug)
58 		return;
59 
60 	va_start(ap, fmt);
61 	(void) vprintf(fmt, ap);
62 	va_end(ap);
63 }
64 
65 static void
66 compress_slashes(const char *src, char *dest)
67 {
68 	while (*src != '\0') {
69 		*dest = *src++;
70 		while (*dest == '/' && *src == '/')
71 			++src;
72 		++dest;
73 	}
74 	*dest = '\0';
75 }
76 
77 /*
78  * Given a full path to a file, translate into a dataset name and a relative
79  * path within the dataset.  'dataset' must be at least MAXNAMELEN characters,
80  * and 'relpath' must be at least MAXPATHLEN characters.  We also pass a stat64
81  * buffer, which we need later to get the object ID.
82  */
83 static int
84 parse_pathname(const char *inpath, char *dataset, char *relpath,
85     struct stat64 *statbuf)
86 {
87 	struct extmnttab mp;
88 	const char *rel;
89 	char fullpath[MAXPATHLEN];
90 
91 	compress_slashes(inpath, fullpath);
92 
93 	if (fullpath[0] != '/') {
94 		(void) fprintf(stderr, "invalid object '%s': must be full "
95 		    "path\n", fullpath);
96 		usage();
97 		return (-1);
98 	}
99 
100 	if (getextmntent(fullpath, &mp, statbuf) != 0) {
101 		(void) fprintf(stderr, "cannot find mountpoint for '%s'\n",
102 		    fullpath);
103 		return (-1);
104 	}
105 
106 	if (strcmp(mp.mnt_fstype, MNTTYPE_ZFS) != 0) {
107 		(void) fprintf(stderr, "invalid path '%s': not a ZFS "
108 		    "filesystem\n", fullpath);
109 		return (-1);
110 	}
111 
112 	if (strncmp(fullpath, mp.mnt_mountp, strlen(mp.mnt_mountp)) != 0) {
113 		(void) fprintf(stderr, "invalid path '%s': mountpoint "
114 		    "doesn't match path\n", fullpath);
115 		return (-1);
116 	}
117 
118 	(void) strcpy(dataset, mp.mnt_special);
119 
120 	rel = fullpath + strlen(mp.mnt_mountp);
121 	if (rel[0] == '/')
122 		rel++;
123 	(void) strcpy(relpath, rel);
124 
125 	return (0);
126 }
127 
128 /*
129  * Convert from a dataset to a objset id. Note that
130  * we grab the object number from the inode number.
131  */
132 static int
133 object_from_path(const char *dataset, uint64_t object, zinject_record_t *record)
134 {
135 	zfs_handle_t *zhp;
136 
137 	if ((zhp = zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET)) == NULL)
138 		return (-1);
139 
140 	record->zi_objset = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
141 	record->zi_object = object;
142 
143 	zfs_close(zhp);
144 
145 	return (0);
146 }
147 
148 /*
149  * Initialize the range based on the type, level, and range given.
150  */
151 static int
152 initialize_range(err_type_t type, int level, char *range,
153     zinject_record_t *record)
154 {
155 	/*
156 	 * Determine the numeric range from the string.
157 	 */
158 	if (range == NULL) {
159 		/*
160 		 * If range is unspecified, set the range to [0,-1], which
161 		 * indicates that the whole object should be treated as an
162 		 * error.
163 		 */
164 		record->zi_start = 0;
165 		record->zi_end = -1ULL;
166 	} else {
167 		char *end;
168 
169 		/* XXX add support for suffixes */
170 		record->zi_start = strtoull(range, &end, 10);
171 
172 
173 		if (*end == '\0')
174 			record->zi_end = record->zi_start + 1;
175 		else if (*end == ',')
176 			record->zi_end = strtoull(end + 1, &end, 10);
177 
178 		if (*end != '\0') {
179 			(void) fprintf(stderr, "invalid range '%s': must be "
180 			    "a numeric range of the form 'start[,end]'\n",
181 			    range);
182 			return (-1);
183 		}
184 	}
185 
186 	switch (type) {
187 	default:
188 		break;
189 	case TYPE_DATA:
190 		break;
191 
192 	case TYPE_DNODE:
193 		/*
194 		 * If this is a request to inject faults into the dnode, then we
195 		 * must translate the current (objset,object) pair into an
196 		 * offset within the metadnode for the objset.  Specifying any
197 		 * kind of range with type 'dnode' is illegal.
198 		 */
199 		if (range != NULL) {
200 			(void) fprintf(stderr, "range cannot be specified when "
201 			    "type is 'dnode'\n");
202 			return (-1);
203 		}
204 
205 		record->zi_start = record->zi_object * sizeof (dnode_phys_t);
206 		record->zi_end = record->zi_start + sizeof (dnode_phys_t);
207 		record->zi_object = 0;
208 		break;
209 	}
210 
211 	record->zi_level = level;
212 
213 	return (0);
214 }
215 
216 int
217 translate_record(err_type_t type, const char *object, const char *range,
218     int level, zinject_record_t *record, char *poolname, char *dataset)
219 {
220 	char path[MAXPATHLEN];
221 	char *slash;
222 	struct stat64 statbuf;
223 	int ret = -1;
224 
225 	debug = (getenv("ZINJECT_DEBUG") != NULL);
226 
227 	ziprintf("translating: %s\n", object);
228 
229 	if (MOS_TYPE(type)) {
230 		/*
231 		 * MOS objects are treated specially.
232 		 */
233 		switch (type) {
234 		default:
235 			break;
236 		case TYPE_MOS:
237 			record->zi_type = 0;
238 			break;
239 		case TYPE_MOSDIR:
240 			record->zi_type = DMU_OT_OBJECT_DIRECTORY;
241 			break;
242 		case TYPE_METASLAB:
243 			record->zi_type = DMU_OT_OBJECT_ARRAY;
244 			break;
245 		case TYPE_CONFIG:
246 			record->zi_type = DMU_OT_PACKED_NVLIST;
247 			break;
248 		case TYPE_BPOBJ:
249 			record->zi_type = DMU_OT_BPOBJ;
250 			break;
251 		case TYPE_SPACEMAP:
252 			record->zi_type = DMU_OT_SPACE_MAP;
253 			break;
254 		case TYPE_ERRLOG:
255 			record->zi_type = DMU_OT_ERROR_LOG;
256 			break;
257 		}
258 
259 		dataset[0] = '\0';
260 		(void) strcpy(poolname, object);
261 		return (0);
262 	}
263 
264 	/*
265 	 * Convert a full path into a (dataset, file) pair.
266 	 */
267 	if (parse_pathname(object, dataset, path, &statbuf) != 0)
268 		goto err;
269 
270 	ziprintf("   dataset: %s\n", dataset);
271 	ziprintf("      path: %s\n", path);
272 
273 	/*
274 	 * Convert (dataset, file) into (objset, object)
275 	 */
276 	if (object_from_path(dataset, statbuf.st_ino, record) != 0)
277 		goto err;
278 
279 	ziprintf("raw objset: %llu\n", record->zi_objset);
280 	ziprintf("raw object: %llu\n", record->zi_object);
281 
282 	/*
283 	 * For the given object, initialize the range in bytes
284 	 */
285 	if (initialize_range(type, level, (char *)range, record) != 0)
286 		goto err;
287 
288 	ziprintf("    objset: %llu\n", record->zi_objset);
289 	ziprintf("    object: %llu\n", record->zi_object);
290 	if (record->zi_start == 0 &&
291 	    record->zi_end == -1ULL)
292 		ziprintf("     range: all\n");
293 	else
294 		ziprintf("     range: [%llu, %llu]\n", record->zi_start,
295 		    record->zi_end);
296 
297 	/*
298 	 * Copy the pool name
299 	 */
300 	(void) strcpy(poolname, dataset);
301 	if ((slash = strchr(poolname, '/')) != NULL)
302 		*slash = '\0';
303 
304 	ret = 0;
305 
306 err:
307 	return (ret);
308 }
309 
310 int
311 translate_raw(const char *str, zinject_record_t *record)
312 {
313 	/*
314 	 * A raw bookmark of the form objset:object:level:blkid, where each
315 	 * number is a hexadecimal value.
316 	 */
317 	if (sscanf(str, "%llx:%llx:%x:%llx", (u_longlong_t *)&record->zi_objset,
318 	    (u_longlong_t *)&record->zi_object, &record->zi_level,
319 	    (u_longlong_t *)&record->zi_start) != 4) {
320 		(void) fprintf(stderr, "bad raw spec '%s': must be of the form "
321 		    "'objset:object:level:blkid'\n", str);
322 		return (-1);
323 	}
324 
325 	record->zi_end = record->zi_start;
326 
327 	return (0);
328 }
329 
330 int
331 translate_device(const char *pool, const char *device, err_type_t label_type,
332     zinject_record_t *record)
333 {
334 	char *end;
335 	zpool_handle_t *zhp;
336 	nvlist_t *tgt;
337 	boolean_t isspare, iscache;
338 
339 	/*
340 	 * Given a device name or GUID, create an appropriate injection record
341 	 * with zi_guid set.
342 	 */
343 	if ((zhp = zpool_open(g_zfs, pool)) == NULL)
344 		return (-1);
345 
346 	record->zi_guid = strtoull(device, &end, 0);
347 	if (record->zi_guid == 0 || *end != '\0') {
348 		tgt = zpool_find_vdev(zhp, device, &isspare, &iscache, NULL);
349 
350 		if (tgt == NULL) {
351 			(void) fprintf(stderr, "cannot find device '%s' in "
352 			    "pool '%s'\n", device, pool);
353 			zpool_close(zhp);
354 			return (-1);
355 		}
356 
357 		verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID,
358 		    &record->zi_guid) == 0);
359 	}
360 
361 	/*
362 	 * Device faults can take on three different forms:
363 	 * 1). delayed or hanging I/O
364 	 * 2). zfs label faults
365 	 * 3). generic disk faults
366 	 */
367 	if (record->zi_timer != 0) {
368 		record->zi_cmd = ZINJECT_DELAY_IO;
369 	} else if (label_type != TYPE_INVAL) {
370 		record->zi_cmd = ZINJECT_LABEL_FAULT;
371 	} else {
372 		record->zi_cmd = ZINJECT_DEVICE_FAULT;
373 	}
374 
375 	switch (label_type) {
376 	default:
377 		break;
378 	case TYPE_LABEL_UBERBLOCK:
379 		record->zi_start = offsetof(vdev_label_t, vl_uberblock[0]);
380 		record->zi_end = record->zi_start + VDEV_UBERBLOCK_RING - 1;
381 		break;
382 	case TYPE_LABEL_NVLIST:
383 		record->zi_start = offsetof(vdev_label_t, vl_vdev_phys);
384 		record->zi_end = record->zi_start + VDEV_PHYS_SIZE - 1;
385 		break;
386 	case TYPE_LABEL_PAD1:
387 		record->zi_start = offsetof(vdev_label_t, vl_pad1);
388 		record->zi_end = record->zi_start + VDEV_PAD_SIZE - 1;
389 		break;
390 	case TYPE_LABEL_PAD2:
391 		record->zi_start = offsetof(vdev_label_t, vl_be);
392 		record->zi_end = record->zi_start + VDEV_PAD_SIZE - 1;
393 		break;
394 	}
395 	zpool_close(zhp);
396 	return (0);
397 }
398