OpenSolaris_b135/cmd/stat/common/acquire_iodevs.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "statcommon.h"
#include "dsr.h"

#include <sys/dklabel.h>
#include <sys/dktp/fdisk.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>
#include <limits.h>

static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev);

static struct iodev_snapshot *
make_controller(int cid)
{
	struct iodev_snapshot *new;

	new = safe_alloc(sizeof (struct iodev_snapshot));
	(void) memset(new, 0, sizeof (struct iodev_snapshot));
	new->is_type = IODEV_CONTROLLER;
	new->is_id.id = cid;
	new->is_parent_id.id = IODEV_NO_ID;

	(void) snprintf(new->is_name, sizeof (new->is_name), "c%d", cid);

	return (new);
}

static struct iodev_snapshot *
find_iodev_by_name(struct iodev_snapshot *list, const char *name)
{
	struct iodev_snapshot *pos;
	struct iodev_snapshot *pos2;

	for (pos = list; pos; pos = pos->is_next) {
		if (strcmp(pos->is_name, name) == 0)
			return (pos);

		pos2 = find_iodev_by_name(pos->is_children, name);
		if (pos2 != NULL)
			return (pos2);
	}

	return (NULL);
}

static enum iodev_type
parent_iodev_type(enum iodev_type type)
{
	switch (type) {
		case IODEV_CONTROLLER: return (0);
		case IODEV_IOPATH_LT: return (0);
		case IODEV_IOPATH_LI: return (0);
		case IODEV_NFS: return (0);
		case IODEV_TAPE: return (0);
		case IODEV_IOPATH_LTI: return (IODEV_DISK);
		case IODEV_DISK: return (IODEV_CONTROLLER);
		case IODEV_PARTITION: return (IODEV_DISK);
	}
	return (IODEV_UNKNOWN);
}

static int
id_match(struct iodev_id *id1, struct iodev_id *id2)
{
	return (id1->id == id2->id &&
	    strcmp(id1->tid, id2->tid) == 0);
}

static struct iodev_snapshot *
find_parent(struct snapshot *ss, struct iodev_snapshot *iodev)
{
	enum iodev_type parent_type = parent_iodev_type(iodev->is_type);
	struct iodev_snapshot *pos;
	struct iodev_snapshot *pos2;

	if (parent_type == 0 || parent_type == IODEV_UNKNOWN)
		return (NULL);

	if (iodev->is_parent_id.id == IODEV_NO_ID &&
	    iodev->is_parent_id.tid[0] == '\0')
		return (NULL);

	if (parent_type == IODEV_CONTROLLER) {
		for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
			if (pos->is_type != IODEV_CONTROLLER)
				continue;
			if (pos->is_id.id != iodev->is_parent_id.id)
				continue;
			return (pos);
		}

		if (!(ss->s_types & SNAP_CONTROLLERS))
			return (NULL);

		pos = make_controller(iodev->is_parent_id.id);
		insert_iodev(ss, pos);
		return (pos);
	}

	/* IODEV_DISK parent */
	for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
		if (id_match(&iodev->is_parent_id, &pos->is_id) &&
		    pos->is_type == IODEV_DISK)
			return (pos);
		if (pos->is_type != IODEV_CONTROLLER)
			continue;
		for (pos2 = pos->is_children; pos2; pos2 = pos2->is_next) {
			if (pos2->is_type != IODEV_DISK)
				continue;
			if (id_match(&iodev->is_parent_id, &pos2->is_id))
				return (pos2);
		}
	}

	return (NULL);
}

/*
 * Introduce an index into the list to speed up insert_into looking for the
 * right position in the list. This index is an AVL tree of all the
 * iodev_snapshot in the list.
 */

#define	offsetof(s, m)	(size_t)(&(((s *)0)->m))	/* for avl_create */

static int
avl_iodev_cmp(const void* is1, const void* is2)
{
	int c = iodev_cmp((struct iodev_snapshot *)is1,
	    (struct iodev_snapshot *)is2);

	if (c > 0)
		return (1);

	if (c < 0)
		return (-1);

	return (0);
}

