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