xref: /illumos-gate/usr/src/cmd/stat/common/acquire_iodevs.c (revision 60405de4d8688d96dd05157c28db3ade5c9bc234)
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 2006 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 "statcommon.h"
29 #include "dsr.h"
30 
31 #include <sys/dklabel.h>
32 #include <sys/dktp/fdisk.h>
33 #include <stdlib.h>
34 #include <stdarg.h>
35 #include <unistd.h>
36 #include <strings.h>
37 #include <errno.h>
38 #include <limits.h>
39 
40 static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev);
41 
42 static struct iodev_snapshot *
43 make_controller(int cid)
44 {
45 	struct iodev_snapshot *new;
46 
47 	new = safe_alloc(sizeof (struct iodev_snapshot));
48 	(void) memset(new, 0, sizeof (struct iodev_snapshot));
49 	new->is_type = IODEV_CONTROLLER;
50 	new->is_id.id = cid;
51 	new->is_parent_id.id = IODEV_NO_ID;
52 
53 	(void) snprintf(new->is_name, sizeof (new->is_name), "c%d", cid);
54 
55 	return (new);
56 }
57 
58 static struct iodev_snapshot *
59 find_iodev_by_name(struct iodev_snapshot *list, const char *name)
60 {
61 	struct iodev_snapshot *pos;
62 	struct iodev_snapshot *pos2;
63 
64 	for (pos = list; pos; pos = pos->is_next) {
65 		if (strcmp(pos->is_name, name) == 0)
66 			return (pos);
67 
68 		pos2 = find_iodev_by_name(pos->is_children, name);
69 		if (pos2 != NULL)
70 			return (pos2);
71 	}
72 
73 	return (NULL);
74 }
75 
76 static enum iodev_type
77 parent_iodev_type(enum iodev_type type)
78 {
79 	switch (type) {
80 		case IODEV_CONTROLLER: return (0);
81 		case IODEV_NFS: return (0);
82 		case IODEV_TAPE: return (0);
83 		case IODEV_IOPATH: return (IODEV_DISK);
84 		case IODEV_DISK: return (IODEV_CONTROLLER);
85 		case IODEV_PARTITION: return (IODEV_DISK);
86 	}
87 	return (IODEV_UNKNOWN);
88 }
89 
90 static int
91 id_match(struct iodev_id *id1, struct iodev_id *id2)
92 {
93 	return (id1->id == id2->id &&
94 		strcmp(id1->tid, id2->tid) == 0);
95 }
96 
97 static struct iodev_snapshot *
98 find_parent(struct snapshot *ss, struct iodev_snapshot *iodev)
99 {
100 	enum iodev_type parent_type = parent_iodev_type(iodev->is_type);
101 	struct iodev_snapshot *pos;
102 	struct iodev_snapshot *pos2;
103 
104 	if (parent_type == 0 || parent_type == IODEV_UNKNOWN)
105 		return (NULL);
106 
107 	if (iodev->is_parent_id.id == IODEV_NO_ID &&
108 	    iodev->is_parent_id.tid[0] == '\0')
109 		return (NULL);
110 
111 	if (parent_type == IODEV_CONTROLLER) {
112 		for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
113 			if (pos->is_type != IODEV_CONTROLLER)
114 				continue;
115 			if (pos->is_id.id != iodev->is_parent_id.id)
116 				continue;
117 			return (pos);
118 		}
119 
120 		if (!(ss->s_types & SNAP_CONTROLLERS))
121 			return (NULL);
122 
123 		pos = make_controller(iodev->is_parent_id.id);
124 		insert_iodev(ss, pos);
125 		return (pos);
126 	}
127 
128 	/* IODEV_DISK parent */
129 	for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
130 		if (id_match(&iodev->is_parent_id, &pos->is_id) &&
131 		    pos->is_type == IODEV_DISK)
132 			return (pos);
133 		if (pos->is_type != IODEV_CONTROLLER)
134 			continue;
135 		for (pos2 = pos->is_children; pos2; pos2 = pos2->is_next) {
136 			if (pos2->is_type != IODEV_DISK)
137 				continue;
138 			if (id_match(&iodev->is_parent_id, &pos2->is_id))
139 				return (pos2);
140 		}
141 	}
142 
143 	return (NULL);
144 }
145 
146 static void
147 list_del(struct iodev_snapshot **list, struct iodev_snapshot *pos)
148 {
149 	if (*list == pos)
150 		*list = pos->is_next;
151 	if (pos->is_next)
152 		pos->is_next->is_prev = pos->is_prev;
153 	if (pos->is_prev)
154 		pos->is_prev->is_next = pos->is_next;
155 	pos->is_prev = pos->is_next = NULL;
156 }
157 
158 static void
159 insert_before(struct iodev_snapshot **list, struct iodev_snapshot *pos,
160     struct iodev_snapshot *new)
161 {
162 	if (pos == NULL) {
163 		new->is_prev = new->is_next = NULL;
164 		*list = new;
165 		return;
166 	}
167 
168 	new->is_next = pos;
169 	new->is_prev = pos->is_prev;
170 	if (pos->is_prev)
171 		pos->is_prev->is_next = new;
172 	else
173 		*list = new;
174 	pos->is_prev = new;
175 }
176 
177 static void
178 insert_after(struct iodev_snapshot **list, struct iodev_snapshot *pos,
179     struct iodev_snapshot *new)
180 {
181 	if (pos == NULL) {
182 		new->is_prev = new->is_next = NULL;
183 		*list = new;
184 		return;
185 	}
186 
187 	new->is_next = pos->is_next;
188 	new->is_prev = pos;
189 	if (pos->is_next)
190 		pos->is_next->is_prev = new;
191 	pos->is_next = new;
192 }
193 
194 static void
195 insert_into(struct iodev_snapshot **list, struct iodev_snapshot *iodev)
196 {
197 	struct iodev_snapshot *tmp = *list;
198 	if (*list == NULL) {
199 		*list = iodev;
200 		return;
201 	}
202 
203 	for (;;) {
204 		if (iodev_cmp(tmp, iodev) > 0) {
205 			insert_before(list, tmp, iodev);
206 			return;
207 		}
208 
209 		if (tmp->is_next == NULL)
210 			break;
211 
212 		tmp = tmp->is_next;
213 	}
214 
215 	insert_after(list, tmp, iodev);
216 }
217 
218 static int
219 disk_or_partition(enum iodev_type type)
220 {
221 	return (type == IODEV_DISK || type == IODEV_PARTITION);
222 }
223 
224 static void
225 insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev)
226 {
227 	struct iodev_snapshot *parent = find_parent(ss, iodev);
228 	struct iodev_snapshot **list;
229 
230 	if (parent != NULL) {
231 		list = &parent->is_children;
232 		parent->is_nr_children++;
233 	} else {
234 		list = &ss->s_iodevs;
235 		ss->s_nr_iodevs++;
236 	}
237 
238 	insert_into(list, iodev);
239 }
240 
241 static int
242 iodev_match(struct iodev_snapshot *dev, struct iodev_filter *df)
243 {
244 	size_t i;
245 	int is_floppy = (strncmp(dev->is_name, "fd", 2) == 0);
246 
247 	/* no filter, pass */
248 	if (df == NULL)
249 		return (1);
250 
251 	/* no filtered names, pass if not floppy and skipped */
252 	if (df->if_nr_names == NULL)
253 		return (!(df->if_skip_floppy && is_floppy));
254 
255 	for (i = 0; i < df->if_nr_names; i++) {
256 		if (strcmp(dev->is_name, df->if_names[i]) == 0)
257 			return (1);
258 		if (dev->is_pretty != NULL &&
259 		    strcmp(dev->is_pretty, df->if_names[i]) == 0)
260 			return (1);
261 	}
262 
263 	/* not found in specified names, fail match */
264 	return (0);
265 }
266 
267 /* select which I/O devices to collect stats for */
268 static void
269 choose_iodevs(struct snapshot *ss, struct iodev_snapshot *iodevs,
270     struct iodev_filter *df)
271 {
272 	struct iodev_snapshot *pos = iodevs;
273 	int nr_iodevs = df ? df->if_max_iodevs : UNLIMITED_IODEVS;
274 
275 	if (nr_iodevs == UNLIMITED_IODEVS)
276 		nr_iodevs = INT_MAX;
277 
278 	while (pos && nr_iodevs) {
279 		struct iodev_snapshot *tmp = pos;
280 		pos = pos->is_next;
281 
282 		if (!iodev_match(tmp, df))
283 			continue;
284 
285 		list_del(&iodevs, tmp);
286 		insert_iodev(ss, tmp);
287 
288 		--nr_iodevs;
289 	}
290 
291 	pos = iodevs;
292 
293 	/* now insert any iodevs into the remaining slots */
294 	while (pos && nr_iodevs) {
295 		struct iodev_snapshot *tmp = pos;
296 		pos = pos->is_next;
297 
298 		if (df && df->if_skip_floppy &&
299 			strncmp(tmp->is_name, "fd", 2) == 0)
300 			continue;
301 
302 		list_del(&iodevs, tmp);
303 		insert_iodev(ss, tmp);
304 
305 		--nr_iodevs;
306 	}
307 
308 	/* clear the unwanted ones */
309 	pos = iodevs;
310 	while (pos) {
311 		struct iodev_snapshot *tmp = pos;
312 		pos = pos->is_next;
313 		free_iodev(tmp);
314 	}
315 }
316 
317 static int
318 collate_controller(struct iodev_snapshot *controller,
319     struct iodev_snapshot *disk)
320 {
321 	controller->is_stats.nread += disk->is_stats.nread;
322 	controller->is_stats.nwritten += disk->is_stats.nwritten;
323 	controller->is_stats.reads += disk->is_stats.reads;
324 	controller->is_stats.writes += disk->is_stats.writes;
325 	controller->is_stats.wtime += disk->is_stats.wtime;
326 	controller->is_stats.wlentime += disk->is_stats.wlentime;
327 	controller->is_stats.rtime += disk->is_stats.rtime;
328 	controller->is_stats.rlentime += disk->is_stats.rlentime;
329 	controller->is_crtime += disk->is_crtime;
330 	controller->is_snaptime += disk->is_snaptime;
331 	if (kstat_add(&disk->is_errors, &controller->is_errors))
332 		return (errno);
333 	return (0);
334 }
335 
336 static int
337 acquire_iodev_stats(struct iodev_snapshot *list, kstat_ctl_t *kc)
338 {
339 	struct iodev_snapshot *pos;
340 	int err = 0;
341 
342 	for (pos = list; pos; pos = pos->is_next) {
343 		/* controllers don't have stats (yet) */
344 		if (pos->is_ksp != NULL) {
345 			if (kstat_read(kc, pos->is_ksp, &pos->is_stats) == -1)
346 				return (errno);
347 			/* make sure crtime/snaptime is updated */
348 			pos->is_crtime = pos->is_ksp->ks_crtime;
349 			pos->is_snaptime = pos->is_ksp->ks_snaptime;
350 		}
351 
352 		if ((err = acquire_iodev_stats(pos->is_children, kc)))
353 			return (err);
354 
355 		if (pos->is_type == IODEV_CONTROLLER) {
356 			struct iodev_snapshot *pos2 = pos->is_children;
357 
358 			for (; pos2; pos2 = pos2->is_next) {
359 				if ((err = collate_controller(pos, pos2)))
360 					return (err);
361 			}
362 		}
363 	}
364 
365 	return (0);
366 }
367 
368 static int
369 acquire_iodev_errors(struct snapshot *ss, kstat_ctl_t *kc)
370 {
371 	kstat_t *ksp;
372 
373 	if (!(ss->s_types && SNAP_IODEV_ERRORS))
374 		return (0);
375 
376 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
377 		char kstat_name[KSTAT_STRLEN];
378 		char *dname = kstat_name;
379 		char *ename = ksp->ks_name;
380 		struct iodev_snapshot *iodev;
381 
382 		if (ksp->ks_type != KSTAT_TYPE_NAMED)
383 			continue;
384 		if (strncmp(ksp->ks_class, "device_error", 12) != 0 &&
385 		    strncmp(ksp->ks_class, "iopath_error", 12) != 0)
386 			continue;
387 
388 		/*
389 		 * Some drivers may not follow the naming convention
390 		 * for error kstats (i.e., drivername,err) so
391 		 * be sure we don't walk off the end.
392 		 */
393 		while (*ename && *ename != ',') {
394 			*dname = *ename;
395 			dname++;
396 			ename++;
397 		}
398 		*dname = '\0';
399 
400 		iodev = find_iodev_by_name(ss->s_iodevs, kstat_name);
401 
402 		if (iodev == NULL)
403 			continue;
404 
405 		if (kstat_read(kc, ksp, NULL) == -1)
406 			return (errno);
407 		if (kstat_copy(ksp, &iodev->is_errors) == -1)
408 			return (errno);
409 	}
410 
411 	return (0);
412 }
413 
414 static void
415 get_ids(struct iodev_snapshot *iodev, const char *pretty)
416 {
417 	int ctr, disk, slice, ret;
418 	char *target;
419 	const char *p1;
420 	const char *p2;
421 
422 	if (pretty == NULL)
423 		return;
424 
425 	if (sscanf(pretty, "c%d", &ctr) != 1)
426 		return;
427 
428 	p1 = pretty;
429 	while (*p1 && *p1 != 't')
430 		++p1;
431 
432 	if (!*p1)
433 		return;
434 	++p1;
435 
436 	p2 = p1;
437 	while (*p2 && *p2 != 'd')
438 		++p2;
439 
440 	if (!*p2 || p2 == p1)
441 		return;
442 
443 	target = safe_alloc(1 + p2 - p1);
444 	(void) strlcpy(target, p1, 1 + p2 - p1);
445 
446 	ret = sscanf(p2, "d%d%*[sp]%d", &disk, &slice);
447 
448 	if (ret == 2 && iodev->is_type == IODEV_PARTITION) {
449 		iodev->is_id.id = slice;
450 		iodev->is_parent_id.id = disk;
451 		(void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN);
452 	} else if (ret == 1) {
453 		if (iodev->is_type == IODEV_DISK) {
454 			iodev->is_id.id = disk;
455 			(void) strlcpy(iodev->is_id.tid, target, KSTAT_STRLEN);
456 			iodev->is_parent_id.id = ctr;
457 		} else if (iodev->is_type == IODEV_IOPATH) {
458 			iodev->is_parent_id.id = disk;
459 			(void) strlcpy(iodev->is_parent_id.tid,
460 				target, KSTAT_STRLEN);
461 		}
462 	}
463 
464 	free(target);
465 }
466 
467 static char *
468 get_slice(int partition, disk_list_t *dl)
469 {
470 	char *tmpbuf;
471 	size_t tmplen;
472 
473 	if (!(dl->flags & SLICES_OK))
474 		return (NULL);
475 	if (partition < 0 || partition >= NDKMAP)
476 		return (NULL);
477 
478 	/* space for 's', and integer < NDKMAP (16) */
479 	tmplen = strlen(dl->dsk) + strlen("sXX") + 1;
480 	tmpbuf = safe_alloc(tmplen);
481 
482 	/*
483 	 * This is a regular slice. Create the name and
484 	 * copy it for use by the calling routine.
485 	 */
486 	(void) snprintf(tmpbuf, tmplen, "%ss%d", dl->dsk, partition);
487 	return (tmpbuf);
488 }
489 
490 static char *
491 get_intel_partition(int partition, disk_list_t *dl)
492 {
493 	char *tmpbuf;
494 	size_t tmplen;
495 
496 	if (partition <= 0 || !(dl->flags & PARTITIONS_OK))
497 		return (NULL);
498 
499 	/*
500 	 * See if it falls in the range of allowable partitions. The
501 	 * fdisk partitions show up after the traditional slices so we
502 	 * determine which partition we're in and return that.
503 	 * The NUMPART + 1 is not a mistake. There are currently
504 	 * FD_NUMPART + 1 partitions that show up in the device directory.
505 	 */
506 	partition -= NDKMAP;
507 	if (partition < 0 || partition >= (FD_NUMPART + 1))
508 		return (NULL);
509 
510 	/* space for 'p', and integer < NDKMAP (16) */
511 	tmplen = strlen(dl->dsk) + strlen("pXX") + 1;
512 	tmpbuf = safe_alloc(tmplen);
513 
514 	(void) snprintf(tmpbuf, tmplen, "%sp%d", dl->dsk, partition);
515 	return (tmpbuf);
516 }
517 
518 static void
519 get_pretty_name(enum snapshot_types types, struct iodev_snapshot *iodev,
520 	kstat_ctl_t *kc)
521 {
522 	disk_list_t *dl;
523 	char *pretty = NULL;
524 	char *tmp;
525 	int partition;
526 
527 	if (iodev->is_type == IODEV_NFS) {
528 		if (!(types & SNAP_IODEV_PRETTY))
529 			return;
530 
531 		iodev->is_pretty = lookup_nfs_name(iodev->is_name, kc);
532 		return;
533 	}
534 
535 	if (iodev->is_type == IODEV_IOPATH) {
536 		char buf[KSTAT_STRLEN];
537 		size_t len;
538 
539 		tmp = iodev->is_name;
540 		while (*tmp && *tmp != '.')
541 			tmp++;
542 		if (!*tmp)
543 			return;
544 		(void) strlcpy(buf, iodev->is_name, 1 + tmp - iodev->is_name);
545 		dl = lookup_ks_name(buf, (types & SNAP_IODEV_DEVID) ? 1 : 0);
546 		if (dl == NULL || dl->dsk == NULL)
547 			return;
548 		len = strlen(dl->dsk) + strlen(tmp) + 1;
549 		pretty = safe_alloc(len);
550 		(void) strlcpy(pretty, dl->dsk, len);
551 		(void) strlcat(pretty, tmp, len);
552 		goto out;
553 	}
554 
555 	dl = lookup_ks_name(iodev->is_name, (types & SNAP_IODEV_DEVID) ? 1 : 0);
556 	if (dl == NULL)
557 		return;
558 
559 	if (dl->dsk)
560 		pretty = safe_strdup(dl->dsk);
561 
562 	if (types & SNAP_IODEV_PRETTY) {
563 		if (dl->dname)
564 			iodev->is_dname = safe_strdup(dl->dname);
565 	}
566 
567 	if (dl->devidstr)
568 		iodev->is_devid = safe_strdup(dl->devidstr);
569 
570 	/* look for a possible partition number */
571 	tmp = iodev->is_name;
572 	while (*tmp && *tmp != ',')
573 		tmp++;
574 	if (*tmp != ',')
575 		goto out;
576 
577 	tmp++;
578 	partition = (int)(*tmp - 'a');
579 
580 	if (iodev->is_type == IODEV_PARTITION) {
581 		char *part;
582 		if ((part = get_slice(partition, dl)) == NULL)
583 			part = get_intel_partition(partition, dl);
584 		if (part != NULL) {
585 			free(pretty);
586 			pretty = part;
587 		}
588 	}
589 
590 out:
591 	get_ids(iodev, pretty);
592 
593 	/* only fill in the pretty name if specifically asked for */
594 	if (types & SNAP_IODEV_PRETTY) {
595 		iodev->is_pretty = pretty;
596 	} else {
597 		free(pretty);
598 	}
599 }
600 
601 static enum iodev_type
602 get_iodev_type(kstat_t *ksp)
603 {
604 	if (strcmp(ksp->ks_class, "disk") == 0)
605 		return (IODEV_DISK);
606 	if (strcmp(ksp->ks_class, "partition") == 0)
607 		return (IODEV_PARTITION);
608 	if (strcmp(ksp->ks_class, "nfs") == 0)
609 		return (IODEV_NFS);
610 	if (strcmp(ksp->ks_class, "iopath") == 0)
611 		return (IODEV_IOPATH);
612 	if (strcmp(ksp->ks_class, "tape") == 0)
613 		return (IODEV_TAPE);
614 	return (IODEV_UNKNOWN);
615 }
616 
617 int
618 iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2)
619 {
620 	/* neutral sort order between disk and part */
621 	if (!disk_or_partition(io1->is_type) ||
622 		!disk_or_partition(io2->is_type)) {
623 		if (io1->is_type < io2->is_type)
624 			return (-1);
625 		if (io1->is_type > io2->is_type)
626 			return (1);
627 	}
628 
629 	/* controller doesn't have ksp */
630 	if (io1->is_ksp && io2->is_ksp) {
631 		if (strcmp(io1->is_module, io2->is_module) != 0)
632 			return (strcmp(io1->is_module, io2->is_module));
633 		if (io1->is_instance < io2->is_instance)
634 			return (-1);
635 		if (io1->is_instance > io2->is_instance)
636 			return (1);
637 	} else {
638 		if (io1->is_id.id < io2->is_id.id)
639 			return (-1);
640 		if (io1->is_id.id > io2->is_id.id)
641 			return (1);
642 	}
643 
644 	return (strcmp(io1->is_name, io2->is_name));
645 }
646 
647 int
648 acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df)
649 {
650 	kstat_t *ksp;
651 	int err = 0;
652 	struct iodev_snapshot *pos;
653 	struct iodev_snapshot *list = NULL;
654 
655 	ss->s_nr_iodevs = 0;
656 
657 	/*
658 	 * Call cleanup_iodevs_snapshot() so that a cache miss in
659 	 * lookup_ks_name() will result in a fresh snapshot.
660 	 */
661 	cleanup_iodevs_snapshot();
662 
663 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
664 		enum iodev_type type;
665 
666 		if (ksp->ks_type != KSTAT_TYPE_IO)
667 			continue;
668 
669 		/* e.g. "usb_byte_count" is not handled */
670 		if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN)
671 			continue;
672 
673 		if (df && !(type & df->if_allowed_types))
674 			continue;
675 
676 		if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) {
677 			err = errno;
678 			goto out;
679 		}
680 
681 		(void) memset(pos, 0, sizeof (struct iodev_snapshot));
682 
683 		pos->is_type = type;
684 		pos->is_crtime = ksp->ks_crtime;
685 		pos->is_snaptime = ksp->ks_snaptime;
686 		pos->is_id.id = IODEV_NO_ID;
687 		pos->is_parent_id.id = IODEV_NO_ID;
688 		pos->is_ksp = ksp;
689 		pos->is_instance = ksp->ks_instance;
690 
691 		(void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN);
692 		(void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN);
693 		get_pretty_name(ss->s_types, pos, kc);
694 
695 		/*
696 		 * We must insert in sort order so e.g. vmstat -l
697 		 * chooses in order.
698 		 */
699 		insert_into(&list, pos);
700 	}
701 
702 	choose_iodevs(ss, list, df);
703 
704 	/* before acquire_stats for collate_controller()'s benefit */
705 	if (ss->s_types & SNAP_IODEV_ERRORS) {
706 		if ((err = acquire_iodev_errors(ss, kc)) != 0)
707 			goto out;
708 	}
709 
710 	if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0)
711 		goto out;
712 
713 	err = 0;
714 out:
715 	return (err);
716 }
717 
718 void
719 free_iodev(struct iodev_snapshot *iodev)
720 {
721 	while (iodev->is_children) {
722 		struct iodev_snapshot *tmp = iodev->is_children;
723 		iodev->is_children = iodev->is_children->is_next;
724 		free_iodev(tmp);
725 	}
726 
727 	free(iodev->is_errors.ks_data);
728 	free(iodev->is_pretty);
729 	free(iodev->is_dname);
730 	free(iodev->is_devid);
731 	free(iodev);
732 }
733