static void
ix_new_list(struct iodev_snapshot *elem)
{
	avl_tree_t *l = malloc(sizeof (avl_tree_t));

	elem->avl_list = l;
	if (l == NULL)
		return;

	avl_create(l, avl_iodev_cmp, sizeof (struct iodev_snapshot),
	    offsetof(struct iodev_snapshot, avl_link));

	avl_add(l, elem);
}

static void
ix_list_del(struct iodev_snapshot *elem)
{
	avl_tree_t *l = elem->avl_list;

	if (l == NULL)
		return;

	elem->avl_list = NULL;

	avl_remove(l, elem);
	if (avl_numnodes(l) == 0) {
		avl_destroy(l);
		free(l);
	}
}

static void
ix_insert_here(struct iodev_snapshot *pos, struct iodev_snapshot *elem, int ba)
{
	avl_tree_t *l = pos->avl_list;
	elem->avl_list = l;

	if (l == NULL)
		return;

	avl_insert_here(l, elem, pos, ba);
}

static void
list_del(struct iodev_snapshot **list, struct iodev_snapshot *pos)
{
	ix_list_del(pos);

	if (*list == pos)
		*list = pos->is_next;
	if (pos->is_next)
		pos->is_next->is_prev = pos->is_prev;
	if (pos->is_prev)
		pos->is_prev->is_next = pos->is_next;
	pos->is_prev = pos->is_next = NULL;
}

static void
insert_before(struct iodev_snapshot **list, struct iodev_snapshot *pos,
    struct iodev_snapshot *new)
{
	if (pos == NULL) {
		new->is_prev = new->is_next = NULL;
		*list = new;
		ix_new_list(new);
		return;
	}

	new->is_next = pos;
	new->is_prev = pos->is_prev;
	if (pos->is_prev)
		pos->is_prev->is_next = new;
	else
		*list = new;
	pos->is_prev = new;

	ix_insert_here(pos, new, AVL_BEFORE);
}

static void
insert_after(struct iodev_snapshot **list, struct iodev_snapshot *pos,
    struct iodev_snapshot *new)
{
	if (pos == NULL) {
		new->is_prev = new->is_next = NULL;
		*list = new;
		ix_new_list(new);
		return;
	}

	new->is_next = pos->is_next;
	new->is_prev = pos;
	if (pos->is_next)
		pos->is_next->is_prev = new;
	pos->is_next = new;

	ix_insert_here(pos, new, AVL_AFTER);
}

static void
insert_into(struct iodev_snapshot **list, struct iodev_snapshot *iodev)
{
	struct iodev_snapshot *tmp = *list;
	avl_tree_t *l;
	void *p;
	avl_index_t where;

	if (*list == NULL) {
		*list = iodev;
		ix_new_list(iodev);
		return;
	}

	/*
	 * Optimize the search: instead of walking the entire list
	 * (which can contain thousands of nodes), search in the AVL
	 * tree the nearest node and reposition the startup point to
	 * this node rather than always starting from the beginning
	 * of the list.
	 */
	l = tmp->avl_list;
	if (l != NULL) {
		p = avl_find(l, iodev, &where);
		if (p == NULL) {
			p = avl_nearest(l, where, AVL_BEFORE);
		}
		if (p != NULL) {
			tmp = (struct iodev_snapshot *)p;
		}
	}

	for (;;) {
		if (iodev_cmp(tmp, iodev) > 0) {
			insert_before(list, tmp, iodev);
			return;
		}

		if (tmp->is_next == NULL)
			break;

		tmp = tmp->is_next;
	}

	insert_after(list, tmp, iodev);
}

static int
disk_or_partition(enum iodev_type type)
{
	return (type == IODEV_DISK || type == IODEV_PARTITION);
}

static int
disk_or_partition_or_iopath(enum iodev_type type)
{
	return (type == IODEV_DISK || type == IODEV_PARTITION ||
	    type == IODEV_IOPATH_LTI);
}

static void
insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev)
{
	struct iodev_snapshot *parent = find_parent(ss, iodev);
	struct iodev_snapshot **list;

	if (parent != NULL) {
		list = &parent->is_children;
		parent->is_nr_children++;
	} else {
		list = &ss->s_iodevs;
		ss->s_nr_iodevs++;
	}

	insert_into(list, iodev);
}

