xref: /freebsd/sys/contrib/openzfs/cmd/zinject/translate.c (revision 5f4c09dd85bff675e0ca63c55ea3c517e0fddfcc)
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 https://opensource.org/licenses/CDDL-1.0.
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 <string.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) strlcpy(dataset, mp.mnt_special, MAXNAMELEN);
119 
120 	rel = fullpath + strlen(mp.mnt_mountp);
121 	if (rel[0] == '/')
122 		rel++;
123 	(void) strlcpy(relpath, rel, MAXPATHLEN);
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 
190 	case TYPE_DATA:
191 		break;
192 
193 	case TYPE_DNODE:
194 		/*
195 		 * If this is a request to inject faults into the dnode, then we
196 		 * must translate the current (objset,object) pair into an
197 		 * offset within the metadnode for the objset.  Specifying any
198 		 * kind of range with type 'dnode' is illegal.
199 		 */
200 		if (range != NULL) {
201 			(void) fprintf(stderr, "range cannot be specified when "
202 			    "type is 'dnode'\n");
203 			return (-1);
204 		}
205 
206 		record->zi_start = record->zi_object * sizeof (dnode_phys_t);
207 		record->zi_end = record->zi_start + sizeof (dnode_phys_t);
208 		record->zi_object = 0;
209 		break;
210 	}
211 
212 	record->zi_level = level;
213 
214 	return (0);
215 }
216 
217 int
218 translate_record(err_type_t type, const char *object, const char *range,
219     int level, zinject_record_t *record, char *poolname, char *dataset)
220 {
221 	char path[MAXPATHLEN];
222 	char *slash;
223 	struct stat64 statbuf;
224 	int ret = -1;
225 
226 	debug = (getenv("ZINJECT_DEBUG") != NULL);
227 
228 	ziprintf("translating: %s\n", object);
229 
230 	if (MOS_TYPE(type)) {
231 		/*
232 		 * MOS objects are treated specially.
233 		 */
234 		switch (type) {
235 		default:
236 			break;
237 		case TYPE_MOS:
238 			record->zi_type = 0;
239 			break;
240 		case TYPE_MOSDIR:
241 			record->zi_type = DMU_OT_OBJECT_DIRECTORY;
242 			break;
243 		case TYPE_METASLAB:
244 			record->zi_type = DMU_OT_OBJECT_ARRAY;
245 			break;
246 		case TYPE_CONFIG:
247 			record->zi_type = DMU_OT_PACKED_NVLIST;
248 			break;
249 		case TYPE_BPOBJ:
250 			record->zi_type = DMU_OT_BPOBJ;
251 			break;
252 		case TYPE_SPACEMAP:
253 			record->zi_type = DMU_OT_SPACE_MAP;
254 			break;
255 		case TYPE_ERRLOG:
256 			record->zi_type = DMU_OT_ERROR_LOG;
257 			break;
258 		}
259 
260 		dataset[0] = '\0';
261 		(void) strlcpy(poolname, object, MAXNAMELEN);
262 		return (0);
263 	}
264 
265 	/*
266 	 * Convert a full path into a (dataset, file) pair.
267 	 */
268 	if (parse_pathname(object, dataset, path, &statbuf) != 0)
269 		goto err;
270 
271 	ziprintf("   dataset: %s\n", dataset);
272 	ziprintf("      path: %s\n", path);
273 
274 	/*
275 	 * Convert (dataset, file) into (objset, object)
276 	 */
277 	if (object_from_path(dataset, statbuf.st_ino, record) != 0)
278 		goto err;
279 
280 	ziprintf("raw objset: %llu\n", record->zi_objset);
281 	ziprintf("raw object: %llu\n", record->zi_object);
282 
283 	/*
284 	 * For the given object, initialize the range in bytes
285 	 */
286 	if (initialize_range(type, level, (char *)range, record) != 0)
287 		goto err;
288 
289 	ziprintf("    objset: %llu\n", record->zi_objset);
290 	ziprintf("    object: %llu\n", record->zi_object);
291 	if (record->zi_start == 0 &&
292 	    record->zi_end == -1ULL)
293 		ziprintf("     range: all\n");
294 	else
295 		ziprintf("     range: [%llu, %llu]\n", record->zi_start,
296 		    record->zi_end);
297 
298 	/*
299 	 * Copy the pool name
300 	 */
301 	(void) strlcpy(poolname, dataset, MAXNAMELEN);
302 	if ((slash = strchr(poolname, '/')) != NULL)
303 		*slash = '\0';
304 
305 	ret = 0;
306 
307 err:
308 	return (ret);
309 }
310 
311 int
312 translate_raw(const char *str, zinject_record_t *record)
313 {
314 	/*
315 	 * A raw bookmark of the form objset:object:level:blkid, where each
316 	 * number is a hexadecimal value.
317 	 */
318 	if (sscanf(str, "%llx:%llx:%x:%llx", (u_longlong_t *)&record->zi_objset,
319 	    (u_longlong_t *)&record->zi_object, &record->zi_level,
320 	    (u_longlong_t *)&record->zi_start) != 4) {
321 		(void) fprintf(stderr, "bad raw spec '%s': must be of the form "
322 		    "'objset:object:level:blkid'\n", str);
323 		return (-1);
324 	}
325 
326 	record->zi_end = record->zi_start;
327 
328 	return (0);
329 }
330 
331 int
332 translate_device(const char *pool, const char *device, err_type_t label_type,
333     zinject_record_t *record)
334 {
335 	char *end;
336 	zpool_handle_t *zhp;
337 	nvlist_t *tgt;
338 	boolean_t isspare, iscache;
339 
340 	/*
341 	 * Given a device name or GUID, create an appropriate injection record
342 	 * with zi_guid set.
343 	 */
344 	if ((zhp = zpool_open(g_zfs, pool)) == NULL)
345 		return (-1);
346 
347 	record->zi_guid = strtoull(device, &end, 0);
348 	if (record->zi_guid == 0 || *end != '\0') {
349 		tgt = zpool_find_vdev(zhp, device, &isspare, &iscache, NULL);
350 
351 		if (tgt == NULL) {
352 			(void) fprintf(stderr, "cannot find device '%s' in "
353 			    "pool '%s'\n", device, pool);
354 			zpool_close(zhp);
355 			return (-1);
356 		}
357 
358 		verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID,
359 		    &record->zi_guid) == 0);
360 	}
361 
362 	/*
363 	 * Device faults can take on three different forms:
364 	 * 1). delayed or hanging I/O
365 	 * 2). zfs label faults
366 	 * 3). generic disk faults
367 	 */
368 	if (record->zi_timer != 0) {
369 		record->zi_cmd = ZINJECT_DELAY_IO;
370 	} else if (label_type != TYPE_INVAL) {
371 		record->zi_cmd = ZINJECT_LABEL_FAULT;
372 	} else {
373 		record->zi_cmd = ZINJECT_DEVICE_FAULT;
374 	}
375 
376 	switch (label_type) {
377 	default:
378 		break;
379 	case TYPE_LABEL_UBERBLOCK:
380 		record->zi_start = offsetof(vdev_label_t, vl_uberblock[0]);
381 		record->zi_end = record->zi_start + VDEV_UBERBLOCK_RING - 1;
382 		break;
383 	case TYPE_LABEL_NVLIST:
384 		record->zi_start = offsetof(vdev_label_t, vl_vdev_phys);
385 		record->zi_end = record->zi_start + VDEV_PHYS_SIZE - 1;
386 		break;
387 	case TYPE_LABEL_PAD1:
388 		record->zi_start = offsetof(vdev_label_t, vl_pad1);
389 		record->zi_end = record->zi_start + VDEV_PAD_SIZE - 1;
390 		break;
391 	case TYPE_LABEL_PAD2:
392 		record->zi_start = offsetof(vdev_label_t, vl_be);
393 		record->zi_end = record->zi_start + VDEV_PAD_SIZE - 1;
394 		break;
395 	}
396 	zpool_close(zhp);
397 	return (0);
398 }
399