xref: /illumos-gate/usr/src/cmd/stat/common/acquire_iodevs.c (revision e77c795bcbe51aebd7579fe13cbf2a6d56eca47f)
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) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 #include "statcommon.h"
26 #include "dsr.h"
27 
28 #include <sys/dklabel.h>
29 #include <sys/dktp/fdisk.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <unistd.h>
33 #include <strings.h>
34 #include <errno.h>
35 #include <limits.h>
36 
37 static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev);
38 
39 static struct iodev_snapshot *
40 make_controller(int cid)
41 {
42 	struct iodev_snapshot *new;
43 
44 	new = safe_alloc(sizeof (struct iodev_snapshot));
45 	(void) memset(new, 0, sizeof (struct iodev_snapshot));
46 	new->is_type = IODEV_CONTROLLER;
47 	new->is_id.id = cid;
48 	new->is_parent_id.id = IODEV_NO_ID;
49 
50 	(void) snprintf(new->is_name, sizeof (new->is_name), "c%d", cid);
51 
52 	return (new);
53 }
54 
55 static struct iodev_snapshot *
56 find_iodev_by_name(struct iodev_snapshot *list, const char *name)
57 {
58 	struct iodev_snapshot *pos;
59 	struct iodev_snapshot *pos2;
60 
61 	for (pos = list; pos; pos = pos->is_next) {
62 		if (strcmp(pos->is_name, name) == 0)
63 			return (pos);
64 
65 		pos2 = find_iodev_by_name(pos->is_children, name);
66 		if (pos2 != NULL)
67 			return (pos2);
68 	}
69 
70 	return (NULL);
71 }
72 
73 static enum iodev_type
74 parent_iodev_type(enum iodev_type type)
75 {
76 	switch (type) {
77 		case IODEV_CONTROLLER: return (0);
78 		case IODEV_IOPATH_LT: return (0);
79 		case IODEV_IOPATH_LI: return (0);
80 		case IODEV_NFS: return (0);
81 		case IODEV_TAPE: return (0);
82 		case IODEV_IOPATH_LTI: return (IODEV_DISK);
83 		case IODEV_DISK: return (IODEV_CONTROLLER);
84 		case IODEV_PARTITION: return (IODEV_DISK);
85 	}
86 	return (IODEV_UNKNOWN);
87 }
88 
89 static int
90 id_match(struct iodev_id *id1, struct iodev_id *id2)
91 {
92 	return (id1->id == id2->id &&
93 	    strcmp(id1->tid, id2->tid) == 0);
94 }
95 
96 static struct iodev_snapshot *
97 find_parent(struct snapshot *ss, struct iodev_snapshot *iodev)
98 {
99 	enum iodev_type parent_type = parent_iodev_type(iodev->is_type);
100 	struct iodev_snapshot *pos;
101 	struct iodev_snapshot *pos2;
102 
103 	if (parent_type == 0 || parent_type == IODEV_UNKNOWN)
104 		return (NULL);
105 
106 	if (iodev->is_parent_id.id == IODEV_NO_ID &&
107 	    iodev->is_parent_id.tid[0] == '\0')
108 		return (NULL);
109 
110 	if (parent_type == IODEV_CONTROLLER) {
111 		for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
112 			if (pos->is_type != IODEV_CONTROLLER)
113 				continue;
114 			if (pos->is_id.id != iodev->is_parent_id.id)
115 				continue;
116 			return (pos);
117 		}
118 
119 		if (!(ss->s_types & SNAP_CONTROLLERS))
120 			return (NULL);
121 
122 		pos = make_controller(iodev->is_parent_id.id);
123 		insert_iodev(ss, pos);
124 		return (pos);
125 	}
126 
127 	/* IODEV_DISK parent */
128 	for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
129 		if (id_match(&iodev->is_parent_id, &pos->is_id) &&
130 		    pos->is_type == IODEV_DISK)
131 			return (pos);
132 		if (pos->is_type != IODEV_CONTROLLER)
133 			continue;
134 		for (pos2 = pos->is_children; pos2; pos2 = pos2->is_next) {
135 			if (pos2->is_type != IODEV_DISK)
136 				continue;
137 			if (id_match(&iodev->is_parent_id, &pos2->is_id))
138 				return (pos2);
139 		}
140 	}
141 
142 	return (NULL);
143 }
144 
145 /*
146  * Introduce an index into the list to speed up insert_into looking for the
147  * right position in the list. This index is an AVL tree of all the
148  * iodev_snapshot in the list.
149  */
150 
151 #define	offsetof(s, m)	(size_t)(&(((s *)0)->m))	/* for avl_create */
152 
153 static int
154 avl_iodev_cmp(const void* is1, const void* is2)
155 {
156 	int c = iodev_cmp((struct iodev_snapshot *)is1,
157 	    (struct iodev_snapshot *)is2);
158 
159 	if (c > 0)
160 		return (1);
161 
162 	if (c < 0)
163 		return (-1);
164 
165 	return (0);
166 }
167 
168 static void
169 ix_new_list(struct iodev_snapshot *elem)
170 {
171 	avl_tree_t *l = malloc(sizeof (avl_tree_t));
172 
173 	elem->avl_list = l;
174 	if (l == NULL)
175 		return;
176 
177 	avl_create(l, avl_iodev_cmp, sizeof (struct iodev_snapshot),
178 	    offsetof(struct iodev_snapshot, avl_link));
179 
180 	avl_add(l, elem);
181 }
182 
183 static void
184 ix_list_del(struct iodev_snapshot *elem)
185 {
186 	avl_tree_t *l = elem->avl_list;
187 
188 	if (l == NULL)
189 		return;
190 
191 	elem->avl_list = NULL;
192 
193 	avl_remove(l, elem);
194 	if (avl_numnodes(l) == 0) {
195 		avl_destroy(l);
196 		free(l);
197 	}
198 }
199 
200 static void
201 ix_insert_here(struct iodev_snapshot *pos, struct iodev_snapshot *elem, int ba)
202 {
203 	avl_tree_t *l = pos->avl_list;
204 	elem->avl_list = l;
205 
206 	if (l == NULL)
207 		return;
208 
209 	avl_insert_here(l, elem, pos, ba);
210 }
211 
212 static void
213 list_del(struct iodev_snapshot **list, struct iodev_snapshot *pos)
214 {
215 	ix_list_del(pos);
216 
217 	if (*list == pos)
218 		*list = pos->is_next;
219 	if (pos->is_next)
220 		pos->is_next->is_prev = pos->is_prev;
221 	if (pos->is_prev)
222 		pos->is_prev->is_next = pos->is_next;
223 	pos->is_prev = pos->is_next = NULL;
224 }
225 
226 static void
227 insert_before(struct iodev_snapshot **list, struct iodev_snapshot *pos,
228     struct iodev_snapshot *new)
229 {
230 	if (pos == NULL) {
231 		new->is_prev = new->is_next = NULL;
232 		*list = new;
233 		ix_new_list(new);
234 		return;
235 	}
236 
237 	new->is_next = pos;
238 	new->is_prev = pos->is_prev;
239 	if (pos->is_prev)
240 		pos->is_prev->is_next = new;
241 	else
242 		*list = new;
243 	pos->is_prev = new;
244 
245 	ix_insert_here(pos, new, AVL_BEFORE);
246 }
247 
248 static void
249 insert_after(struct iodev_snapshot **list, struct iodev_snapshot *pos,
250     struct iodev_snapshot *new)
251 {
252 	if (pos == NULL) {
253 		new->is_prev = new->is_next = NULL;
254 		*list = new;
255 		ix_new_list(new);
256 		return;
257 	}
258 
259 	new->is_next = pos->is_next;
260 	new->is_prev = pos;
261 	if (pos->is_next)
262 		pos->is_next->is_prev = new;
263 	pos->is_next = new;
264 
265 	ix_insert_here(pos, new, AVL_AFTER);
266 }
267 
268 static void
269 insert_into(struct iodev_snapshot **list, struct iodev_snapshot *iodev)
270 {
271 	struct iodev_snapshot *tmp = *list;
272 	avl_tree_t *l;
273 	void *p;
274 	avl_index_t where;
275 
276 	if (*list == NULL) {
277 		*list = iodev;
278 		ix_new_list(iodev);
279 		return;
280 	}
281 
282 	/*
283 	 * Optimize the search: instead of walking the entire list
284 	 * (which can contain thousands of nodes), search in the AVL
285 	 * tree the nearest node and reposition the startup point to
286 	 * this node rather than always starting from the beginning
287 	 * of the list.
288 	 */
289 	l = tmp->avl_list;
290 	if (l != NULL) {
291 		p = avl_find(l, iodev, &where);
292 		if (p == NULL) {
293 			p = avl_nearest(l, where, AVL_BEFORE);
294 		}
295 		if (p != NULL) {
296 			tmp = (struct iodev_snapshot *)p;
297 		}
298 	}
299 
300 	for (;;) {
301 		if (iodev_cmp(tmp, iodev) > 0) {
302 			insert_before(list, tmp, iodev);
303 			return;
304 		}
305 
306 		if (tmp->is_next == NULL)
307 			break;
308 
309 		tmp = tmp->is_next;
310 	}
311 
312 	insert_after(list, tmp, iodev);
313 }
314 
315 static int
316 disk_or_partition(enum iodev_type type)
317 {
318 	return (type == IODEV_DISK || type == IODEV_PARTITION);
319 }
320 
321 static int
322 disk_or_partition_or_iopath(enum iodev_type type)
323 {
324 	return (type == IODEV_DISK || type == IODEV_PARTITION ||
325 	    type == IODEV_IOPATH_LTI);
326 }
327 
328 static void
329 insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev)
330 {
331 	struct iodev_snapshot *parent = find_parent(ss, iodev);
332 	struct iodev_snapshot **list;
333 
334 	if (parent != NULL) {
335 		list = &parent->is_children;
336 		parent->is_nr_children++;
337 	} else {
338 		list = &ss->s_iodevs;
339 		ss->s_nr_iodevs++;
340 	}
341 
342 	insert_into(list, iodev);
343 }
344 
345 /* return 1 if dev passes filter */
346 static int
347 iodev_match(struct iodev_snapshot *dev, struct iodev_filter *df)
348 {
349 	int	is_floppy = (strncmp(dev->is_name, "fd", 2) == 0);
350 	char	*isn, *ispn, *ifn;
351 	char	*path;
352 	int	ifnl;
353 	size_t	i;
354 
355 	/* no filter, pass */
356 	if (df == NULL)
357 		return (1);		/* pass */
358 
359 	/* no filtered names, pass if not floppy and skipped */
360 	if (df->if_nr_names == NULL)
361 		return (!(df->if_skip_floppy && is_floppy));
362 
363 	isn = dev->is_name;
364 	ispn = dev->is_pretty;
365 	for (i = 0; i < df->if_nr_names; i++) {
366 		ifn = df->if_names[i];
367 		ifnl = strlen(ifn);
368 		path = strchr(ifn, '.');
369 
370 		if ((strcmp(isn, ifn) == 0) ||
371 		    (ispn && (strcmp(ispn, ifn) == 0)))
372 			return (1);	/* pass */
373 
374 		/* if filter is a path allow partial match */
375 		if (path &&
376 		    ((strncmp(isn, ifn, ifnl) == 0) ||
377 		    (ispn && (strncmp(ispn, ifn, ifnl) == 0))))
378 			return (1);	/* pass */
379 	}
380 
381 	return (0);			/* fail */
382 }
383 
384 /* return 1 if path is an mpxio path associated with dev */
385 static int
386 iodev_path_match(struct iodev_snapshot *dev, struct iodev_snapshot *path)
387 {
388 	char	*dn, *pn;
389 	int	dnl;
390 
391 	dn = dev->is_name;
392 	pn = path->is_name;
393 	dnl = strlen(dn);
394 
395 	if ((strncmp(pn, dn, dnl) == 0) && (pn[dnl] == '.'))
396 		return (1);			/* yes */
397 
398 	return (0);				/* no */
399 }
400 
401 /* select which I/O devices to collect stats for */
402 static void
403 choose_iodevs(struct snapshot *ss, struct iodev_snapshot *iodevs,
404     struct iodev_filter *df)
405 {
406 	struct iodev_snapshot	*pos, *ppos, *tmp, *ptmp;
407 	int			nr_iodevs;
408 	int			nr_iodevs_orig;
409 
410 	nr_iodevs = df ? df->if_max_iodevs : UNLIMITED_IODEVS;
411 	nr_iodevs_orig = nr_iodevs;
412 
413 	if (nr_iodevs == UNLIMITED_IODEVS)
414 		nr_iodevs = INT_MAX;
415 
416 	/* add the full matches */
417 	pos = iodevs;
418 	while (pos && nr_iodevs) {
419 		tmp = pos;
420 		pos = pos->is_next;
421 
422 		if (!iodev_match(tmp, df))
423 			continue;	/* failed full match */
424 
425 		list_del(&iodevs, tmp);
426 		insert_iodev(ss, tmp);
427 
428 		/*
429 		 * Add all mpxio paths associated with match above. Added
430 		 * paths don't count against nr_iodevs.
431 		 */
432 		if (strchr(tmp->is_name, '.') == NULL) {
433 		ppos = iodevs;
434 		while (ppos) {
435 			ptmp = ppos;
436 			ppos = ppos->is_next;
437 
438 			if (!iodev_path_match(tmp, ptmp))
439 				continue;	/* not an mpxio path */
440 
441 			list_del(&iodevs, ptmp);
442 			insert_iodev(ss, ptmp);
443 			if (pos == ptmp)
444 				pos = ppos;
445 		}
446 		}
447 
448 		nr_iodevs--;
449 	}
450 
451 	/*
452 	 * If we had a filter, and *nothing* passed the filter then we
453 	 * don't want to fill the  remaining slots - it is just confusing
454 	 * if we don that, it makes it look like the filter code is broken.
455 	 */
456 	if ((df->if_nr_names == NULL) || (nr_iodevs != nr_iodevs_orig)) {
457 		/* now insert any iodevs into the remaining slots */
458 		pos = iodevs;
459 		while (pos && nr_iodevs) {
460 			tmp = pos;
461 			pos = pos->is_next;
462 
463 			if (df && df->if_skip_floppy &&
464 			    strncmp(tmp->is_name, "fd", 2) == 0)
465 				continue;
466 
467 			list_del(&iodevs, tmp);
468 			insert_iodev(ss, tmp);
469 
470 			--nr_iodevs;
471 		}
472 	}
473 
474 	/* clear the unwanted ones */
475 	pos = iodevs;
476 	while (pos) {
477 		struct iodev_snapshot *tmp = pos;
478 		pos = pos->is_next;
479 		free_iodev(tmp);
480 	}
481 }
482 
483 static int
484 collate_controller(struct iodev_snapshot *controller,
485     struct iodev_snapshot *disk)
486 {
487 	controller->is_stats.nread += disk->is_stats.nread;
488 	controller->is_stats.nwritten += disk->is_stats.nwritten;
489 	controller->is_stats.reads += disk->is_stats.reads;
490 	controller->is_stats.writes += disk->is_stats.writes;
491 	controller->is_stats.wtime += disk->is_stats.wtime;
492 	controller->is_stats.wlentime += disk->is_stats.wlentime;
493 	controller->is_stats.rtime += disk->is_stats.rtime;
494 	controller->is_stats.rlentime += disk->is_stats.rlentime;
495 	controller->is_crtime += disk->is_crtime;
496 	controller->is_snaptime += disk->is_snaptime;
497 	if (kstat_add(&disk->is_errors, &controller->is_errors))
498 		return (errno);
499 	return (0);
500 }
501 
502 static int
503 acquire_iodev_stats(struct iodev_snapshot *list, kstat_ctl_t *kc)
504 {
505 	struct iodev_snapshot *pos;
506 	int err = 0;
507 
508 	for (pos = list; pos; pos = pos->is_next) {
509 		/* controllers don't have stats (yet) */
510 		if (pos->is_ksp != NULL) {
511 			if (kstat_read(kc, pos->is_ksp, &pos->is_stats) == -1)
512 				return (errno);
513 			/* make sure crtime/snaptime is updated */
514 			pos->is_crtime = pos->is_ksp->ks_crtime;
515 			pos->is_snaptime = pos->is_ksp->ks_snaptime;
516 		}
517 
518 		if ((err = acquire_iodev_stats(pos->is_children, kc)))
519 			return (err);
520 
521 		if (pos->is_type == IODEV_CONTROLLER) {
522 			struct iodev_snapshot *pos2 = pos->is_children;
523 
524 			for (; pos2; pos2 = pos2->is_next) {
525 				if ((err = collate_controller(pos, pos2)))
526 					return (err);
527 			}
528 		}
529 	}
530 
531 	return (0);
532 }
533 
534 static int
535 acquire_iodev_errors(struct snapshot *ss, kstat_ctl_t *kc)
536 {
537 	kstat_t *ksp;
538 
539 	if (!(ss->s_types && SNAP_IODEV_ERRORS))
540 		return (0);
541 
542 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
543 		char kstat_name[KSTAT_STRLEN];
544 		char *dname = kstat_name;
545 		char *ename = ksp->ks_name;
546 		struct iodev_snapshot *iodev;
547 
548 		if (ksp->ks_type != KSTAT_TYPE_NAMED)
549 			continue;
550 		if (strncmp(ksp->ks_class, "device_error", 12) != 0 &&
551 		    strncmp(ksp->ks_class, "iopath_error", 12) != 0)
552 			continue;
553 
554 		/*
555 		 * Some drivers may not follow the naming convention
556 		 * for error kstats (i.e., drivername,err) so
557 		 * be sure we don't walk off the end.
558 		 */
559 		while (*ename && *ename != ',') {
560 			*dname = *ename;
561 			dname++;
562 			ename++;
563 		}
564 		*dname = '\0';
565 
566 		iodev = find_iodev_by_name(ss->s_iodevs, kstat_name);
567 
568 		if (iodev == NULL)
569 			continue;
570 
571 		if (kstat_read(kc, ksp, NULL) == -1)
572 			return (errno);
573 		if (kstat_copy(ksp, &iodev->is_errors) == -1)
574 			return (errno);
575 	}
576 
577 	return (0);
578 }
579 
580 static void
581 get_ids(struct iodev_snapshot *iodev, const char *pretty)
582 {
583 	int ctr, disk, slice, ret;
584 	char *target;
585 	const char *p1;
586 	const char *p2;
587 
588 	if (pretty == NULL)
589 		return;
590 
591 	if (sscanf(pretty, "c%d", &ctr) != 1)
592 		return;
593 
594 	p1 = pretty;
595 	while (*p1 && *p1 != 't')
596 		++p1;
597 
598 	if (!*p1)
599 		return;
600 	++p1;
601 
602 	p2 = p1;
603 	while (*p2 && *p2 != 'd')
604 		++p2;
605 
606 	if (!*p2 || p2 == p1)
607 		return;
608 
609 	target = safe_alloc(1 + p2 - p1);
610 	(void) strlcpy(target, p1, 1 + p2 - p1);
611 
612 	ret = sscanf(p2, "d%d%*[sp]%d", &disk, &slice);
613 
614 	if (ret == 2 && iodev->is_type == IODEV_PARTITION) {
615 		iodev->is_id.id = slice;
616 		iodev->is_parent_id.id = disk;
617 		(void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN);
618 	} else if (ret == 1) {
619 		if (iodev->is_type == IODEV_DISK) {
620 			iodev->is_id.id = disk;
621 			(void) strlcpy(iodev->is_id.tid, target, KSTAT_STRLEN);
622 			iodev->is_parent_id.id = ctr;
623 		} else if (iodev->is_type == IODEV_IOPATH_LTI) {
624 			iodev->is_parent_id.id = disk;
625 			(void) strlcpy(iodev->is_parent_id.tid,
626 			    target, KSTAT_STRLEN);
627 		}
628 	}
629 
630 	free(target);
631 }
632 
633 static void
634 get_pretty_name(enum snapshot_types types, struct iodev_snapshot *iodev,
635 	kstat_ctl_t *kc)
636 {
637 	disk_list_t	*dl;
638 	char		*pretty = NULL;
639 
640 	if (iodev->is_type == IODEV_NFS) {
641 		if (!(types & SNAP_IODEV_PRETTY))
642 			return;
643 
644 		iodev->is_pretty = lookup_nfs_name(iodev->is_name, kc);
645 		return;
646 	}
647 
648 	/* lookup/translate the kstat name */
649 	dl = lookup_ks_name(iodev->is_name, (types & SNAP_IODEV_DEVID) ? 1 : 0);
650 	if (dl == NULL)
651 		return;
652 
653 	if (dl->dsk)
654 		pretty = safe_strdup(dl->dsk);
655 
656 	if (types & SNAP_IODEV_PRETTY) {
657 		if (dl->dname)
658 			iodev->is_dname = safe_strdup(dl->dname);
659 	}
660 
661 	if (dl->devidstr)
662 		iodev->is_devid = safe_strdup(dl->devidstr);
663 
664 	get_ids(iodev, pretty);
665 
666 	/*
667 	 * we fill in pretty name wether it is asked for or not because
668 	 * it could be used in a filter by match_iodevs.
669 	 */
670 	iodev->is_pretty = pretty;
671 }
672 
673 static enum iodev_type
674 get_iodev_type(kstat_t *ksp)
675 {
676 	if (strcmp(ksp->ks_class, "disk") == 0)
677 		return (IODEV_DISK);
678 	if (strcmp(ksp->ks_class, "partition") == 0)
679 		return (IODEV_PARTITION);
680 	if (strcmp(ksp->ks_class, "nfs") == 0)
681 		return (IODEV_NFS);
682 	if (strcmp(ksp->ks_class, "iopath") == 0)
683 		return (IODEV_IOPATH_LTI);
684 	if (strcmp(ksp->ks_class, "tape") == 0)
685 		return (IODEV_TAPE);
686 	return (IODEV_UNKNOWN);
687 }
688 
689 /* get the lun/target/initiator from the name, return 1 on success */
690 static int
691 get_lti(char *s,
692 	char *lname, int *l, char *tname, int *t, char *iname, int *i)
693 {
694 	int  num = 0;
695 
696 	num = sscanf(s, "%[a-z]%d%*[.]%[a-z]%d%*[.]%[a-z_]%d", lname, l,
697 	    tname, t, iname, i);
698 	return ((num == 6) ? 1 : 0);
699 }
700 
701 
702 /* get the lun, target, and initiator name and instance */
703 static void
704 get_path_info(struct iodev_snapshot *io, char *mod, size_t modlen, int *type,
705     int *inst, char *name, size_t size)
706 {
707 
708 	/*
709 	 * If it is iopath or ssd then pad the name with i/t/l so we can sort
710 	 * by alpha order and set type for IOPATH to DISK since we want to
711 	 * have it grouped with its ssd parent. The lun can be 5 digits,
712 	 * the target can be 4 digits, and the initiator can be 3 digits and
713 	 * the padding is done appropriately for string comparisons.
714 	 */
715 	if (disk_or_partition_or_iopath(io->is_type)) {
716 		int i1, t1, l1;
717 		char tname[KSTAT_STRLEN], iname[KSTAT_STRLEN];
718 		char *ptr, lname[KSTAT_STRLEN];
719 
720 		i1 = t1 = l1 = 0;
721 		(void) get_lti(io->is_name, lname, &l1, tname, &t1, iname, &i1);
722 		*type = io->is_type;
723 		if (io->is_type == IODEV_DISK) {
724 			(void) snprintf(name, size, "%s%05d", lname, l1);
725 		} else if (io->is_type == IODEV_PARTITION) {
726 			ptr = strchr(io->is_name, ',');
727 			(void) snprintf(name, size, "%s%05d%s", lname, l1, ptr);
728 		} else {
729 			(void) snprintf(name, size, "%s%05d.%s%04d.%s%03d",
730 			    lname, l1, tname, t1, iname, i1);
731 			/* set to disk so we sort with disks */
732 			*type = IODEV_DISK;
733 		}
734 		(void) strlcpy(mod, lname, modlen);
735 		*inst = l1;
736 	} else {
737 		(void) strlcpy(mod, io->is_module, modlen);
738 		(void) strlcpy(name, io->is_name, size);
739 		*type = io->is_type;
740 		*inst = io->is_instance;
741 	}
742 }
743 
744 int
745 iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2)
746 {
747 	int	type1, type2;
748 	int	inst1, inst2;
749 	char	name1[KSTAT_STRLEN], name2[KSTAT_STRLEN];
750 	char	mod1[KSTAT_STRLEN], mod2[KSTAT_STRLEN];
751 
752 	get_path_info(io1, mod1, sizeof (mod1), &type1, &inst1, name1,
753 	    sizeof (name1));
754 	get_path_info(io2, mod2, sizeof (mod2), &type2, &inst2, name2,
755 	    sizeof (name2));
756 	if ((!disk_or_partition(type1)) ||
757 	    (!disk_or_partition(type2))) {
758 		/* neutral sort order between disk and part */
759 		if (type1 < type2) {
760 			return (-1);
761 		}
762 		if (type1 > type2) {
763 			return (1);
764 		}
765 	}
766 
767 	/* controller doesn't have ksp */
768 	if (io1->is_ksp && io2->is_ksp) {
769 		if (strcmp(mod1, mod2) != 0) {
770 			return (strcmp(mod1, mod2));
771 		}
772 		if (inst1 < inst2) {
773 			return (-1);
774 		}
775 		if (inst1 > inst2) {
776 			return (1);
777 		}
778 	} else {
779 		if (io1->is_id.id < io2->is_id.id) {
780 			return (-1);
781 		}
782 		if (io1->is_id.id > io2->is_id.id) {
783 			return (1);
784 		}
785 	}
786 
787 	return (strcmp(name1, name2));
788 }
789 
790 /* update the target reads and writes */
791 static void
792 update_target(struct iodev_snapshot *tgt, struct iodev_snapshot *path)
793 {
794 	tgt->is_stats.reads += path->is_stats.reads;
795 	tgt->is_stats.writes += path->is_stats.writes;
796 	tgt->is_stats.nread += path->is_stats.nread;
797 	tgt->is_stats.nwritten += path->is_stats.nwritten;
798 	tgt->is_stats.wcnt += path->is_stats.wcnt;
799 	tgt->is_stats.rcnt += path->is_stats.rcnt;
800 
801 	/*
802 	 * Stash the t_delta in the crtime for use in show_disk
803 	 * NOTE: this can't be done in show_disk because the
804 	 * itl entry is removed for the old format
805 	 */
806 	tgt->is_crtime += hrtime_delta(path->is_crtime, path->is_snaptime);
807 	tgt->is_snaptime += path->is_snaptime;
808 	tgt->is_nr_children += 1;
809 }
810 
811 /*
812  * Create a new synthetic device entry of the specified type. The supported
813  * synthetic types are IODEV_IOPATH_LT and IODEV_IOPATH_LI.
814  */
815 static struct iodev_snapshot *
816 make_extended_device(int type, struct iodev_snapshot *old)
817 {
818 	struct iodev_snapshot	*tptr = NULL;
819 	char			*ptr;
820 	int			lun, tgt, initiator;
821 	char			lun_name[KSTAT_STRLEN];
822 	char			tgt_name[KSTAT_STRLEN];
823 	char			initiator_name[KSTAT_STRLEN];
824 
825 	if (old == NULL)
826 		return (NULL);
827 	if (get_lti(old->is_name,
828 	    lun_name, &lun, tgt_name, &tgt, initiator_name, &initiator) != 1) {
829 		return (NULL);
830 	}
831 	tptr = safe_alloc(sizeof (*old));
832 	bzero(tptr, sizeof (*old));
833 	if (old->is_pretty != NULL) {
834 		tptr->is_pretty = safe_alloc(strlen(old->is_pretty) + 1);
835 		(void) strcpy(tptr->is_pretty, old->is_pretty);
836 	}
837 	bcopy(&old->is_parent_id, &tptr->is_parent_id,
838 	    sizeof (old->is_parent_id));
839 
840 	tptr->is_type = type;
841 
842 	if (type == IODEV_IOPATH_LT) {
843 		/* make new synthetic entry that is the LT */
844 		/* set the id to the target id */
845 		tptr->is_id.id = tgt;
846 		(void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
847 		    "%s%d", tgt_name, tgt);
848 		(void) snprintf(tptr->is_name, sizeof (tptr->is_name),
849 		    "%s%d.%s%d", lun_name, lun, tgt_name, tgt);
850 
851 		if (old->is_pretty) {
852 			ptr = strrchr(tptr->is_pretty, '.');
853 			if (ptr)
854 				*ptr = '\0';
855 		}
856 	} else if (type == IODEV_IOPATH_LI) {
857 		/* make new synthetic entry that is the LI */
858 		/* set the id to the initiator number */
859 		tptr->is_id.id = initiator;
860 		(void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
861 		    "%s%d", initiator_name, initiator);
862 		(void) snprintf(tptr->is_name, sizeof (tptr->is_name),
863 		    "%s%d.%s%d", lun_name, lun, initiator_name, initiator);
864 
865 		if (old->is_pretty) {
866 			ptr = strchr(tptr->is_pretty, '.');
867 			if (ptr)
868 				(void) snprintf(ptr + 1,
869 				    strlen(tptr->is_pretty) + 1,
870 				    "%s%d", initiator_name, initiator);
871 		}
872 	}
873 	return (tptr);
874 }
875 
876 /*
877  * This is to get the original -X LI format (e.g. ssd1.fp0). When an LTI kstat
878  * is found - traverse the children looking for the same initiator and sum
879  * them up. Add an LI entry and delete all of the LTI entries with the same
880  * initiator.
881  */
882 static int
883 create_li_delete_lti(struct snapshot *ss, struct iodev_snapshot *list)
884 {
885 	struct iodev_snapshot	*pos, *entry, *parent;
886 	int			lun, tgt, initiator;
887 	char			lun_name[KSTAT_STRLEN];
888 	char			tgt_name[KSTAT_STRLEN];
889 	char			initiator_name[KSTAT_STRLEN];
890 	int			err;
891 
892 	for (entry = list; entry; entry = entry->is_next) {
893 		if ((err = create_li_delete_lti(ss, entry->is_children)) != 0)
894 			return (err);
895 
896 		if (entry->is_type == IODEV_IOPATH_LTI) {
897 			parent = find_parent(ss, entry);
898 			if (get_lti(entry->is_name, lun_name, &lun,
899 			    tgt_name, &tgt, initiator_name, &initiator) != 1) {
900 				return (1);
901 			}
902 
903 			pos = (parent == NULL) ? NULL : parent->is_children;
904 			for (; pos; pos = pos->is_next) {
905 				if (pos->is_id.id != -1 &&
906 				    pos->is_id.id == initiator &&
907 				    pos->is_type == IODEV_IOPATH_LI) {
908 					/* found the same initiator */
909 					update_target(pos, entry);
910 					list_del(&parent->is_children, entry);
911 					free_iodev(entry);
912 					parent->is_nr_children--;
913 					entry = pos;
914 					break;
915 				}
916 			}
917 
918 			if (!pos) {
919 				/* make the first LI entry */
920 				pos = make_extended_device(
921 				    IODEV_IOPATH_LI, entry);
922 				update_target(pos, entry);
923 
924 				if (parent) {
925 					insert_before(&parent->is_children,
926 					    entry, pos);
927 					list_del(&parent->is_children, entry);
928 					free_iodev(entry);
929 				} else {
930 					insert_before(&ss->s_iodevs, entry,
931 					    pos);
932 					list_del(&ss->s_iodevs, entry);
933 					free_iodev(entry);
934 				}
935 				entry = pos;
936 			}
937 		}
938 	}
939 	return (0);
940 }
941 
942 /*
943  * We have the LTI kstat, now add an entry for the LT that sums up all of
944  * the LTI's with the same target(t).
945  */
946 static int
947 create_lt(struct snapshot *ss, struct iodev_snapshot *list)
948 {
949 	struct iodev_snapshot	*entry, *parent, *pos;
950 	int			lun, tgt, initiator;
951 	char			lun_name[KSTAT_STRLEN];
952 	char			tgt_name[KSTAT_STRLEN];
953 	char			initiator_name[KSTAT_STRLEN];
954 	int			err;
955 
956 	for (entry = list; entry; entry = entry->is_next) {
957 		if ((err = create_lt(ss, entry->is_children)) != 0)
958 			return (err);
959 
960 		if (entry->is_type == IODEV_IOPATH_LTI) {
961 			parent = find_parent(ss, entry);
962 			if (get_lti(entry->is_name, lun_name, &lun,
963 			    tgt_name, &tgt, initiator_name, &initiator) != 1) {
964 				return (1);
965 			}
966 
967 			pos = (parent == NULL) ? NULL : parent->is_children;
968 			for (; pos; pos = pos->is_next) {
969 				if (pos->is_id.id != -1 &&
970 				    pos->is_id.id == tgt &&
971 				    pos->is_type == IODEV_IOPATH_LT) {
972 					/* found the same target */
973 					update_target(pos, entry);
974 					break;
975 				}
976 			}
977 
978 			if (!pos) {
979 				pos = make_extended_device(
980 				    IODEV_IOPATH_LT, entry);
981 				update_target(pos, entry);
982 
983 				if (parent) {
984 					insert_before(&parent->is_children,
985 					    entry, pos);
986 					parent->is_nr_children++;
987 				} else {
988 					insert_before(&ss->s_iodevs,
989 					    entry, pos);
990 				}
991 			}
992 		}
993 	}
994 	return (0);
995 }
996 
997 /* Find the longest is_name field to aid formatting of output */
998 static int
999 iodevs_is_name_maxlen(struct iodev_snapshot *list)
1000 {
1001 	struct iodev_snapshot	*entry;
1002 	int			max = 0, cmax, len;
1003 
1004 	for (entry = list; entry; entry = entry->is_next) {
1005 		cmax = iodevs_is_name_maxlen(entry->is_children);
1006 		max = (cmax > max) ? cmax : max;
1007 		len = strlen(entry->is_name);
1008 		max = (len > max) ? len : max;
1009 	}
1010 	return (max);
1011 }
1012 
1013 int
1014 acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df)
1015 {
1016 	kstat_t	*ksp;
1017 	struct	iodev_snapshot *pos;
1018 	struct	iodev_snapshot *list = NULL;
1019 	int	err = 0;
1020 
1021 	ss->s_nr_iodevs = 0;
1022 	ss->s_iodevs_is_name_maxlen = 0;
1023 
1024 	/*
1025 	 * Call cleanup_iodevs_snapshot() so that a cache miss in
1026 	 * lookup_ks_name() will result in a fresh snapshot.
1027 	 */
1028 	cleanup_iodevs_snapshot();
1029 
1030 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
1031 		enum iodev_type type;
1032 
1033 		if (ksp->ks_type != KSTAT_TYPE_IO)
1034 			continue;
1035 
1036 		/* e.g. "usb_byte_count" is not handled */
1037 		if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN)
1038 			continue;
1039 
1040 		if (df && !(type & df->if_allowed_types))
1041 			continue;
1042 
1043 		if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) {
1044 			err = errno;
1045 			goto out;
1046 		}
1047 
1048 		(void) memset(pos, 0, sizeof (struct iodev_snapshot));
1049 
1050 		pos->is_type = type;
1051 		pos->is_crtime = ksp->ks_crtime;
1052 		pos->is_snaptime = ksp->ks_snaptime;
1053 		pos->is_id.id = IODEV_NO_ID;
1054 		pos->is_parent_id.id = IODEV_NO_ID;
1055 		pos->is_ksp = ksp;
1056 		pos->is_instance = ksp->ks_instance;
1057 
1058 		(void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN);
1059 		(void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN);
1060 		get_pretty_name(ss->s_types, pos, kc);
1061 
1062 		/*
1063 		 * We must insert in sort order so e.g. vmstat -l
1064 		 * chooses in order.
1065 		 */
1066 		insert_into(&list, pos);
1067 	}
1068 
1069 	choose_iodevs(ss, list, df);
1070 
1071 	/* before acquire_stats for collate_controller()'s benefit */
1072 	if (ss->s_types & SNAP_IODEV_ERRORS) {
1073 		if ((err = acquire_iodev_errors(ss, kc)) != 0)
1074 			goto out;
1075 	}
1076 
1077 	if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0)
1078 		goto out;
1079 
1080 	if (ss->s_types & SNAP_IOPATHS_LTI) {
1081 		/*
1082 		 * -Y: kstats are LTI, need to create a synthetic LT
1083 		 * for -Y output.
1084 		 */
1085 		if ((err = create_lt(ss, ss->s_iodevs)) != 0) {
1086 			return (err);
1087 		}
1088 	}
1089 	if (ss->s_types & SNAP_IOPATHS_LI) {
1090 		/*
1091 		 * -X: kstats are LTI, need to create a synthetic LI and
1092 		 * delete the LTI for -X output
1093 		 */
1094 		if ((err = create_li_delete_lti(ss, ss->s_iodevs)) != 0) {
1095 			return (err);
1096 		}
1097 	}
1098 
1099 	/* determine width of longest is_name */
1100 	ss->s_iodevs_is_name_maxlen = iodevs_is_name_maxlen(ss->s_iodevs);
1101 
1102 	err = 0;
1103 out:
1104 	return (err);
1105 }
1106 
1107 void
1108 free_iodev(struct iodev_snapshot *iodev)
1109 {
1110 	while (iodev->is_children) {
1111 		struct iodev_snapshot *tmp = iodev->is_children;
1112 		iodev->is_children = iodev->is_children->is_next;
1113 		free_iodev(tmp);
1114 	}
1115 
1116 	if (iodev->avl_list) {
1117 		avl_remove(iodev->avl_list, iodev);
1118 		if (avl_numnodes(iodev->avl_list) == 0) {
1119 			avl_destroy(iodev->avl_list);
1120 			free(iodev->avl_list);
1121 		}
1122 	}
1123 
1124 	free(iodev->is_errors.ks_data);
1125 	free(iodev->is_pretty);
1126 	free(iodev->is_dname);
1127 	free(iodev->is_devid);
1128 	free(iodev);
1129 }
1130