/* return 1 if dev passes filter */
static int
iodev_match(struct iodev_snapshot *dev, struct iodev_filter *df)
{
	int	is_floppy = (strncmp(dev->is_name, "fd", 2) == 0);
	char	*isn, *ispn, *ifn;
	char	*path;
	int	ifnl;
	size_t	i;

	/* no filter, pass */
	if (df == NULL)
		return (1);		/* pass */

	/* no filtered names, pass if not floppy and skipped */
	if (df->if_nr_names == NULL)
		return (!(df->if_skip_floppy && is_floppy));

	isn = dev->is_name;
	ispn = dev->is_pretty;
	for (i = 0; i < df->if_nr_names; i++) {
		ifn = df->if_names[i];
		ifnl = strlen(ifn);
		path = strchr(ifn, '.');

		if ((strcmp(isn, ifn) == 0) ||
		    (ispn && (strcmp(ispn, ifn) == 0)))
			return (1);	/* pass */

		/* if filter is a path allow partial match */
		if (path &&
		    ((strncmp(isn, ifn, ifnl) == 0) ||
		    (ispn && (strncmp(ispn, ifn, ifnl) == 0))))
			return (1);	/* pass */
	}

	return (0);			/* fail */
}

/* return 1 if path is an mpxio path associated with dev */
static int
iodev_path_match(struct iodev_snapshot *dev, struct iodev_snapshot *path)
{
	char	*dn, *pn;
	int	dnl;

	dn = dev->is_name;
	pn = path->is_name;
	dnl = strlen(dn);

	if ((strncmp(pn, dn, dnl) == 0) && (pn[dnl] == '.'))
		return (1);			/* yes */

	return (0);				/* no */
}

/* select which I/O devices to collect stats for */
static void
choose_iodevs(struct snapshot *ss, struct iodev_snapshot *iodevs,
    struct iodev_filter *df)
{
	struct iodev_snapshot	*pos, *ppos, *tmp, *ptmp;
	int			nr_iodevs;
	int			nr_iodevs_orig;

	nr_iodevs = df ? df->if_max_iodevs : UNLIMITED_IODEVS;
	nr_iodevs_orig = nr_iodevs;

	if (nr_iodevs == UNLIMITED_IODEVS)
		nr_iodevs = INT_MAX;

	/* add the full matches */
	pos = iodevs;
	while (pos && nr_iodevs) {
		tmp = pos;
		pos = pos->is_next;

		if (!iodev_match(tmp, df))
			continue;	/* failed full match */

		list_del(&iodevs, tmp);
		insert_iodev(ss, tmp);

		/*
		 * Add all mpxio paths associated with match above. Added
		 * paths don't count against nr_iodevs.
		 */
		if (strchr(tmp->is_name, '.') == NULL) {
		ppos = iodevs;
		while (ppos) {
			ptmp = ppos;
			ppos = ppos->is_next;

			if (!iodev_path_match(tmp, ptmp))
				continue;	/* not an mpxio path */

			list_del(&iodevs, ptmp);
			insert_iodev(ss, ptmp);
			if (pos == ptmp)
				pos = ppos;
		}
		}

		nr_iodevs--;
	}

	/*
	 * If we had a filter, and *nothing* passed the filter then we
	 * don't want to fill the  remaining slots - it is just confusing
	 * if we don that, it makes it look like the filter code is broken.
	 */
	if ((df->if_nr_names == NULL) || (nr_iodevs != nr_iodevs_orig)) {
		/* now insert any iodevs into the remaining slots */
		pos = iodevs;
		while (pos && nr_iodevs) {
			tmp = pos;
			pos = pos->is_next;

			if (df && df->if_skip_floppy &&
			    strncmp(tmp->is_name, "fd", 2) == 0)
				continue;

			list_del(&iodevs, tmp);
			insert_iodev(ss, tmp);

			--nr_iodevs;
		}
	}

