xref: /illumos-gate/usr/src/cmd/stat/common/acquire_iodevs.c (revision 2a6e99a0f1f7d22c0396e8b2ce9b9babbd1056cf)
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 == NULL)
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 == NULL) || (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 && 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,
687 	char *lname, int *l, char *tname, int *t, char *iname, int *i)
688 {
689 	int  num = 0;
690 
691 	num = sscanf(s, "%[a-z]%d%*[.]%[a-z]%d%*[.]%[a-z_]%d", lname, l,
692 	    tname, t, iname, i);
693 	return ((num == 6) ? 1 : 0);
694 }
695 
696 
697 /* get the lun, target, and initiator name and instance */
698 static void
699 get_path_info(struct iodev_snapshot *io, char *mod, size_t modlen, int *type,
700     int *inst, char *name, size_t size)
701 {
702 
703 	/*
704 	 * If it is iopath or ssd then pad the name with i/t/l so we can sort
705 	 * by alpha order and set type for IOPATH to DISK since we want to
706 	 * have it grouped with its ssd parent. The lun can be 5 digits,
707 	 * the target can be 4 digits, and the initiator can be 3 digits and
708 	 * the padding is done appropriately for string comparisons.
709 	 */
710 	if (disk_or_partition_or_iopath(io->is_type)) {
711 		int i1, t1, l1;
712 		char tname[KSTAT_STRLEN], iname[KSTAT_STRLEN];
713 		char *ptr, lname[KSTAT_STRLEN];
714 
715 		i1 = t1 = l1 = 0;
716 		(void) get_lti(io->is_name, lname, &l1, tname, &t1, iname, &i1);
717 		*type = io->is_type;
718 		if (io->is_type == IODEV_DISK) {
719 			(void) snprintf(name, size, "%s%05d", lname, l1);
720 		} else if (io->is_type == IODEV_PARTITION) {
721 			ptr = strchr(io->is_name, ',');
722 			(void) snprintf(name, size, "%s%05d%s", lname, l1, ptr);
723 		} else {
724 			(void) snprintf(name, size, "%s%05d.%s%04d.%s%03d",
725 			    lname, l1, tname, t1, iname, i1);
726 			/* set to disk so we sort with disks */
727 			*type = IODEV_DISK;
728 		}
729 		(void) strlcpy(mod, lname, modlen);
730 		*inst = l1;
731 	} else {
732 		(void) strlcpy(mod, io->is_module, modlen);
733 		(void) strlcpy(name, io->is_name, size);
734 		*type = io->is_type;
735 		*inst = io->is_instance;
736 	}
737 }
738 
739 int
740 iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2)
741 {
742 	int	type1, type2;
743 	int	inst1, inst2;
744 	char	name1[KSTAT_STRLEN], name2[KSTAT_STRLEN];
745 	char	mod1[KSTAT_STRLEN], mod2[KSTAT_STRLEN];
746 
747 	get_path_info(io1, mod1, sizeof (mod1), &type1, &inst1, name1,
748 	    sizeof (name1));
749 	get_path_info(io2, mod2, sizeof (mod2), &type2, &inst2, name2,
750 	    sizeof (name2));
751 	if ((!disk_or_partition(type1)) ||
752 	    (!disk_or_partition(type2))) {
753 		/* neutral sort order between disk and part */
754 		if (type1 < type2) {
755 			return (-1);
756 		}
757 		if (type1 > type2) {
758 			return (1);
759 		}
760 	}
761 
762 	/* controller doesn't have ksp */
763 	if (io1->is_ksp && io2->is_ksp) {
764 		if (strcmp(mod1, mod2) != 0) {
765 			return (strcmp(mod1, mod2));
766 		}
767 		if (inst1 < inst2) {
768 			return (-1);
769 		}
770 		if (inst1 > inst2) {
771 			return (1);
772 		}
773 	} else {
774 		if (io1->is_id.id < io2->is_id.id) {
775 			return (-1);
776 		}
777 		if (io1->is_id.id > io2->is_id.id) {
778 			return (1);
779 		}
780 	}
781 
782 	return (strcmp(name1, name2));
783 }
784 
785 /* update the target reads and writes */
786 static void
787 update_target(struct iodev_snapshot *tgt, struct iodev_snapshot *path)
788 {
789 	tgt->is_stats.reads += path->is_stats.reads;
790 	tgt->is_stats.writes += path->is_stats.writes;
791 	tgt->is_stats.nread += path->is_stats.nread;
792 	tgt->is_stats.nwritten += path->is_stats.nwritten;
793 	tgt->is_stats.wcnt += path->is_stats.wcnt;
794 	tgt->is_stats.rcnt += path->is_stats.rcnt;
795 
796 	/*
797 	 * Stash the t_delta in the crtime for use in show_disk
798 	 * NOTE: this can't be done in show_disk because the
799 	 * itl entry is removed for the old format
800 	 */
801 	tgt->is_crtime += hrtime_delta(path->is_crtime, path->is_snaptime);
802 	tgt->is_snaptime += path->is_snaptime;
803 	tgt->is_nr_children += 1;
804 }
805 
806 /*
807  * Create a new synthetic device entry of the specified type. The supported
808  * synthetic types are IODEV_IOPATH_LT and IODEV_IOPATH_LI.
809  */
810 static struct iodev_snapshot *
811 make_extended_device(int type, struct iodev_snapshot *old)
812 {
813 	struct iodev_snapshot	*tptr = NULL;
814 	char			*ptr;
815 	int			lun, tgt, initiator;
816 	char			lun_name[KSTAT_STRLEN];
817 	char			tgt_name[KSTAT_STRLEN];
818 	char			initiator_name[KSTAT_STRLEN];
819 
820 	if (old == NULL)
821 		return (NULL);
822 	if (get_lti(old->is_name,
823 	    lun_name, &lun, tgt_name, &tgt, initiator_name, &initiator) != 1) {
824 		return (NULL);
825 	}
826 	tptr = safe_alloc(sizeof (*old));
827 	bzero(tptr, sizeof (*old));
828 	if (old->is_pretty != NULL) {
829 		tptr->is_pretty = safe_alloc(strlen(old->is_pretty) + 1);
830 		(void) strcpy(tptr->is_pretty, old->is_pretty);
831 	}
832 	bcopy(&old->is_parent_id, &tptr->is_parent_id,
833 	    sizeof (old->is_parent_id));
834 
835 	tptr->is_type = type;
836 
837 	if (type == IODEV_IOPATH_LT) {
838 		/* make new synthetic entry that is the LT */
839 		/* set the id to the target id */
840 		tptr->is_id.id = tgt;
841 		(void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
842 		    "%s%d", tgt_name, tgt);
843 		(void) snprintf(tptr->is_name, sizeof (tptr->is_name),
844 		    "%s%d.%s%d", lun_name, lun, tgt_name, tgt);
845 
846 		if (old->is_pretty) {
847 			ptr = strrchr(tptr->is_pretty, '.');
848 			if (ptr)
849 				*ptr = '\0';
850 		}
851 	} else if (type == IODEV_IOPATH_LI) {
852 		/* make new synthetic entry that is the LI */
853 		/* set the id to the initiator number */
854 		tptr->is_id.id = initiator;
855 		(void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
856 		    "%s%d", initiator_name, initiator);
857 		(void) snprintf(tptr->is_name, sizeof (tptr->is_name),
858 		    "%s%d.%s%d", lun_name, lun, initiator_name, initiator);
859 
860 		if (old->is_pretty) {
861 			ptr = strchr(tptr->is_pretty, '.');
862 			if (ptr)
863 				(void) snprintf(ptr + 1,
864 				    strlen(tptr->is_pretty) + 1,
865 				    "%s%d", initiator_name, initiator);
866 		}
867 	}
868 	return (tptr);
869 }
870 
871 /*
872  * This is to get the original -X LI format (e.g. ssd1.fp0). When an LTI kstat
873  * is found - traverse the children looking for the same initiator and sum
874  * them up. Add an LI entry and delete all of the LTI entries with the same
875  * initiator.
876  */
877 static int
878 create_li_delete_lti(struct snapshot *ss, struct iodev_snapshot *list)
879 {
880 	struct iodev_snapshot	*pos, *entry, *parent;
881 	int			lun, tgt, initiator;
882 	char			lun_name[KSTAT_STRLEN];
883 	char			tgt_name[KSTAT_STRLEN];
884 	char			initiator_name[KSTAT_STRLEN];
885 	int			err;
886 
887 	for (entry = list; entry; entry = entry->is_next) {
888 		if ((err = create_li_delete_lti(ss, entry->is_children)) != 0)
889 			return (err);
890 
891 		if (entry->is_type == IODEV_IOPATH_LTI) {
892 			parent = find_parent(ss, entry);
893 			if (get_lti(entry->is_name, lun_name, &lun,
894 			    tgt_name, &tgt, initiator_name, &initiator) != 1) {
895 				return (1);
896 			}
897 
898 			pos = (parent == NULL) ? NULL : parent->is_children;
899 			for (; pos; pos = pos->is_next) {
900 				if (pos->is_id.id != -1 &&
901 				    pos->is_id.id == initiator &&
902 				    pos->is_type == IODEV_IOPATH_LI) {
903 					/* found the same initiator */
904 					update_target(pos, entry);
905 					list_del(&parent->is_children, entry);
906 					free_iodev(entry);
907 					parent->is_nr_children--;
908 					entry = pos;
909 					break;
910 				}
911 			}
912 
913 			if (!pos) {
914 				/* make the first LI entry */
915 				pos = make_extended_device(
916 				    IODEV_IOPATH_LI, entry);
917 				update_target(pos, entry);
918 
919 				if (parent) {
920 					insert_before(&parent->is_children,
921 					    entry, pos);
922 					list_del(&parent->is_children, entry);
923 					free_iodev(entry);
924 				} else {
925 					insert_before(&ss->s_iodevs, entry,
926 					    pos);
927 					list_del(&ss->s_iodevs, entry);
928 					free_iodev(entry);
929 				}
930 				entry = pos;
931 			}
932 		}
933 	}
934 	return (0);
935 }
936 
937 /*
938  * We have the LTI kstat, now add an entry for the LT that sums up all of
939  * the LTI's with the same target(t).
940  */
941 static int
942 create_lt(struct snapshot *ss, struct iodev_snapshot *list)
943 {
944 	struct iodev_snapshot	*entry, *parent, *pos;
945 	int			lun, tgt, initiator;
946 	char			lun_name[KSTAT_STRLEN];
947 	char			tgt_name[KSTAT_STRLEN];
948 	char			initiator_name[KSTAT_STRLEN];
949 	int			err;
950 
951 	for (entry = list; entry; entry = entry->is_next) {
952 		if ((err = create_lt(ss, entry->is_children)) != 0)
953 			return (err);
954 
955 		if (entry->is_type == IODEV_IOPATH_LTI) {
956 			parent = find_parent(ss, entry);
957 			if (get_lti(entry->is_name, lun_name, &lun,
958 			    tgt_name, &tgt, initiator_name, &initiator) != 1) {
959 				return (1);
960 			}
961 
962 			pos = (parent == NULL) ? NULL : parent->is_children;
963 			for (; pos; pos = pos->is_next) {
964 				if (pos->is_id.id != -1 &&
965 				    pos->is_id.id == tgt &&
966 				    pos->is_type == IODEV_IOPATH_LT) {
967 					/* found the same target */
968 					update_target(pos, entry);
969 					break;
970 				}
971 			}
972 
973 			if (!pos) {
974 				pos = make_extended_device(
975 				    IODEV_IOPATH_LT, entry);
976 				update_target(pos, entry);
977 
978 				if (parent) {
979 					insert_before(&parent->is_children,
980 					    entry, pos);
981 					parent->is_nr_children++;
982 				} else {
983 					insert_before(&ss->s_iodevs,
984 					    entry, pos);
985 				}
986 			}
987 		}
988 	}
989 	return (0);
990 }
991 
992 /* Find the longest is_name field to aid formatting of output */
993 static int
994 iodevs_is_name_maxlen(struct iodev_snapshot *list)
995 {
996 	struct iodev_snapshot	*entry;
997 	int			max = 0, cmax, len;
998 
999 	for (entry = list; entry; entry = entry->is_next) {
1000 		cmax = iodevs_is_name_maxlen(entry->is_children);
1001 		max = (cmax > max) ? cmax : max;
1002 		len = strlen(entry->is_name);
1003 		max = (len > max) ? len : max;
1004 	}
1005 	return (max);
1006 }
1007 
1008 int
1009 acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df)
1010 {
1011 	kstat_t	*ksp;
1012 	struct	iodev_snapshot *pos;
1013 	struct	iodev_snapshot *list = NULL;
1014 	int	err = 0;
1015 
1016 	ss->s_nr_iodevs = 0;
1017 	ss->s_iodevs_is_name_maxlen = 0;
1018 
1019 	/*
1020 	 * Call cleanup_iodevs_snapshot() so that a cache miss in
1021 	 * lookup_ks_name() will result in a fresh snapshot.
1022 	 */
1023 	cleanup_iodevs_snapshot();
1024 
1025 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
1026 		enum iodev_type type;
1027 
1028 		if (ksp->ks_type != KSTAT_TYPE_IO)
1029 			continue;
1030 
1031 		/* e.g. "usb_byte_count" is not handled */
1032 		if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN)
1033 			continue;
1034 
1035 		if (df && !(type & df->if_allowed_types))
1036 			continue;
1037 
1038 		if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) {
1039 			err = errno;
1040 			goto out;
1041 		}
1042 
1043 		(void) memset(pos, 0, sizeof (struct iodev_snapshot));
1044 
1045 		pos->is_type = type;
1046 		pos->is_crtime = ksp->ks_crtime;
1047 		pos->is_snaptime = ksp->ks_snaptime;
1048 		pos->is_id.id = IODEV_NO_ID;
1049 		pos->is_parent_id.id = IODEV_NO_ID;
1050 		pos->is_ksp = ksp;
1051 		pos->is_instance = ksp->ks_instance;
1052 
1053 		(void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN);
1054 		(void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN);
1055 		get_pretty_name(ss->s_types, pos, kc);
1056 
1057 		/*
1058 		 * We must insert in sort order so e.g. vmstat -l
1059 		 * chooses in order.
1060 		 */
1061 		insert_into(&list, pos);
1062 	}
1063 
1064 	choose_iodevs(ss, list, df);
1065 
1066 	/* before acquire_stats for collate_controller()'s benefit */
1067 	if (ss->s_types & SNAP_IODEV_ERRORS) {
1068 		if ((err = acquire_iodev_errors(ss, kc)) != 0)
1069 			goto out;
1070 	}
1071 
1072 	if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0)
1073 		goto out;
1074 
1075 	if (ss->s_types & SNAP_IOPATHS_LTI) {
1076 		/*
1077 		 * -Y: kstats are LTI, need to create a synthetic LT
1078 		 * for -Y output.
1079 		 */
1080 		if ((err = create_lt(ss, ss->s_iodevs)) != 0) {
1081 			return (err);
1082 		}
1083 	}
1084 	if (ss->s_types & SNAP_IOPATHS_LI) {
1085 		/*
1086 		 * -X: kstats are LTI, need to create a synthetic LI and
1087 		 * delete the LTI for -X output
1088 		 */
1089 		if ((err = create_li_delete_lti(ss, ss->s_iodevs)) != 0) {
1090 			return (err);
1091 		}
1092 	}
1093 
1094 	/* determine width of longest is_name */
1095 	ss->s_iodevs_is_name_maxlen = iodevs_is_name_maxlen(ss->s_iodevs);
1096 
1097 	err = 0;
1098 out:
1099 	return (err);
1100 }
1101 
1102 void
1103 free_iodev(struct iodev_snapshot *iodev)
1104 {
1105 	while (iodev->is_children) {
1106 		struct iodev_snapshot *tmp = iodev->is_children;
1107 		iodev->is_children = iodev->is_children->is_next;
1108 		free_iodev(tmp);
1109 	}
1110 
1111 	if (iodev->avl_list) {
1112 		avl_remove(iodev->avl_list, iodev);
1113 		if (avl_numnodes(iodev->avl_list) == 0) {
1114 			avl_destroy(iodev->avl_list);
1115 			free(iodev->avl_list);
1116 		}
1117 	}
1118 
1119 	free(iodev->is_errors.ks_data);
1120 	free(iodev->is_pretty);
1121 	free(iodev->is_dname);
1122 	free(iodev->is_devid);
1123 	free(iodev);
1124 }
1125