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