	/* clear the unwanted ones */
	pos = iodevs;
	while (pos) {
		struct iodev_snapshot *tmp = pos;
		pos = pos->is_next;
		free_iodev(tmp);
	}
}

static int
collate_controller(struct iodev_snapshot *controller,
    struct iodev_snapshot *disk)
{
	controller->is_stats.nread += disk->is_stats.nread;
	controller->is_stats.nwritten += disk->is_stats.nwritten;
	controller->is_stats.reads += disk->is_stats.reads;
	controller->is_stats.writes += disk->is_stats.writes;
	controller->is_stats.wtime += disk->is_stats.wtime;
	controller->is_stats.wlentime += disk->is_stats.wlentime;
	controller->is_stats.rtime += disk->is_stats.rtime;
	controller->is_stats.rlentime += disk->is_stats.rlentime;
	controller->is_crtime += disk->is_crtime;
	controller->is_snaptime += disk->is_snaptime;
	if (kstat_add(&disk->is_errors, &controller->is_errors))
		return (errno);
	return (0);
}

static int
acquire_iodev_stats(struct iodev_snapshot *list, kstat_ctl_t *kc)
{
	struct iodev_snapshot *pos;
	int err = 0;

	for (pos = list; pos; pos = pos->is_next) {
		/* controllers don't have stats (yet) */
		if (pos->is_ksp != NULL) {
			if (kstat_read(kc, pos->is_ksp, &pos->is_stats) == -1)
				return (errno);
			/* make sure crtime/snaptime is updated */
			pos->is_crtime = pos->is_ksp->ks_crtime;
			pos->is_snaptime = pos->is_ksp->ks_snaptime;
		}

		if ((err = acquire_iodev_stats(pos->is_children, kc)))
			return (err);

		if (pos->is_type == IODEV_CONTROLLER) {
			struct iodev_snapshot *pos2 = pos->is_children;

			for (; pos2; pos2 = pos2->is_next) {
				if ((err = collate_controller(pos, pos2)))
					return (err);
			}
		}
	}

	return (0);
}

static int
acquire_iodev_errors(struct snapshot *ss, kstat_ctl_t *kc)
{
	kstat_t *ksp;

	if (!(ss->s_types && SNAP_IODEV_ERRORS))
		return (0);

	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
		char kstat_name[KSTAT_STRLEN];
		char *dname = kstat_name;
		char *ename = ksp->ks_name;
		struct iodev_snapshot *iodev;

		if (ksp->ks_type != KSTAT_TYPE_NAMED)
			continue;
		if (strncmp(ksp->ks_class, "device_error", 12) != 0 &&
		    strncmp(ksp->ks_class, "iopath_error", 12) != 0)
			continue;

		/*
		 * Some drivers may not follow the naming convention
		 * for error kstats (i.e., drivername,err) so
		 * be sure we don't walk off the end.
		 */
		while (*ename && *ename != ',') {
			*dname = *ename;
			dname++;
			ename++;
		}
		*dname = '\0';

		iodev = find_iodev_by_name(ss->s_iodevs, kstat_name);

		if (iodev == NULL)
			continue;

		if (kstat_read(kc, ksp, NULL) == -1)
			return (errno);
		if (kstat_copy(ksp, &iodev->is_errors) == -1)
			return (errno);
	}

	return (0);
}

static void
get_ids(struct iodev_snapshot *iodev, const char *pretty)
{
	int ctr, disk, slice, ret;
	char *target;
	const char *p1;
	const char *p2;

	if (pretty == NULL)
		return;

	if (sscanf(pretty, "c%d", &ctr) != 1)
		return;

	p1 = pretty;
	while (*p1 && *p1 != 't')
		++p1;

	if (!*p1)
		return;
	++p1;

	p2 = p1;
	while (*p2 && *p2 != 'd')
		++p2;

	if (!*p2 || p2 == p1)
		return;

	target = safe_alloc(1 + p2 - p1);
	(void) strlcpy(target, p1, 1 + p2 - p1);

	ret = sscanf(p2, "d%d%*[sp]%d", &disk, &slice);

	if (ret == 2 && iodev->is_type == IODEV_PARTITION) {
		iodev->is_id.id = slice;
		iodev->is_parent_id.id = disk;
		(void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN);
	} else if (ret == 1) {
		if (iodev->is_type == IODEV_DISK) {
			iodev->is_id.id = disk;
			(void) strlcpy(iodev->is_id.tid, target, KSTAT_STRLEN);
			iodev->is_parent_id.id = ctr;
		} else if (iodev->is_type == IODEV_IOPATH_LTI) {
			iodev->is_parent_id.id = disk;
			(void) strlcpy(iodev->is_parent_id.tid,
			    target, KSTAT_STRLEN);
		}
	}

	free(target);
}

