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