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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include "statcommon.h"
30 #include "dsr.h"
31 
32 #include <sys/dklabel.h>
33 #include <sys/dktp/fdisk.h>
34 #include <stdlib.h>
35 #include <stdarg.h>
36 #include <unistd.h>
37 #include <strings.h>
38 #include <errno.h>
39 #include <limits.h>
40 
41 static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev);
42 
43 static struct iodev_snapshot *
44 make_controller(int cid)
45 {
46 	struct iodev_snapshot *new;
47 
48 	new = safe_alloc(sizeof (struct iodev_snapshot));
49 	(void) memset(new, 0, sizeof (struct iodev_snapshot));
50 	new->is_type = IODEV_CONTROLLER;
51 	new->is_id.id = cid;
52 	new->is_parent_id.id = IODEV_NO_ID;
53 
54 	(void) snprintf(new->is_name, sizeof (new->is_name), "c%d", cid);
55 
56 	return (new);
57 }
58 
59 static struct iodev_snapshot *
60 find_iodev_by_name(struct iodev_snapshot *list, const char *name)
61 {
62 	struct iodev_snapshot *pos;
63 	struct iodev_snapshot *pos2;
64 
65 	for (pos = list; pos; pos = pos->is_next) {
66 		if (strcmp(pos->is_name, name) == 0)
67 			return (pos);
68 
69 		pos2 = find_iodev_by_name(pos->is_children, name);
70 		if (pos2 != NULL)
71 			return (pos2);
72 	}
73 
74 	return (NULL);
75 }
76 
77 static enum iodev_type
78 parent_iodev_type(enum iodev_type type)
79 {
80 	switch (type) {
81 		case IODEV_CONTROLLER: return (0);
82 		case IODEV_NFS: return (0);
83 		case IODEV_TAPE: return (0);
84 		case IODEV_IOPATH: return (IODEV_DISK);
85 		case IODEV_DISK: return (IODEV_CONTROLLER);
86 		case IODEV_PARTITION: return (IODEV_DISK);
87 	}
88 	return (IODEV_UNKNOWN);
89 }
90 
91 static int
92 id_match(struct iodev_id *id1, struct iodev_id *id2)
93 {
94 	return (id1->id == id2->id &&
95 		strcmp(id1->tid, id2->tid) == 0);
96 }
97 
98 static struct iodev_snapshot *
99 find_parent(struct snapshot *ss, struct iodev_snapshot *iodev)
100 {
101 	enum iodev_type parent_type = parent_iodev_type(iodev->is_type);
102 	struct iodev_snapshot *pos;
103 	struct iodev_snapshot *pos2;
104 
105 	if (parent_type == 0 || parent_type == IODEV_UNKNOWN)
106 		return (NULL);
107 
108 	if (iodev->is_parent_id.id == IODEV_NO_ID &&
109 	    iodev->is_parent_id.tid[0] == '\0')
110 		return (NULL);
111 
112 	if (parent_type == IODEV_CONTROLLER) {
113 		for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
114 			if (pos->is_type != IODEV_CONTROLLER)
115 				continue;
116 			if (pos->is_id.id != iodev->is_parent_id.id)
117 				continue;
118 			return (pos);
119 		}
120 
121 		if (!(ss->s_types & SNAP_CONTROLLERS))
122 			return (NULL);
123 
124 		pos = make_controller(iodev->is_parent_id.id);
125 		insert_iodev(ss, pos);
126 		return (pos);
127 	}
128 
129 	/* IODEV_DISK parent */
130 	for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
131 		if (id_match(&iodev->is_parent_id, &pos->is_id) &&
132 		    pos->is_type == IODEV_DISK)
133 			return (pos);
134 		if (pos->is_type != IODEV_CONTROLLER)
135 			continue;
136 		for (pos2 = pos->is_children; pos2; pos2 = pos2->is_next) {
137 			if (pos2->is_type != IODEV_DISK)
138 				continue;
139 			if (id_match(&iodev->is_parent_id, &pos2->is_id))
140 				return (pos2);
141 		}
142 	}
143 
144 	return (NULL);
145 }
146 
147 static void
148 list_del(struct iodev_snapshot **list, struct iodev_snapshot *pos)
149 {
150 	if (*list == pos)
151 		*list = pos->is_next;
152 	if (pos->is_next)
153 		pos->is_next->is_prev = pos->is_prev;
154 	if (pos->is_prev)
155 		pos->is_prev->is_next = pos->is_next;
156 	pos->is_prev = pos->is_next = NULL;
157 }
158 
159 static void
160 insert_before(struct iodev_snapshot **list, struct iodev_snapshot *pos,
161     struct iodev_snapshot *new)
162 {
163 	if (pos == NULL) {
164 		new->is_prev = new->is_next = NULL;
165 		*list = new;
166 		return;
167 	}
168 
169 	new->is_next = pos;
170 	new->is_prev = pos->is_prev;
171 	if (pos->is_prev)
172 		pos->is_prev->is_next = new;
173 	else
174 		*list = new;
175 	pos->is_prev = new;
176 }
177 
178 static void
179 insert_after(struct iodev_snapshot **list, struct iodev_snapshot *pos,
180     struct iodev_snapshot *new)
181 {
182 	if (pos == NULL) {
183 		new->is_prev = new->is_next = NULL;
184 		*list = new;
185 		return;
186 	}
187 
188 	new->is_next = pos->is_next;
189 	new->is_prev = pos;
190 	if (pos->is_next)
191 		pos->is_next->is_prev = new;
192 	pos->is_next = new;
193 }
194 
195 static void
196 insert_into(struct iodev_snapshot **list, struct iodev_snapshot *iodev)
197 {
198 	struct iodev_snapshot *tmp = *list;
199 	if (*list == NULL) {
200 		*list = iodev;
201 		return;
202 	}
203 
204 	for (;;) {
205 		if (iodev_cmp(tmp, iodev) > 0) {
206 			insert_before(list, tmp, iodev);
207 			return;
208 		}
209 
210 		if (tmp->is_next == NULL)
211 			break;
212 
213 		tmp = tmp->is_next;
214 	}
215 
216 	insert_after(list, tmp, iodev);
217 }
218 
219 static int
220 disk_or_partition(enum iodev_type type)
221 {
222 	return (type == IODEV_DISK || type == IODEV_PARTITION);
223 }
224 
225 static void
226 insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev)
227 {
228 	struct iodev_snapshot *parent = find_parent(ss, iodev);
229 	struct iodev_snapshot **list;
230 
231 	if (parent != NULL) {
232 		list = &parent->is_children;
233 		parent->is_nr_children++;
234 	} else {
235 		list = &ss->s_iodevs;
236 		ss->s_nr_iodevs++;
237 	}
238 
239 	insert_into(list, iodev);
240 }
241 
242 static int
243 iodev_match(struct iodev_snapshot *dev, struct iodev_filter *df)
244 {
245 	size_t i;
246 	int is_floppy = (strncmp(dev->is_name, "fd", 2) == 0);
247 
248 	/* no filter, pass */
249 	if (df == NULL)
250 		return (1);
251 
252 	/* no filtered names, pass if not floppy and skipped */
253 	if (df->if_nr_names == NULL)
254 		return (!(df->if_skip_floppy && is_floppy));
255 
256 	for (i = 0; i < df->if_nr_names; i++) {
257 		if (strcmp(dev->is_name, df->if_names[i]) == 0)
258 			return (1);
259 		if (dev->is_pretty != NULL &&
260 		    strcmp(dev->is_pretty, df->if_names[i]) == 0)
261 			return (1);
262 	}
263 
264 	/* not found in specified names, fail match */
265 	return (0);
266 }
267 
268 /* select which I/O devices to collect stats for */
269 static void
270 choose_iodevs(struct snapshot *ss, struct iodev_snapshot *iodevs,
271     struct iodev_filter *df)
272 {
273 	struct iodev_snapshot *pos = iodevs;
274 	int nr_iodevs = df ? df->if_max_iodevs : UNLIMITED_IODEVS;
275 
276 	if (nr_iodevs == UNLIMITED_IODEVS)
277 		nr_iodevs = INT_MAX;
278 
279 	while (pos && nr_iodevs) {
280 		struct iodev_snapshot *tmp = pos;
281 		pos = pos->is_next;
282 
283 		if (!iodev_match(tmp, df))
284 			continue;
285 
286 		list_del(&iodevs, tmp);
287 		insert_iodev(ss, tmp);
288 
289 		--nr_iodevs;
290 	}
291 
292 	pos = iodevs;
293 
294 	/* now insert any iodevs into the remaining slots */
295 	while (pos && nr_iodevs) {
296 		struct iodev_snapshot *tmp = pos;
297 		pos = pos->is_next;
298 
299 		if (df && df->if_skip_floppy &&
300 			strncmp(tmp->is_name, "fd", 2) == 0)
301 			continue;
302 
303 		list_del(&iodevs, tmp);
304 		insert_iodev(ss, tmp);
305 
306 		--nr_iodevs;
307 	}
308 
309 	/* clear the unwanted ones */
310 	pos = iodevs;
311 	while (pos) {
312 		struct iodev_snapshot *tmp = pos;
313 		pos = pos->is_next;
314 		free_iodev(tmp);
315 	}
316 }
317 
318 static int
319 collate_controller(struct iodev_snapshot *controller,
320     struct iodev_snapshot *disk)
321 {
322 	controller->is_stats.nread += disk->is_stats.nread;
323 	controller->is_stats.nwritten += disk->is_stats.nwritten;
324 	controller->is_stats.reads += disk->is_stats.reads;
325 	controller->is_stats.writes += disk->is_stats.writes;
326 	controller->is_stats.wtime += disk->is_stats.wtime;
327 	controller->is_stats.wlentime += disk->is_stats.wlentime;
328 	controller->is_stats.rtime += disk->is_stats.rtime;
329 	controller->is_stats.rlentime += disk->is_stats.rlentime;
330 	controller->is_crtime += disk->is_crtime;
331 	controller->is_snaptime += disk->is_snaptime;
332 	if (kstat_add(&disk->is_errors, &controller->is_errors))
333 		return (errno);
334 	return (0);
335 }
336 
337 static int
338 acquire_iodev_stats(struct iodev_snapshot *list, kstat_ctl_t *kc)
339 {
340 	struct iodev_snapshot *pos;
341 	int err = 0;
342 
343 	for (pos = list; pos; pos = pos->is_next) {
344 		/* controllers don't have stats (yet) */
345 		if (pos->is_ksp != NULL) {
346 			if (kstat_read(kc, pos->is_ksp, &pos->is_stats) == -1)
347 				return (errno);
348 			/* make sure crtime/snaptime is updated */
349 			pos->is_crtime = pos->is_ksp->ks_crtime;
350 			pos->is_snaptime = pos->is_ksp->ks_snaptime;
351 		}
352 
353 		if ((err = acquire_iodev_stats(pos->is_children, kc)))
354 			return (err);
355 
356 		if (pos->is_type == IODEV_CONTROLLER) {
357 			struct iodev_snapshot *pos2 = pos->is_children;
358 
359 			for (; pos2; pos2 = pos2->is_next) {
360 				if ((err = collate_controller(pos, pos2)))
361 					return (err);
362 			}
363 		}
364 	}
365 
366 	return (0);
367 }
368 
369 static int
370 acquire_iodev_errors(struct snapshot *ss, kstat_ctl_t *kc)
371 {
372 	kstat_t *ksp;
373 
374 	if (!(ss->s_types && SNAP_IODEV_ERRORS))
375 		return (0);
376 
377 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
378 		char kstat_name[KSTAT_STRLEN];
379 		char *dname = kstat_name;
380 		char *ename = ksp->ks_name;
381 		struct iodev_snapshot *iodev;
382 
383 		if (ksp->ks_type != KSTAT_TYPE_NAMED)
384 			continue;
385 		if (strncmp(ksp->ks_class, "device_error", 12) != 0 &&
386 		    strncmp(ksp->ks_class, "iopath_error", 12) != 0)
387 			continue;
388 
389 		/*
390 		 * Some drivers may not follow the naming convention
391 		 * for error kstats (i.e., drivername,err) so
392 		 * be sure we don't walk off the end.
393 		 */
394 		while (*ename && *ename != ',') {
395 			*dname = *ename;
396 			dname++;
397 			ename++;
398 		}
399 		*dname = '\0';
400 
401 		iodev = find_iodev_by_name(ss->s_iodevs, kstat_name);
402 
403 		if (iodev == NULL)
404 			continue;
405 
406 		if (kstat_read(kc, ksp, NULL) == -1)
407 			return (errno);
408 		if (kstat_copy(ksp, &iodev->is_errors) == -1)
409 			return (errno);
410 	}
411 
412 	return (0);
413 }
414 
415 static void
416 get_ids(struct iodev_snapshot *iodev, const char *pretty)
417 {
418 	int ctr, disk, slice, ret;
419 	char *target;
420 	const char *p1;
421 	const char *p2;
422 
423 	if (pretty == NULL)
424 		return;
425 
426 	if (sscanf(pretty, "c%d", &ctr) != 1)
427 		return;
428 
429 	p1 = pretty;
430 	while (*p1 && *p1 != 't')
431 		++p1;
432 
433 	if (!*p1)
434 		return;
435 	++p1;
436 
437 	p2 = p1;
438 	while (*p2 && *p2 != 'd')
439 		++p2;
440 
441 	if (!*p2 || p2 == p1)
442 		return;
443 
444 	target = safe_alloc(1 + p2 - p1);
445 	(void) strlcpy(target, p1, 1 + p2 - p1);
446 
447 	ret = sscanf(p2, "d%d%*[sp]%d", &disk, &slice);
448 
449 	if (ret == 2 && iodev->is_type == IODEV_PARTITION) {
450 		iodev->is_id.id = slice;
451 		iodev->is_parent_id.id = disk;
452 		(void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN);
453 	} else if (ret == 1) {
454 		if (iodev->is_type == IODEV_DISK) {
455 			iodev->is_id.id = disk;
456 			(void) strlcpy(iodev->is_id.tid, target, KSTAT_STRLEN);
457 			iodev->is_parent_id.id = ctr;
458 		} else if (iodev->is_type == IODEV_IOPATH) {
459 			iodev->is_parent_id.id = disk;
460 			(void) strlcpy(iodev->is_parent_id.tid,
461 				target, KSTAT_STRLEN);
462 		}
463 	}
464 
465 	free(target);
466 }
467 
468 static char *
469 get_slice(int partition, disk_list_t *dl)
470 {
471 	char *tmpbuf;
472 	size_t tmplen;
473 
474 	if (!(dl->flags & SLICES_OK))
475 		return (NULL);
476 	if (partition < 0 || partition >= NDKMAP)
477 		return (NULL);
478 
479 	/* space for 's', and integer < NDKMAP (16) */
480 	tmplen = strlen(dl->dsk) + strlen("sXX") + 1;
481 	tmpbuf = safe_alloc(tmplen);
482 
483 	/*
484 	 * This is a regular slice. Create the name and
485 	 * copy it for use by the calling routine.
486 	 */
487 	(void) snprintf(tmpbuf, tmplen, "%ss%d", dl->dsk, partition);
488 	return (tmpbuf);
489 }
490 
491 static char *
492 get_intel_partition(int partition, disk_list_t *dl)
493 {
494 	char *tmpbuf;
495 	size_t tmplen;
496 
497 	if (partition <= 0 || !(dl->flags & PARTITIONS_OK))
498 		return (NULL);
499 
500 	/*
501 	 * See if it falls in the range of allowable partitions. The
502 	 * fdisk partitions show up after the traditional slices so we
503 	 * determine which partition we're in and return that.
504 	 * The NUMPART + 1 is not a mistake. There are currently
505 	 * FD_NUMPART + 1 partitions that show up in the device directory.
506 	 */
507 	partition -= NDKMAP;
508 	if (partition < 0 || partition >= (FD_NUMPART + 1))
509 		return (NULL);
510 
511 	/* space for 'p', and integer < NDKMAP (16) */
512 	tmplen = strlen(dl->dsk) + strlen("pXX") + 1;
513 	tmpbuf = safe_alloc(tmplen);
514 
515 	(void) snprintf(tmpbuf, tmplen, "%sp%d", dl->dsk, partition);
516 	return (tmpbuf);
517 }
518 
519 static void
520 get_pretty_name(enum snapshot_types types, struct iodev_snapshot *iodev,
521 	kstat_ctl_t *kc)
522 {
523 	disk_list_t *dl;
524 	char *pretty = NULL;
525 	char *tmp;
526 	int partition;
527 
528 	if (iodev->is_type == IODEV_NFS) {
529 		if (!(types & SNAP_IODEV_PRETTY))
530 			return;
531 
532 		iodev->is_pretty = lookup_nfs_name(iodev->is_name, kc);
533 		return;
534 	}
535 
536 	if (iodev->is_type == IODEV_IOPATH) {
537 		char buf[KSTAT_STRLEN];
538 		size_t len;
539 
540 		tmp = iodev->is_name;
541 		while (*tmp && *tmp != '.')
542 			tmp++;
543 		if (!*tmp)
544 			return;
545 		(void) strlcpy(buf, iodev->is_name, 1 + tmp - iodev->is_name);
546 		dl = lookup_ks_name(buf);
547 		if (dl == NULL || dl->dsk == NULL)
548 			return;
549 		len = strlen(dl->dsk) + strlen(tmp) + 1;
550 		pretty = safe_alloc(len);
551 		(void) strlcpy(pretty, dl->dsk, len);
552 		(void) strlcat(pretty, tmp, len);
553 		goto out;
554 	}
555 
556 	dl = lookup_ks_name(iodev->is_name);
557 	if (dl == NULL)
558 		return;
559 
560 	if (dl->dsk)
561 		pretty = safe_strdup(dl->dsk);
562 
563 	if (types & SNAP_IODEV_PRETTY) {
564 		if (dl->dname)
565 			iodev->is_dname = safe_strdup(dl->dname);
566 	}
567 
568 	if (dl->devidstr)
569 		iodev->is_devid = safe_strdup(dl->devidstr);
570 
571 	/* look for a possible partition number */
572 	tmp = iodev->is_name;
573 	while (*tmp && *tmp != ',')
574 		tmp++;
575 	if (*tmp != ',')
576 		goto out;
577 
578 	tmp++;
579 	partition = (int)(*tmp - 'a');
580 
581 	if (iodev->is_type == IODEV_PARTITION) {
582 		char *part;
583 		if ((part = get_slice(partition, dl)) == NULL)
584 			part = get_intel_partition(partition, dl);
585 		if (part != NULL) {
586 			free(pretty);
587 			pretty = part;
588 		}
589 	}
590 
591 out:
592 	get_ids(iodev, pretty);
593 
594 	/* only fill in the pretty name if specifically asked for */
595 	if (types & SNAP_IODEV_PRETTY) {
596 		iodev->is_pretty = pretty;
597 	} else {
598 		free(pretty);
599 	}
600 }
601 
602 static enum iodev_type
603 get_iodev_type(kstat_t *ksp)
604 {
605 	if (strcmp(ksp->ks_class, "disk") == 0)
606 		return (IODEV_DISK);
607 	if (strcmp(ksp->ks_class, "partition") == 0)
608 		return (IODEV_PARTITION);
609 	if (strcmp(ksp->ks_class, "nfs") == 0)
610 		return (IODEV_NFS);
611 	if (strcmp(ksp->ks_class, "iopath") == 0)
612 		return (IODEV_IOPATH);
613 	if (strcmp(ksp->ks_class, "tape") == 0)
614 		return (IODEV_TAPE);
615 	return (IODEV_UNKNOWN);
616 }
617 
618 int
619 iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2)
620 {
621 	/* neutral sort order between disk and part */
622 	if (!disk_or_partition(io1->is_type) ||
623 		!disk_or_partition(io2->is_type)) {
624 		if (io1->is_type < io2->is_type)
625 			return (-1);
626 		if (io1->is_type > io2->is_type)
627 			return (1);
628 	}
629 
630 	/* controller doesn't have ksp */
631 	if (io1->is_ksp && io2->is_ksp) {
632 		if (strcmp(io1->is_module, io2->is_module) != 0)
633 			return (strcmp(io1->is_module, io2->is_module));
634 		if (io1->is_instance < io2->is_instance)
635 			return (-1);
636 		if (io1->is_instance > io2->is_instance)
637 			return (1);
638 	} else {
639 		if (io1->is_id.id < io2->is_id.id)
640 			return (-1);
641 		if (io1->is_id.id > io2->is_id.id)
642 			return (1);
643 	}
644 
645 	return (strcmp(io1->is_name, io2->is_name));
646 }
647 
648 int
649 acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df)
650 {
651 	kstat_t *ksp;
652 	int err = 0;
653 	struct iodev_snapshot *pos;
654 	struct iodev_snapshot *list = NULL;
655 
656 	ss->s_nr_iodevs = 0;
657 
658 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
659 		enum iodev_type type;
660 
661 		if (ksp->ks_type != KSTAT_TYPE_IO)
662 			continue;
663 
664 		/* e.g. "usb_byte_count" is not handled */
665 		if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN)
666 			continue;
667 
668 		if (df && !(type & df->if_allowed_types))
669 			continue;
670 
671 		if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) {
672 			err = errno;
673 			goto out;
674 		}
675 
676 		(void) memset(pos, 0, sizeof (struct iodev_snapshot));
677 
678 		pos->is_type = type;
679 		pos->is_crtime = ksp->ks_crtime;
680 		pos->is_snaptime = ksp->ks_snaptime;
681 		pos->is_id.id = IODEV_NO_ID;
682 		pos->is_parent_id.id = IODEV_NO_ID;
683 		pos->is_ksp = ksp;
684 		pos->is_instance = ksp->ks_instance;
685 
686 		(void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN);
687 		(void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN);
688 		get_pretty_name(ss->s_types, pos, kc);
689 
690 		/*
691 		 * We must insert in sort order so e.g. vmstat -l
692 		 * chooses in order.
693 		 */
694 		insert_into(&list, pos);
695 	}
696 
697 	choose_iodevs(ss, list, df);
698 
699 	/* before acquire_stats for collate_controller()'s benefit */
700 	if (ss->s_types & SNAP_IODEV_ERRORS) {
701 		if ((err = acquire_iodev_errors(ss, kc)) != 0)
702 			goto out;
703 	}
704 
705 	if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0)
706 		goto out;
707 
708 	err = 0;
709 out:
710 	return (err);
711 }
712 
713 void
714 free_iodev(struct iodev_snapshot *iodev)
715 {
716 	while (iodev->is_children) {
717 		struct iodev_snapshot *tmp = iodev->is_children;
718 		iodev->is_children = iodev->is_children->is_next;
719 		free_iodev(tmp);
720 	}
721 
722 	free(iodev->is_errors.ks_data);
723 	free(iodev->is_pretty);
724 	free(iodev->is_dname);
725 	free(iodev->is_devid);
726 	free(iodev);
727 }
728