static void
get_pretty_name(enum snapshot_types types, struct iodev_snapshot *iodev,
	kstat_ctl_t *kc)
{
	disk_list_t	*dl;
	char		*pretty = NULL;

	if (iodev->is_type == IODEV_NFS) {
		if (!(types & SNAP_IODEV_PRETTY))
			return;

		iodev->is_pretty = lookup_nfs_name(iodev->is_name, kc);
		return;
	}

	/* lookup/translate the kstat name */
	dl = lookup_ks_name(iodev->is_name, (types & SNAP_IODEV_DEVID) ? 1 : 0);
	if (dl == NULL)
		return;

	if (dl->dsk)
		pretty = safe_strdup(dl->dsk);

	if (types & SNAP_IODEV_PRETTY) {
		if (dl->dname)
			iodev->is_dname = safe_strdup(dl->dname);
	}

	if (dl->devidstr)
		iodev->is_devid = safe_strdup(dl->devidstr);

	get_ids(iodev, pretty);

	/*
	 * we fill in pretty name wether it is asked for or not because
	 * it could be used in a filter by match_iodevs.
	 */
	iodev->is_pretty = pretty;
}

static enum iodev_type
get_iodev_type(kstat_t *ksp)
{
	if (strcmp(ksp->ks_class, "disk") == 0)
		return (IODEV_DISK);
	if (strcmp(ksp->ks_class, "partition") == 0)
		return (IODEV_PARTITION);
	if (strcmp(ksp->ks_class, "nfs") == 0)
		return (IODEV_NFS);
	if (strcmp(ksp->ks_class, "iopath") == 0)
		return (IODEV_IOPATH_LTI);
	if (strcmp(ksp->ks_class, "tape") == 0)
		return (IODEV_TAPE);
	return (IODEV_UNKNOWN);
}

/* get the lun/target/initiator from the name, return 1 on success */
static int
get_lti(char *s,
	char *lname, int *l, char *tname, int *t, char *iname, int *i)
{
	int  num = 0;

	num = sscanf(s, "%[a-z]%d%*[.]%[a-z]%d%*[.]%[a-z_]%d", lname, l,
	    tname, t, iname, i);
	return ((num == 6) ? 1 : 0);
}


/* get the lun, target, and initiator name and instance */
static void
get_path_info(struct iodev_snapshot *io, char *mod, int *type, int *inst,
    char *name, size_t size)
{

	/*
	 * If it is iopath or ssd then pad the name with i/t/l so we can sort
	 * by alpha order and set type for IOPATH to DISK since we want to
	 * have it grouped with its ssd parent. The lun can be 5 digits,
	 * the target can be 4 digits, and the initiator can be 3 digits and
	 * the padding is done appropriately for string comparisons.
	 */
	if (disk_or_partition_or_iopath(io->is_type)) {
		int i1, t1, l1;
		char tname[KSTAT_STRLEN], iname[KSTAT_STRLEN];
		char *ptr, lname[KSTAT_STRLEN];

		i1 = t1 = l1 = 0;
		(void) get_lti(io->is_name, lname, &l1, tname, &t1, iname, &i1);
		*type = io->is_type;
		if (io->is_type == IODEV_DISK) {
			(void) snprintf(name, size, "%s%05d", lname, l1);
		} else if (io->is_type == IODEV_PARTITION) {
			ptr = strchr(io->is_name, ',');
			(void) snprintf(name, size, "%s%05d%s", lname, l1, ptr);
		} else {
			(void) snprintf(name, size, "%s%05d.%s%04d.%s%03d",
			    lname, l1, tname, t1, iname, i1);
			/* set to disk so we sort with disks */
			*type = IODEV_DISK;
		}
		(void) strcpy(mod, lname);
		*inst = l1;
	} else {
		(void) strcpy(mod, io->is_module);
		(void) strcpy(name, io->is_name);
		*type = io->is_type;
		*inst = io->is_instance;
	}
}

int
iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2)
{
	int	type1, type2;
	int	inst1, inst2;
	char	name1[KSTAT_STRLEN], name2[KSTAT_STRLEN];
	char	mod1[KSTAT_STRLEN], mod2[KSTAT_STRLEN];

	get_path_info(io1, mod1, &type1, &inst1, name1, sizeof (name1));
	get_path_info(io2, mod2, &type2, &inst2, name2, sizeof (name2));
	if ((!disk_or_partition(type1)) ||
	    (!disk_or_partition(type2))) {
		/* neutral sort order between disk and part */
		if (type1 < type2) {
			return (-1);
		}
		if (type1 > type2) {
			return (1);
		}
	}

	/* controller doesn't have ksp */
	if (io1->is_ksp && io2->is_ksp) {
		if (strcmp(mod1, mod2) != 0) {
			return (strcmp(mod1, mod2));
		}
		if (inst1 < inst2) {
			return (-1);
		}
		if (inst1 > inst2) {
			return (1);
		}
	} else {
		if (io1->is_id.id < io2->is_id.id) {
			return (-1);
		}
		if (io1->is_id.id > io2->is_id.id) {
			return (1);
		}
	}

	return (strcmp(name1, name2));
}

/* update the target reads and writes */
static void
update_target(struct iodev_snapshot *tgt, struct iodev_snapshot *path)
{
	tgt->is_stats.reads += path->is_stats.reads;
	tgt->is_stats.writes += path->is_stats.writes;
	tgt->is_stats.nread += path->is_stats.nread;
	tgt->is_stats.nwritten += path->is_stats.nwritten;
	tgt->is_stats.wcnt += path->is_stats.wcnt;
	tgt->is_stats.rcnt += path->is_stats.rcnt;

	/*
	 * Stash the t_delta in the crtime for use in show_disk
	 * NOTE: this can't be done in show_disk because the
	 * itl entry is removed for the old format
	 */
	tgt->is_crtime += hrtime_delta(path->is_crtime, path->is_snaptime);
	tgt->is_snaptime += path->is_snaptime;
	tgt->is_nr_children += 1;
}

/*
 * Create a new synthetic device entry of the specified type. The supported
 * synthetic types are IODEV_IOPATH_LT and IODEV_IOPATH_LI.
 */
static struct iodev_snapshot *
make_extended_device(int type, struct iodev_snapshot *old)
{
	struct iodev_snapshot	*tptr = NULL;
	char			*ptr;
	int			lun, tgt, initiator;
	char			lun_name[KSTAT_STRLEN];
	char			tgt_name[KSTAT_STRLEN];
	char			initiator_name[KSTAT_STRLEN];

	if (old == NULL)
		return (NULL);
	if (get_lti(old->is_name,
	    lun_name, &lun, tgt_name, &tgt, initiator_name, &initiator) != 1) {
		return (NULL);
	}
	tptr = safe_alloc(sizeof (*old));
	bzero(tptr, sizeof (*old));
	if (old->is_pretty != NULL) {
		tptr->is_pretty = safe_alloc(strlen(old->is_pretty) + 1);
		(void) strcpy(tptr->is_pretty, old->is_pretty);
	}
	bcopy(&old->is_parent_id, &tptr->is_parent_id,
	    sizeof (old->is_parent_id));

	tptr->is_type = type;

	if (type == IODEV_IOPATH_LT) {
		/* make new synthetic entry that is the LT */
		/* set the id to the target id */
		tptr->is_id.id = tgt;
		(void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
		    "%s%d", tgt_name, tgt);
		(void) snprintf(tptr->is_name, sizeof (tptr->is_name),
		    "%s%d.%s%d", lun_name, lun, tgt_name, tgt);

		if (old->is_pretty) {
			ptr = strrchr(tptr->is_pretty, '.');
			if (ptr)
				*ptr = '\0';
		}
	} else if (type == IODEV_IOPATH_LI) {
		/* make new synthetic entry that is the LI */
		/* set the id to the initiator number */
		tptr->is_id.id = initiator;
		(void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
		    "%s%d", initiator_name, initiator);
		(void) snprintf(tptr->is_name, sizeof (tptr->is_name),
		    "%s%d.%s%d", lun_name, lun, initiator_name, initiator);

		if (old->is_pretty) {
			ptr = strchr(tptr->is_pretty, '.');
			if (ptr)
				(void) snprintf(ptr + 1,
				    strlen(tptr->is_pretty) + 1,
				    "%s%d", initiator_name, initiator);
		}
	}
	return (tptr);
}

/*
 * This is to get the original -X LI format (e.g. ssd1.fp0). When an LTI kstat
 * is found - traverse the children looking for the same initiator and sum
 * them up. Add an LI entry and delete all of the LTI entries with the same
 * initiator.
 */
static int
create_li_delete_lti(struct snapshot *ss, struct iodev_snapshot *list)
{
	struct iodev_snapshot	*pos, *entry, *parent;
	int			lun, tgt, initiator;
	char			lun_name[KSTAT_STRLEN];
	char			tgt_name[KSTAT_STRLEN];
	char			initiator_name[KSTAT_STRLEN];
	int			err;

	for (entry = list; entry; entry = entry->is_next) {
		if ((err = create_li_delete_lti(ss, entry->is_children)) != 0)
			return (err);

		if (entry->is_type == IODEV_IOPATH_LTI) {
			parent = find_parent(ss, entry);
			if (get_lti(entry->is_name, lun_name, &lun,
			    tgt_name, &tgt, initiator_name, &initiator) != 1) {
				return (1);
			}

			pos = (parent == NULL) ? NULL : parent->is_children;
			for (; pos; pos = pos->is_next) {
				if (pos->is_id.id != -1 &&
				    pos->is_id.id == initiator &&
				    pos->is_type == IODEV_IOPATH_LI) {
					/* found the same initiator */
					update_target(pos, entry);
					list_del(&parent->is_children, entry);
					free_iodev(entry);
					parent->is_nr_children--;
					entry = pos;
					break;
				}
			}

			if (!pos) {
				/* make the first LI entry */
				pos = make_extended_device(
				    IODEV_IOPATH_LI, entry);
				update_target(pos, entry);

				if (parent) {
					insert_before(&parent->is_children,
					    entry, pos);
					list_del(&parent->is_children, entry);
					free_iodev(entry);
				} else {
					insert_before(&ss->s_iodevs, entry,
					    pos);
					list_del(&ss->s_iodevs, entry);
					free_iodev(entry);
				}
				entry = pos;
			}
		}
	}
	return (0);
}

/*
 * We have the LTI kstat, now add an entry for the LT that sums up all of
 * the LTI's with the same target(t).
 */
static int
create_lt(struct snapshot *ss, struct iodev_snapshot *list)
{
	struct iodev_snapshot	*entry, *parent, *pos;
	int			lun, tgt, initiator;
	char			lun_name[KSTAT_STRLEN];
	char			tgt_name[KSTAT_STRLEN];
	char			initiator_name[KSTAT_STRLEN];
	int			err;

	for (entry = list; entry; entry = entry->is_next) {
		if ((err = create_lt(ss, entry->is_children)) != 0)
			return (err);

		if (entry->is_type == IODEV_IOPATH_LTI) {
			parent = find_parent(ss, entry);
			if (get_lti(entry->is_name, lun_name, &lun,
			    tgt_name, &tgt, initiator_name, &initiator) != 1) {
				return (1);
			}

			pos = (parent == NULL) ? NULL : parent->is_children;
			for (; pos; pos = pos->is_next) {
				if (pos->is_id.id != -1 &&
				    pos->is_id.id == tgt &&
				    pos->is_type == IODEV_IOPATH_LT) {
					/* found the same target */
					update_target(pos, entry);
					break;
				}
			}

			if (!pos) {
				pos = make_extended_device(
				    IODEV_IOPATH_LT, entry);
				update_target(pos, entry);

				if (parent) {
					insert_before(&parent->is_children,
					    entry, pos);
					parent->is_nr_children++;
				} else {
					insert_before(&ss->s_iodevs,
					    entry, pos);
				}
			}
		}
	}
	return (0);
}

/* Find the longest is_name field to aid formatting of output */
static int
iodevs_is_name_maxlen(struct iodev_snapshot *list)
{
	struct iodev_snapshot	*entry;
	int			max = 0, cmax, len;

	for (entry = list; entry; entry = entry->is_next) {
		cmax = iodevs_is_name_maxlen(entry->is_children);
		max = (cmax > max) ? cmax : max;
		len = strlen(entry->is_name);
		max = (len > max) ? len : max;
	}
	return (max);
}

int
acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df)
{
	kstat_t	*ksp;
	struct	iodev_snapshot *pos;
	struct	iodev_snapshot *list = NULL;
	int	err = 0;

	ss->s_nr_iodevs = 0;
	ss->s_iodevs_is_name_maxlen = 0;

	/*
	 * Call cleanup_iodevs_snapshot() so that a cache miss in
	 * lookup_ks_name() will result in a fresh snapshot.
	 */
	cleanup_iodevs_snapshot();

	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
		enum iodev_type type;

		if (ksp->ks_type != KSTAT_TYPE_IO)
			continue;

		/* e.g. "usb_byte_count" is not handled */
		if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN)
			continue;

		if (df && !(type & df->if_allowed_types))
			continue;

		if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) {
			err = errno;
			goto out;
		}

		(void) memset(pos, 0, sizeof (struct iodev_snapshot));

		pos->is_type = type;
		pos->is_crtime = ksp->ks_crtime;
		pos->is_snaptime = ksp->ks_snaptime;
		pos->is_id.id = IODEV_NO_ID;
		pos->is_parent_id.id = IODEV_NO_ID;
		pos->is_ksp = ksp;
		pos->is_instance = ksp->ks_instance;

		(void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN);
		(void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN);
		get_pretty_name(ss->s_types, pos, kc);

		/*
		 * We must insert in sort order so e.g. vmstat -l
		 * chooses in order.
		 */
		insert_into(&list, pos);
	}

	choose_iodevs(ss, list, df);

	/* before acquire_stats for collate_controller()'s benefit */
	if (ss->s_types & SNAP_IODEV_ERRORS) {
		if ((err = acquire_iodev_errors(ss, kc)) != 0)
			goto out;
	}

	if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0)
		goto out;

	if (ss->s_types & SNAP_IOPATHS_LTI) {
		/*
		 * -Y: kstats are LTI, need to create a synthetic LT
		 * for -Y output.
		 */
		if ((err = create_lt(ss, ss->s_iodevs)) != 0) {
			return (err);
		}
	}
	if (ss->s_types & SNAP_IOPATHS_LI) {
		/*
		 * -X: kstats are LTI, need to create a synthetic LI and
		 * delete the LTI for -X output
		 */
		if ((err = create_li_delete_lti(ss, ss->s_iodevs)) != 0) {
			return (err);
		}
	}

	/* determine width of longest is_name */
	ss->s_iodevs_is_name_maxlen = iodevs_is_name_maxlen(ss->s_iodevs);

	err = 0;
out:
	return (err);
}

void
free_iodev(struct iodev_snapshot *iodev)
{
	while (iodev->is_children) {
		struct iodev_snapshot *tmp = iodev->is_children;
		iodev->is_children = iodev->is_children->is_next;
		free_iodev(tmp);
	}

	if (iodev->avl_list) {
		avl_remove(iodev->avl_list, iodev);
		if (avl_numnodes(iodev->avl_list) == 0) {
			avl_destroy(iodev->avl_list);
			free(iodev->avl_list);
		}
	}

	free(iodev->is_errors.ks_data);
	free(iodev->is_pretty);
	free(iodev->is_dname);
	free(iodev->is_devid);
	free(iodev);
}