root/usr/src/cmd/stat/iostat/iostat.c
/*
 * 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.
 *
 * rewritten from UCB 4.13 83/09/25
 * rewritten from SunOS 4.1 SID 1.18 89/10/06
 */
/*
 * Copyright (c) 2012 by Delphix. All rights reserved.
 * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
 * Copyright 2016 James S. Blachly, MD. All rights reserved.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <unistd.h>
#include <memory.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <time.h>
#include <sys/time.h>
#include <sys/sysinfo.h>
#include <inttypes.h>
#include <strings.h>
#include <sys/systeminfo.h>
#include <kstat.h>
#include <locale.h>

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

#define DISK_OLD                0x0001
#define DISK_NEW                0x0002
#define DISK_EXTENDED           0x0004
#define DISK_ERRORS             0x0008
#define DISK_EXTENDED_ERRORS    0x0010
#define DISK_IOPATH_LI          0x0020  /* LunInitiator */
#define DISK_IOPATH_LTI         0x0040  /* LunTargetInitiator */

#define DISK_NORMAL             (DISK_OLD | DISK_NEW)
#define DISK_IO_MASK            (DISK_OLD | DISK_NEW | DISK_EXTENDED)
#define DISK_ERROR_MASK         (DISK_ERRORS | DISK_EXTENDED_ERRORS)
#define PRINT_VERTICAL          (DISK_ERROR_MASK | DISK_EXTENDED)

#define REPRINT 19

#define NUMBER_OF_ERR_COUNTERS  3

/*
 * It's really a pseudo-gigabyte. We use 1000000000 bytes so that the disk
 * labels don't look bad. 1GB is really 1073741824 bytes.
 */
#define DISK_GIGABYTE   1000000000.0

/*
 * Function desciptor to be called when extended
 * headers are used.
 */
typedef struct formatter {
        void (*nfunc)(void);
        struct formatter *next;
} format_t;

/*
 * Used to get formatting right when printing tty/cpu
 * data to the right of disk data
 */
enum show_disk_mode {
        SHOW_FIRST_ONLY,
        SHOW_SECOND_ONWARDS,
        SHOW_ALL
};

enum show_disk_mode show_disk_mode = SHOW_ALL;

char *cmdname = "iostat";
int caught_cont = 0;

static char one_blank[] = " ";
static char two_blanks[] = "  ";

/*
 * count for number of lines to be emitted before a header is
 * shown again. Only used for the basic format.
 */
static  uint_t  tohdr = 1;

/*
 * If we're in raw format, have we printed a header? We only do it
 * once for raw but we emit it every REPRINT lines in non-raw format.
 * This applies only for the basic header. The extended header is
 * done only once in both formats.
 */
static  uint_t  hdr_out;

/*
 * Flags representing arguments from command line
 */
static  uint_t  do_tty;                 /* show tty info (-t) */
static  uint_t  do_disk;                /* show disk info per selected */
                                        /* format (-d, -D, -e, -E, -x -X -Y) */
static  uint_t  do_cpu;                 /* show cpu info (-c) */
static  uint_t  do_interval;            /* do intervals (-I) */
static  int     do_partitions;          /* per-partition stats (-p) */
static  int     do_partitions_only;     /* per-partition stats only (-P) */
                                        /* no per-device stats for disks */
static  uint_t  do_conversions;         /* display disks as cXtYdZ (-n) */
static  uint_t  do_megabytes;           /* display data in MB/sec (-M) */
static  uint_t  do_controller;          /* display controller info (-C) */
static  uint_t  do_raw;                 /* emit raw format (-r) */
static  uint_t  timestamp_fmt = NODATE; /* timestamp  each display (-T) */
static  uint_t  do_devid;               /* -E should show devid */

/*
 * Default number of disk drives to be displayed in basic format
 */
#define DEFAULT_LIMIT   4

struct iodev_filter df;

static  uint_t  suppress_state;         /* skip state change messages */
static  uint_t  suppress_zero;          /* skip zero valued lines */
static  uint_t  show_mountpts;          /* show mount points */
static  int     interval;               /* interval (seconds) to output */
static  int     iter;                   /* iterations from command line */

#define SMALL_SCRATCH_BUFLEN    MAXNAMELEN

static int      iodevs_nl;              /* name field width */
#define IODEVS_NL_MIN           6       /* not too thin for "device" */
#define IODEVS_NL_MAX           24      /* but keep full width under 80 */

static  char    disk_header[132];
static  uint_t  dh_len;                 /* disk header length for centering */
static  int     lineout;                /* data waiting to be printed? */

static struct snapshot *newss;
static struct snapshot *oldss;
static  double  getime;                 /* elapsed time */
static  double  percent;                /* 100 / etime */

/*
 * List of functions to be called which will construct the desired output
 */
static format_t *formatter_list;
static format_t *formatter_end;

static u_longlong_t     ull_delta(u_longlong_t, u_longlong_t);
static uint_t   u32_delta(uint_t, uint_t);
static void setup(void (*nfunc)(void));
static void print_tty_hdr1(void);
static void print_tty_hdr2(void);
static void print_cpu_hdr1(void);
static void print_cpu_hdr2(void);
static void print_tty_data(void);
static void print_cpu_data(void);
static void print_err_hdr(void);
static void print_disk_header(void);
static void hdrout(void);
static void disk_errors(void);
static void do_newline(void);
static void push_out(const char *, ...);
static void printhdr(int);
static void printxhdr(void);
static void usage(void);
static void do_args(int, char **);
static void do_format(void);
static void show_all_disks(void);
static void show_first_disk(void);
static void show_other_disks(void);
static void show_disk_errors(void *, void *, void *);
static void write_core_header(void);
static int  fzero(double value);
static int  safe_strtoi(char const *val, char *errmsg);

int
main(int argc, char **argv)
{
        enum snapshot_types types = SNAP_SYSTEM;
        kstat_ctl_t *kc;
        long hz;
        int forever;
        hrtime_t start_n;
        hrtime_t period_n = 0;

        (void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)               /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST"          /* Use this only if it weren't */
#endif
        (void) textdomain(TEXT_DOMAIN);

        do_args(argc, argv);

        /*
         * iostat historically showed CPU changes, even though
         * it doesn't provide much useful information
         */
        types |= SNAP_CPUS;

        if (do_disk)
                types |= SNAP_IODEVS;

        if (do_disk && !do_partitions_only)
                df.if_allowed_types |= IODEV_DISK;
        if (do_disk & DISK_IOPATH_LI) {
                df.if_allowed_types |= IODEV_IOPATH_LTI;
                types |= SNAP_IOPATHS_LI;
        }
        if (do_disk & DISK_IOPATH_LTI) {
                df.if_allowed_types |= IODEV_IOPATH_LTI;
                types |= SNAP_IOPATHS_LTI;
        }
        if (do_disk & DISK_ERROR_MASK)
                types |= SNAP_IODEV_ERRORS;
        if (do_partitions || do_partitions_only)
                df.if_allowed_types |= IODEV_PARTITION;
        if (do_conversions)
                types |= SNAP_IODEV_PRETTY;
        if (do_devid)
                types |= SNAP_IODEV_DEVID;
        if (do_controller) {
                if (!(do_disk & PRINT_VERTICAL) ||
                    (do_disk & DISK_EXTENDED_ERRORS))
                        fail(0, "-C can only be used with -e or -x.");
                types |= SNAP_CONTROLLERS;
                df.if_allowed_types |= IODEV_CONTROLLER;
        }

        hz = sysconf(_SC_CLK_TCK);

        /*
         * Undocumented behavior - sending a SIGCONT will result
         * in a new header being emitted. Used only if we're not
         * doing extended headers. This is a historical
         * artifact.
         */
        if (!(do_disk & PRINT_VERTICAL))
                (void) signal(SIGCONT, printhdr);

        if (interval)
                period_n = (hrtime_t)interval * NANOSEC;

        kc = open_kstat();
        if (interval)
                start_n = gethrtime();
        newss = acquire_snapshot(kc, types, &df);

        /* compute width of "device" field */
        iodevs_nl = newss->s_iodevs_is_name_maxlen;
        iodevs_nl = (iodevs_nl < IODEVS_NL_MIN) ?
            IODEVS_NL_MIN : iodevs_nl;
        iodevs_nl = (iodevs_nl > IODEVS_NL_MAX) ?
            IODEVS_NL_MAX : iodevs_nl;

        do_format();

        forever = (iter == 0);
        do {
                if (do_conversions && show_mountpts)
                        do_mnttab();

                if (do_tty || do_cpu) {
                        kstat_t *oldks;
                        oldks = oldss ? &oldss->s_sys.ss_agg_sys : NULL;
                        getime = cpu_ticks_delta(oldks,
                            &newss->s_sys.ss_agg_sys);
                        percent = (getime > 0.0) ? 100.0 / getime : 0.0;
                        getime = (getime / nr_active_cpus(newss)) / hz;
                        if (getime == 0.0)
                                getime = (double)interval;
                        if (getime == 0.0 || do_interval)
                                getime = 1.0;
                }

                if (formatter_list) {
                        format_t *tmp;
                        tmp = formatter_list;

                        if (timestamp_fmt != NODATE)
                                print_timestamp(timestamp_fmt);

                        while (tmp) {
                                (tmp->nfunc)();
                                tmp = tmp->next;
                        }
                        (void) fflush(stdout);
                }

                /* only remaining/doing a single iteration, we are done */
                if (iter == 1)
                        continue;

                if (interval > 0)
                        /* Have a kip */
                        sleep_until(&start_n, period_n, forever, &caught_cont);

                free_snapshot(oldss);
                oldss = newss;
                newss = acquire_snapshot(kc, types, &df);
                iodevs_nl = (newss->s_iodevs_is_name_maxlen > iodevs_nl) ?
                    newss->s_iodevs_is_name_maxlen : iodevs_nl;
                iodevs_nl = (iodevs_nl < IODEVS_NL_MIN) ?
                    IODEVS_NL_MIN : iodevs_nl;
                iodevs_nl = (iodevs_nl > IODEVS_NL_MAX) ?
                    IODEVS_NL_MAX : iodevs_nl;

                if (!suppress_state)
                        snapshot_report_changes(oldss, newss);

                /* if config changed, show stats from boot */
                if (snapshot_has_changed(oldss, newss)) {
                        free_snapshot(oldss);
                        oldss = NULL;
                }

        } while (--iter);

        free_snapshot(oldss);
        free_snapshot(newss);
        (void) kstat_close(kc);
        free(df.if_names);
        return (0);
}

/*
 * Some magic numbers used in header formatting.
 *
 * DISK_LEN = length of either "kps tps serv" or "wps rps util"
 *            using 0 as the first position
 *
 * DISK_ERROR_LEN = length of "s/w h/w trn tot" with one space on
 *              either side. Does not use zero as first pos.
 *
 * DEVICE_LEN = length of "device" + 1 character.
 */

#define DISK_LEN        11
#define DISK_ERROR_LEN  16
#define DEVICE_LEN      7

/*ARGSUSED*/
static void
show_disk_name(void *v1, void *v2, void *data)
{
        struct iodev_snapshot *dev = (struct iodev_snapshot *)v2;
        size_t slen;
        char *name;
        char fbuf[SMALL_SCRATCH_BUFLEN];

        if (dev == NULL)
                return;

        name = do_conversions ? dev->is_pretty : dev->is_name;
        name = name ? name : dev->is_name;

        if (!do_raw) {
                uint_t width;

                slen = strlen(name);
                /*
                 * The length is less
                 * than the section
                 * which will be displayed
                 * on the next line.
                 * Center the entry.
                 */

                width = (DISK_LEN + 1)/2 + (slen / 2);
                (void) snprintf(fbuf, sizeof (fbuf),
                    "%*s", width, name);
                name = fbuf;
                push_out("%-13.13s ", name);
        } else {
                push_out(name);
        }
}

/*ARGSUSED*/
static void
show_disk_header(void *v1, void *v2, void *data)
{
        push_out(disk_header);
}

/*
 * Write out a two line header. What is written out depends on the flags
 * selected but in the worst case consists of a tty header, a disk header
 * providing information for 4 disks and a cpu header.
 *
 * The tty header consists of the word "tty" on the first line above the
 * words "tin tout" on the next line. If present the tty portion consumes
 * the first 10 characters of each line since "tin tout" is surrounded
 * by single spaces.
 *
 * Each of the disk sections is a 14 character "block" in which the name of
 * the disk is centered in the first 12 characters of the first line.
 *
 * The cpu section is an 11 character block with "cpu" centered over the
 * section.
 *
 * The worst case should look as follows:
 *
 * 0---------1--------2---------3---------4---------5---------6---------7-------
 *    tty        sd0           sd1           sd2           sd3           cpu
 *  tin tout kps tps serv  kps tps serv  kps tps serv  kps tps serv  us sy dt id
 *  NNN NNNN NNN NNN NNNN  NNN NNN NNNN  NNN NNN NNNN  NNN NNN NNNN  NN NN NN NN
 *
 * When -D is specified, the disk header looks as follows (worst case):
 *
 * 0---------1--------2---------3---------4---------5---------6---------7-------
 *     tty        sd0           sd1             sd2          sd3          cpu
 *   tin tout rps wps util  rps wps util  rps wps util  rps wps util us sy dt id
 *   NNN NNNN NNN NNN NNNN  NNN NNN NNNN  NNN NNN NNNN  NNN NNN NNNN NN NN NN NN
 */
static void
printhdr(int sig)
{
        /*
         * If we're here because a signal fired, reenable the
         * signal.
         */
        if (sig)
                (void) signal(SIGCONT, printhdr);
        if (sig == SIGCONT)
                caught_cont = 1;
        /*
         * Horizontal mode headers
         *
         * First line
         */
        if (do_tty)
                print_tty_hdr1();

        if (do_disk & DISK_NORMAL) {
                (void) snapshot_walk(SNAP_IODEVS, NULL, newss,
                    show_disk_name, NULL);
        }

        if (do_cpu)
                print_cpu_hdr1();
        do_newline();

        /*
         * Second line
         */
        if (do_tty)
                print_tty_hdr2();

        if (do_disk & DISK_NORMAL) {
                (void) snapshot_walk(SNAP_IODEVS, NULL, newss,
                    show_disk_header, NULL);
        }

        if (do_cpu)
                print_cpu_hdr2();
        do_newline();

        tohdr = REPRINT;
}

/*
 * Write out the extended header centered over the core information.
 */
static void
write_core_header(void)
{
        char *edev = "extended device statistics";
        uint_t lead_space_ct;
        uint_t follow_space_ct;
        size_t edevlen;

        if (do_raw == 0) {
                /*
                 * The things we do to look nice...
                 *
                 * Center the core output header. Make sure we have the
                 * right number of trailing spaces for follow-on headers
                 * (i.e., cpu and/or tty and/or errors).
                 */
                edevlen = strlen(edev);
                lead_space_ct = dh_len - edevlen;
                lead_space_ct /= 2;
                if (lead_space_ct > 0) {
                        follow_space_ct = dh_len - (lead_space_ct + edevlen);
                        if (do_disk & DISK_ERRORS)
                                follow_space_ct -= DISK_ERROR_LEN;
                        if ((do_disk & DISK_EXTENDED) && do_conversions)
                                follow_space_ct -= DEVICE_LEN;

                        push_out("%1$*2$.*2$s%3$s%4$*5$.*5$s", one_blank,
                            lead_space_ct, edev, one_blank, follow_space_ct);
                } else
                        push_out("%56s", edev);
        } else
                push_out(edev);
}

/*
 * In extended mode headers, we don't want to reprint the header on
 * signals as they are printed every time anyways.
 */
static void
printxhdr(void)
{

        /*
         * Vertical mode headers
         */
        if (do_disk & DISK_EXTENDED)
                setup(write_core_header);
        if (do_disk & DISK_ERRORS)
                setup(print_err_hdr);

        if (do_conversions) {
                setup(do_newline);
                if (do_disk & (DISK_EXTENDED | DISK_ERRORS))
                        setup(print_disk_header);
                setup(do_newline);
        } else {
                if (do_tty)
                        setup(print_tty_hdr1);
                if (do_cpu)
                        setup(print_cpu_hdr1);
                setup(do_newline);

                if (do_disk & (DISK_EXTENDED | DISK_ERRORS))
                        setup(print_disk_header);
                if (do_tty)
                        setup(print_tty_hdr2);
                if (do_cpu)
                        setup(print_cpu_hdr2);
                setup(do_newline);
        }
}

/*
 * Write out a line for this disk - note that show_disk writes out
 * full lines or blocks for each selected disk.
 */
static void
show_disk(void *v1, void *v2, void *data)
{
        uint32_t err_counters[NUMBER_OF_ERR_COUNTERS];
        boolean_t display_err_counters = do_disk & DISK_ERRORS;
        struct iodev_snapshot *old = (struct iodev_snapshot *)v1;
        struct iodev_snapshot *new = (struct iodev_snapshot *)v2;
        int *count = (int *)data;
        double rps = 0, wps = 0, tps = 0, mtps, krps = 0, kwps = 0;
        double kps = 0, avw = 0, avr = 0, w_pct = 0, r_pct = 0;
        double wserv = 0, rserv = 0, serv = 0;
        double iosize;  /* kb/sec or MB/sec */
        double etime, hr_etime;
        char *disk_name;
        u_longlong_t ldeltas;
        uint_t udeltas;
        uint64_t t_delta;
        uint64_t w_delta;
        uint64_t r_delta;
        int doit = 1;
        uint_t toterrs;
        char *fstr;

        if (new == NULL)
                return;

        switch (show_disk_mode) {
        case SHOW_FIRST_ONLY:
                if (count != NULL && *count)
                        return;
                break;

        case SHOW_SECOND_ONWARDS:
                if (count != NULL && !*count) {
                        (*count)++;
                        return;
                }
                break;

        default:
                break;
        }

        disk_name = do_conversions ? new->is_pretty : new->is_name;
        disk_name = disk_name ? disk_name : new->is_name;

        /*
         * Only do if we want IO stats - Avoids errors traveling this
         * section if that's all we want to see.
         */
        if (do_disk & DISK_IO_MASK) {
                if (old) {
                        t_delta = hrtime_delta(old->is_snaptime,
                            new->is_snaptime);
                } else {
                        t_delta = hrtime_delta(new->is_crtime,
                            new->is_snaptime);
                }

                if (new->is_nr_children) {
                        if (new->is_type == IODEV_CONTROLLER) {
                                t_delta /= new->is_nr_children;
                        } else if ((new->is_type == IODEV_IOPATH_LT) ||
                            (new->is_type == IODEV_IOPATH_LI)) {
                                /* synthetic path */
                                if (!old) {
                                        t_delta = new->is_crtime;
                                }
                                t_delta /= new->is_nr_children;
                        }
                }

                hr_etime = (double)t_delta;
                if (hr_etime == 0.0)
                        hr_etime = (double)NANOSEC;
                etime = hr_etime / (double)NANOSEC;

                /* reads per second */
                udeltas = u32_delta(old ? old->is_stats.reads : 0,
                    new->is_stats.reads);
                rps = (double)udeltas;
                rps /= etime;

                /* writes per second */
                udeltas = u32_delta(old ? old->is_stats.writes : 0,
                    new->is_stats.writes);
                wps = (double)udeltas;
                wps /= etime;

                tps = rps + wps;
                        /* transactions per second */

                /*
                 * report throughput as either kb/sec or MB/sec
                 */

                if (!do_megabytes)
                        iosize = 1024.0;
                else
                        iosize = 1048576.0;

                ldeltas = ull_delta(old ? old->is_stats.nread : 0,
                    new->is_stats.nread);
                if (ldeltas) {
                        krps = (double)ldeltas;
                        krps /= etime;
                        krps /= iosize;
                } else
                        krps = 0.0;

                ldeltas = ull_delta(old ? old->is_stats.nwritten : 0,
                    new->is_stats.nwritten);
                if (ldeltas) {
                        kwps = (double)ldeltas;
                        kwps /= etime;
                        kwps /= iosize;
                } else
                        kwps = 0.0;

                /*
                 * Blocks transferred per second
                 */
                kps = krps + kwps;

                /*
                 * Average number of wait transactions waiting
                 */
                w_delta = hrtime_delta((u_longlong_t)
                    (old ? old->is_stats.wlentime : 0),
                    new->is_stats.wlentime);
                if (w_delta) {
                        avw = (double)w_delta;
                        avw /= hr_etime;
                } else
                        avw = 0.0;

                /*
                 * Average number of run transactions waiting
                 */
                r_delta = hrtime_delta(old ? old->is_stats.rlentime : 0,
                    new->is_stats.rlentime);
                if (r_delta) {
                        avr = (double)r_delta;
                        avr /= hr_etime;
                } else
                        avr = 0.0;

                /*
                 * Average wait service time in milliseconds
                 */
                if (tps > 0.0 && (avw != 0.0 || avr != 0.0)) {
                        mtps = 1000.0 / tps;
                        if (avw != 0.0)
                                wserv = avw * mtps;
                        else
                                wserv = 0.0;

                        if (avr != 0.0)
                                rserv = avr * mtps;
                        else
                                rserv = 0.0;
                        serv = rserv + wserv;
                } else {
                        rserv = 0.0;
                        wserv = 0.0;
                        serv = 0.0;
                }

                /* % of time there is a transaction waiting for service */
                t_delta = hrtime_delta(old ? old->is_stats.wtime : 0,
                    new->is_stats.wtime);
                if (t_delta) {
                        w_pct = (double)t_delta;
                        w_pct /= hr_etime;
                        w_pct *= 100.0;

                        /*
                         * Average the wait queue utilization over the
                         * the controller's devices, if this is a controller.
                         */
                        if (new->is_type == IODEV_CONTROLLER)
                                w_pct /= new->is_nr_children;
                } else
                        w_pct = 0.0;

                /* % of time there is a transaction running */
                t_delta = hrtime_delta(old ? old->is_stats.rtime : 0,
                    new->is_stats.rtime);
                if (t_delta) {
                        r_pct = (double)t_delta;
                        r_pct /= hr_etime;
                        r_pct *= 100.0;

                        /*
                         * Average the percent busy over the controller's
                         * devices, if this is a controller.
                         */
                        if (new->is_type == IODEV_CONTROLLER)
                                w_pct /= new->is_nr_children;
                } else {
                        r_pct = 0.0;
                }

                /* % of time there is a transaction running */
                if (do_interval) {
                        rps     *= etime;
                        wps     *= etime;
                        tps     *= etime;
                        krps    *= etime;
                        kwps    *= etime;
                        kps     *= etime;
                }
        }

        if (do_disk & (DISK_EXTENDED | DISK_ERRORS)) {
                if ((!do_conversions) && ((suppress_zero == 0) ||
                    ((do_disk & DISK_EXTENDED) == 0))) {
                        if (do_raw == 0) {
                                push_out("%-*.*s",
                                    iodevs_nl, iodevs_nl, disk_name);
                        } else {
                                push_out(disk_name);
                        }
                }
        }

        /*
         * The error counters are read first (if asked for and if they are
         * available).
         */
        bzero(err_counters, sizeof (err_counters));
        toterrs = 0;
        if (display_err_counters && (new->is_errors.ks_data != NULL)) {
                kstat_named_t   *knp;
                int             i;

                knp = KSTAT_NAMED_PTR(&new->is_errors);
                for (i = 0; i < NUMBER_OF_ERR_COUNTERS; i++) {
                        switch (knp[i].data_type) {
                                case KSTAT_DATA_ULONG:
                                case KSTAT_DATA_ULONGLONG:
                                        err_counters[i] = knp[i].value.ui32;
                                        toterrs += knp[i].value.ui32;
                                        break;
                                default:
                                        break;
                        }
                }
        }

        switch (do_disk & DISK_IO_MASK) {
        case DISK_OLD:
                if (do_raw == 0)
                        fstr = "%3.0f %3.0f %4.0f  ";
                else
                        fstr = "%.0f,%.0f,%.0f";
                push_out(fstr, kps, tps, serv);
                break;
        case DISK_NEW:
                if (do_raw == 0)
                        fstr = "%3.0f %3.0f %4.1f  ";
                else
                        fstr = "%.0f,%.0f,%.1f";
                push_out(fstr, rps, wps, r_pct);
                break;
        case DISK_EXTENDED:
                if (suppress_zero) {
                        if (fzero(rps) && fzero(wps) && fzero(krps) &&
                            fzero(kwps) && fzero(avw) && fzero(avr) &&
                            fzero(serv) && fzero(w_pct) && fzero(r_pct) &&
                            (toterrs == 0)) {
                                doit = 0;
                                display_err_counters = B_FALSE;
                        } else if (do_conversions == 0) {
                                if (do_raw == 0) {
                                        push_out("%-*.*s",
                                            iodevs_nl, iodevs_nl, disk_name);
                                } else {
                                        push_out(disk_name);
                                }
                        }
                }
                if (doit) {
                        if (!do_conversions) {
                                if (do_raw == 0) {
                                        fstr = " %6.1f %6.1f %6.1f %6.1f "
                                            "%4.1f %4.1f %6.1f %3.0f "
                                            "%3.0f ";
                                } else {
                                        fstr = "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,"
                                            "%.1f,%.0f,%.0f";
                                }
                                push_out(fstr, rps, wps, krps, kwps, avw, avr,
                                    serv, w_pct, r_pct);
                        } else {
                                if (do_raw == 0) {
                                        fstr = " %6.1f %6.1f %6.1f %6.1f "
                                            "%4.1f %4.1f %6.1f %6.1f "
                                            "%3.0f %3.0f ";
                                } else {
                                        fstr = "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,"
                                            "%.1f,%.1f,%.0f,%.0f";
                                }
                                push_out(fstr, rps, wps, krps, kwps, avw, avr,
                                    wserv, rserv, w_pct, r_pct);
                        }
                }
                break;
        }

        if (display_err_counters) {
                char    *efstr;
                int     i;

                if (do_raw == 0) {
                        if (do_disk == DISK_ERRORS)
                                push_out(two_blanks);
                        efstr = "%3u ";
                } else {
                        efstr = "%u";
                }

                for (i = 0; i < NUMBER_OF_ERR_COUNTERS; i++)
                        push_out(efstr, err_counters[i]);

                push_out(efstr, toterrs);
        }

        if (suppress_zero == 0 || doit == 1) {
                if ((do_disk & (DISK_EXTENDED | DISK_ERRORS)) &&
                    do_conversions) {
                        push_out("%s", disk_name);
                        if (show_mountpts && new->is_dname) {
                                mnt_t *mount_pt;
                                char *lu;
                                char *dnlu;
                                char lub[SMALL_SCRATCH_BUFLEN];

                                lu = strrchr(new->is_dname, '/');
                                if (lu) {
                                        /* only the part after a possible '/' */
                                        dnlu = strrchr(disk_name, '/');
                                        if (dnlu != NULL &&
                                            strcmp(dnlu, lu) == 0)
                                                lu = new->is_dname;
                                        else {
                                                *lu = 0;
                                                (void) strcpy(lub,
                                                    new->is_dname);
                                                *lu = '/';
                                                (void) strcat(lub, "/");
                                                (void) strcat(lub,
                                                    disk_name);
                                                lu = lub;
                                        }
                                } else
                                        lu = disk_name;
                                mount_pt = lookup_mntent_byname(lu);
                                if (mount_pt) {
                                        if (do_raw == 0)
                                                push_out(" (%s)",
                                                    mount_pt->mount_point);
                                        else
                                                push_out("(%s)",
                                                    mount_pt->mount_point);
                                }
                        }
                }
        }

        if ((do_disk & PRINT_VERTICAL) && show_disk_mode != SHOW_FIRST_ONLY)
                do_newline();

        if (count != NULL)
                (*count)++;
}

static void
usage(void)
{
        (void) fprintf(stderr,
            "Usage: iostat [-cCdDeEiImMnpPrstxXYz] "
            " [-l n] [-T d|u] [disk ...] [interval [count]]\n"
            "\t\t-c:    report percentage of time system has spent\n"
            "\t\t\tin user/system/dtrace/idle mode\n"
            "\t\t-C:    report disk statistics by controller\n"
            "\t\t-d:    display disk Kb/sec, transfers/sec, avg. \n"
            "\t\t\tservice time in milliseconds  \n"
            "\t\t-D:    display disk reads/sec, writes/sec, \n"
            "\t\t\tpercentage disk utilization \n"
            "\t\t-e:    report device error summary statistics\n"
            "\t\t-E:    report extended device error statistics\n"
            "\t\t-i:    show device IDs for -E output\n"
            "\t\t-I:    report the counts in each interval,\n"
            "\t\t\tinstead of rates, where applicable\n"
            "\t\t-l n:  Limit the number of disks to n\n"
            "\t\t-m:    Display mount points (most useful with -p)\n"
            "\t\t-M:    Display data throughput in MB/sec "
            "instead of Kb/sec\n"
            "\t\t-n:    convert device names to cXdYtZ format\n"
            "\t\t-p:    report per-partition disk statistics\n"
            "\t\t-P:    report per-partition disk statistics only,\n"
            "\t\t\tno per-device disk statistics\n"
            "\t\t-r:    Display data in comma separated format\n"
            "\t\t-s:    Suppress state change messages\n"
            "\t\t-T d|u Display a timestamp in date (d) or unix "
            "time_t (u)\n"
            "\t\t-t:    display chars read/written to terminals\n"
            "\t\t-x:    display extended disk statistics\n"
            "\t\t-X:    display I/O path statistics\n"
            "\t\t-Y:    display I/O path (I/T/L) statistics\n"
            "\t\t-z:    Suppress entries with all zero values\n");
        exit(1);
}

/*ARGSUSED*/
static void
show_disk_errors(void *v1, void *v2, void *d)
{
        struct iodev_snapshot *disk = (struct iodev_snapshot *)v2;
        kstat_named_t *knp;
        size_t  col;
        int     i, len;
        char    *dev_name;

        if (disk->is_errors.ks_ndata == 0)
                return;
        if (disk->is_type == IODEV_CONTROLLER)
                return;

        dev_name = do_conversions ? disk->is_pretty : disk->is_name;
        dev_name = dev_name ? dev_name : disk->is_name;

        len = strlen(dev_name);
        if (len > 20)
                push_out("%s ", dev_name);
        else if (len > 16)
                push_out("%-20.20s ", dev_name);
        else {
                if (do_conversions)
                        push_out("%-16.16s ", dev_name);
                else
                        push_out("%-9.9s ", dev_name);
        }
        col = 0;

        knp = KSTAT_NAMED_PTR(&disk->is_errors);
        for (i = 0; i < disk->is_errors.ks_ndata; i++) {
                /* skip kstats that the driver did not kstat_named_init */
                if (knp[i].name[0] == 0)
                        continue;

                col += strlen(knp[i].name);

                switch (knp[i].data_type) {
                        case KSTAT_DATA_CHAR:
                        case KSTAT_DATA_STRING:
                                if ((strcmp(knp[i].name, "Serial No") == 0) &&
                                    do_devid) {
                                        if (disk->is_devid) {
                                                push_out("Device Id: %s ",
                                                    disk->is_devid);
                                                col += strlen(disk->is_devid);
                                        } else {
                                                push_out("Device Id: ");
                                        }

                                        break;
                                }
                                if (knp[i].data_type == KSTAT_DATA_CHAR) {
                                        push_out("%s: %-.16s ", knp[i].name,
                                            &knp[i].value.c[0]);
                                        col += strnlen(&knp[i].value.c[0], 16);
                                } else {
                                        push_out("%s: %s ", knp[i].name,
                                            KSTAT_NAMED_STR_PTR(&knp[i]));
                                        col +=
                                            KSTAT_NAMED_STR_BUFLEN(&knp[i]) - 1;
                                }
                                break;
                        case KSTAT_DATA_ULONG:
                                push_out("%s: %u ", knp[i].name,
                                    knp[i].value.ui32);
                                col += 4;
                                break;
                        case KSTAT_DATA_ULONGLONG:
                                if (strcmp(knp[i].name, "Size") == 0) {
                                        do_newline();
                                        push_out("%s: %2.2fGB <%llu bytes>",
                                            knp[i].name,
                                            (float)knp[i].value.ui64 /
                                            DISK_GIGABYTE,
                                            knp[i].value.ui64);
                                        do_newline();
                                        col = 0;
                                        break;
                                }
                                push_out("%s: %u ", knp[i].name,
                                    knp[i].value.ui32);
                                col += 4;
                                break;
                        }
                if ((col >= 62) || (i == 2)) {
                        do_newline();
                        col = 0;
                }
        }
        if (col > 0) {
                do_newline();
        }
        do_newline();
}

void
do_args(int argc, char **argv)
{
        int             c;
        int             errflg = 0;
        extern char     *optarg;
        extern int      optind;

        while ((c = getopt(argc, argv, "tdDxXYCciIpPnmMeEszrT:l:")) != EOF)
                switch (c) {
                case 't':
                        do_tty++;
                        break;
                case 'd':
                        do_disk |= DISK_OLD;
                        break;
                case 'D':
                        do_disk |= DISK_NEW;
                        break;
                case 'x':
                        do_disk |= DISK_EXTENDED;
                        break;
                case 'X':
                        if (do_disk & DISK_IOPATH_LTI)
                                errflg++;       /* -Y already used */
                        else
                                do_disk |= DISK_IOPATH_LI;
                        break;
                case 'Y':
                        if (do_disk & DISK_IOPATH_LI)
                                errflg++;       /* -X already used */
                        else
                                do_disk |= DISK_IOPATH_LTI;
                        break;
                case 'C':
                        do_controller++;
                        break;
                case 'c':
                        do_cpu++;
                        break;
                case 'I':
                        do_interval++;
                        break;
                case 'p':
                        do_partitions++;
                        break;
                case 'P':
                        do_partitions_only++;
                        break;
                case 'n':
                        do_conversions++;
                        break;
                case 'M':
                        do_megabytes++;
                        break;
                case 'e':
                        do_disk |= DISK_ERRORS;
                        break;
                case 'E':
                        do_disk |= DISK_EXTENDED_ERRORS;
                        break;
                case 'i':
                        do_devid = 1;
                        break;
                case 's':
                        suppress_state = 1;
                        break;
                case 'z':
                        suppress_zero = 1;
                        break;
                case 'm':
                        show_mountpts = 1;
                        break;
                case 'T':
                        if (optarg) {
                                if (*optarg == 'u')
                                        timestamp_fmt = UDATE;
                                else if (*optarg == 'd')
                                        timestamp_fmt = DDATE;
                                else
                                        errflg++;
                        } else {
                                errflg++;
                        }
                        break;
                case 'r':
                        do_raw = 1;
                        break;
                case 'l':
                        df.if_max_iodevs = safe_strtoi(optarg, "invalid limit");
                        if (df.if_max_iodevs < 1)
                                usage();
                        break;
                case '?':
                        errflg++;
        }

        if ((do_disk & DISK_OLD) && (do_disk & DISK_NEW)) {
                (void) fprintf(stderr, "-d and -D are incompatible.\n");
                usage();
        }

        if (errflg) {
                usage();
        }

        /* if no output classes explicity specified, use defaults */
        if (do_tty == 0 && do_disk == 0 && do_cpu == 0)
                do_tty = do_cpu = 1, do_disk = DISK_OLD;

        /*
         * multi-path options (-X, -Y) without a specific vertical
         * output format (-x, -e, -E) imply extended -x format
         */
        if ((do_disk & (DISK_IOPATH_LI | DISK_IOPATH_LTI)) &&
            !(do_disk & PRINT_VERTICAL))
                do_disk |= DISK_EXTENDED;

        /*
         * If conflicting options take the preferred
         * -D and -x result in -x
         * -d or -D and -e or -E gives only whatever -d or -D was specified
         */
        if ((do_disk & DISK_EXTENDED) && (do_disk & DISK_NORMAL))
                do_disk &= ~DISK_NORMAL;
        if ((do_disk & DISK_NORMAL) && (do_disk & DISK_ERROR_MASK))
                do_disk &= ~DISK_ERROR_MASK;

        /* nfs, tape, always shown */
        df.if_allowed_types = IODEV_NFS | IODEV_TAPE;

        /*
         * If limit == 0 then no command line limit was set, else if any of
         * the flags that cause unlimited disks were not set,
         * use the default of 4
         */
        if (df.if_max_iodevs == 0) {
                df.if_max_iodevs = DEFAULT_LIMIT;
                df.if_skip_floppy = 1;
                if (do_disk & (DISK_EXTENDED | DISK_ERRORS |
                    DISK_EXTENDED_ERRORS)) {
                        df.if_max_iodevs = UNLIMITED_IODEVS;
                        df.if_skip_floppy = 0;
                }
        }
        if (do_disk) {
                size_t count = 0;
                size_t i = optind;

                while (i < argc && !isdigit(argv[i][0])) {
                        count++;
                        i++;
                }

                /*
                 * "Note:  disks  explicitly  requested
                 * are not subject to this disk limit"
                 */
                if ((count > df.if_max_iodevs) ||
                    (count && (df.if_max_iodevs == UNLIMITED_IODEVS)))
                        df.if_max_iodevs = count;

                df.if_names = safe_alloc(count * sizeof (char *));
                (void) memset(df.if_names, 0, count * sizeof (char *));

                df.if_nr_names = 0;
                while (optind < argc && !isdigit(argv[optind][0]))
                        df.if_names[df.if_nr_names++] = argv[optind++];
        }
        if (optind < argc) {
                interval = safe_strtoi(argv[optind], "invalid interval");
                if (interval < 1)
                        fail(0, "invalid interval");
                optind++;

                if (optind < argc) {
                        iter = safe_strtoi(argv[optind], "invalid count");
                        if (iter < 1)
                                fail(0, "invalid count");
                        optind++;
                }
        }
        if (interval == 0)
                iter = 1;
        if (optind < argc)
                usage();
}

/*
 * Driver for doing the extended header formatting. Will produce
 * the function stack needed to output an extended header based
 * on the options selected.
 */

void
do_format(void)
{
        char    header[SMALL_SCRATCH_BUFLEN] = {0};
        char    ch;
        char    iosz;
        const char    *fstr;

        disk_header[0] = 0;
        ch = (do_interval ? 'i' : 's');
        iosz = (do_megabytes ? 'M' : 'k');
        if (do_disk & DISK_ERRORS) {
                if (do_raw == 0) {
                        (void) sprintf(header, "s/w h/w trn tot ");
                } else
                        (void) sprintf(header, "s/w,h/w,trn,tot");
        }
        switch (do_disk & DISK_IO_MASK) {
                case DISK_OLD:
                        if (do_raw == 0)
                                fstr = "%cp%c tp%c serv  ";
                        else
                                fstr = "%cp%c,tp%c,serv";
                        (void) snprintf(disk_header, sizeof (disk_header),
                            fstr, iosz, ch, ch);
                        break;
                case DISK_NEW:
                        if (do_raw == 0)
                                fstr = "rp%c wp%c util  ";
                        else
                                fstr = "%rp%c,wp%c,util";
                        (void) snprintf(disk_header, sizeof (disk_header),
                            fstr, ch, ch);
                        break;
                case DISK_EXTENDED:
                        /* This is -x option */
                        if (!do_conversions) {
                                /* without -n option */
                                if (do_raw == 0) {
                                        /* without -r option */
                                        (void) snprintf(disk_header,
                                            sizeof (disk_header),
                                            "%-*.*s    r/%c    w/%c   "
                                            "%cr/%c   %cw/%c wait actv  "
                                            "svc_t  %%%%w  %%%%b %s",
                                            iodevs_nl, iodevs_nl, "device",
                                            ch, ch, iosz, ch, iosz, ch, header);
                                } else {
                                        /* with -r option */
                                        (void) snprintf(disk_header,
                                            sizeof (disk_header),
                                            "device,r/%c,w/%c,%cr/%c,%cw/%c,"
                                            "wait,actv,svc_t,%%%%w,"
                                            "%%%%b%s%s",
                                            ch, ch, iosz, ch, iosz, ch,
                                            *header == '\0' ? "" : ",",
                                            header);
                                        /*
                                         * if no -e flag, header == '\0...'
                                         * Ternary operator above is to prevent
                                         * trailing comma in full disk_header
                                         */
                                }
                        } else {
                                /* with -n option */
                                if (do_raw == 0) {
                                        fstr = "    r/%c    w/%c   %cr/%c   "
                                            "%cw/%c wait actv wsvc_t asvc_t  "
                                            "%%%%w  %%%%b %sdevice";
                                } else {
                                        fstr = "r/%c,w/%c,%cr/%c,%cw/%c,"
                                            "wait,actv,wsvc_t,asvc_t,"
                                            "%%%%w,%%%%b,%sdevice";
                                        /*
                                         * if -rnxe, "tot" (from -e) and
                                         * "device" are run together
                                         * due to lack of trailing comma
                                         * in 'header'. However, adding
                                         * trailing comma to header at
                                         * its definition leads to prob-
                                         * lems elsewhere so it's added
                                         * here in this edge case -rnxe
                                         */
                                        if (*header != '\0')
                                                (void) strcat(header, ",");
                                }
                                (void) snprintf(disk_header,
                                    sizeof (disk_header),
                                    fstr, ch, ch, iosz, ch, iosz,
                                    ch, header);
                        }
                        break;
                default:
                        break;
        }

        /* do DISK_ERRORS header (already added above for DISK_EXTENDED) */
        if ((do_disk & DISK_ERRORS) &&
            ((do_disk & DISK_IO_MASK) != DISK_EXTENDED)) {
                if (!do_conversions) {
                        if (do_raw == 0)
                                (void) snprintf(disk_header,
                                    sizeof (disk_header), "%-*.*s  %s",
                                    iodevs_nl, iodevs_nl, "device", header);
                        else
                                (void) snprintf(disk_header,
                                    sizeof (disk_header), "device,%s", header);
                } else {
                        if (do_raw == 0) {
                                (void) snprintf(disk_header,
                                    sizeof (disk_header),
                                    "  %sdevice", header);
                        } else {
                                (void) snprintf(disk_header,
                                    sizeof (disk_header),
                                    "%s,device", header);
                        }
                }
        } else {
                /*
                 * Need to subtract two characters for the % escape in
                 * the string.
                 */
                dh_len = strlen(disk_header) - 2;
        }

        /*
         * -n *and* (-E *or* -e *or* -x)
         */
        if (do_conversions && (do_disk & PRINT_VERTICAL)) {
                if (do_tty)
                        setup(print_tty_hdr1);
                if (do_cpu)
                        setup(print_cpu_hdr1);
                if (do_tty || do_cpu)
                        setup(do_newline);
                if (do_tty)
                        setup(print_tty_hdr2);
                if (do_cpu)
                        setup(print_cpu_hdr2);
                if (do_tty || do_cpu)
                        setup(do_newline);
                if (do_tty)
                        setup(print_tty_data);
                if (do_cpu)
                        setup(print_cpu_data);
                if (do_tty || do_cpu)
                        setup(do_newline);
                printxhdr();

                setup(show_all_disks);
        } else {
                /*
                 * These unholy gymnastics are necessary to place CPU/tty
                 * data to the right of the disks/errors for the first
                 * line in vertical mode.
                 */
                if (do_disk & PRINT_VERTICAL) {
                        printxhdr();

                        setup(show_first_disk);
                        if (do_tty)
                                setup(print_tty_data);
                        if (do_cpu)
                                setup(print_cpu_data);
                        setup(do_newline);

                        setup(show_other_disks);
                } else {
                        setup(hdrout);
                        if (do_tty)
                                setup(print_tty_data);
                        setup(show_all_disks);
                        if (do_cpu)
                                setup(print_cpu_data);
                }

                setup(do_newline);
        }
        if (do_disk & DISK_EXTENDED_ERRORS)
                setup(disk_errors);
}

/*
 * Add a new function to the list of functions
 * for this invocation. Once on the stack the
 * function is never removed nor does its place
 * change.
 */
void
setup(void (*nfunc)(void))
{
        format_t *tmp;

        tmp = safe_alloc(sizeof (format_t));
        tmp->nfunc = nfunc;
        tmp->next = 0;
        if (formatter_end)
                formatter_end->next = tmp;
        else
                formatter_list = tmp;
        formatter_end = tmp;

}

/*
 * The functions after this comment are devoted to printing
 * various parts of the header. They are selected based on the
 * options provided when the program was invoked. The functions
 * are either directly invoked in printhdr() or are indirectly
 * invoked by being placed on the list of functions used when
 * extended headers are used.
 */
void
print_tty_hdr1(void)
{
        char *fstr;
        char *dstr;

        if (do_raw == 0) {
                fstr = "%10.10s";
                dstr = "tty    ";
        } else {
                fstr = "%s";
                dstr = "tty";
        }
        push_out(fstr, dstr);
}

void
print_tty_hdr2(void)
{
        if (do_raw == 0)
                push_out("%-10.10s", " tin tout");
        else
                push_out("tin,tout");
}

void
print_cpu_hdr1(void)
{
        char *dstr;

        if (do_raw == 0)
                dstr = "     cpu";
        else
                dstr = "cpu";
        push_out(dstr);
}

void
print_cpu_hdr2(void)
{
        char *dstr;

        if (do_raw == 0)
                dstr = " us sy dt id";
        else
                dstr = "us,sy,dt,id";
        push_out(dstr);
}

/*
 * Assumption is that tty data is always first - no need for raw mode leading
 * comma.
 */
void
print_tty_data(void)
{
        char *fstr;
        uint64_t deltas;
        double raw;
        double outch;
        kstat_t *oldks = NULL;

        if (oldss)
                oldks = &oldss->s_sys.ss_agg_sys;

        if (do_raw == 0)
                fstr = " %3.0f %4.0f ";
        else
                fstr = "%.0f,%.0f";
        deltas = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "rawch");
        raw = deltas;
        raw /= getime;
        deltas = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "outch");
        outch = deltas;
        outch /= getime;
        push_out(fstr, raw, outch);
}

/*
 * Write out CPU data
 */
void
print_cpu_data(void)
{
        char *fstr;
        uint64_t idle;
        uint64_t user;
        uint64_t kern;
        uint64_t dtrace;
        uint64_t nsec_elapsed;
        kstat_t *oldks = NULL;

        if (oldss)
                oldks = &oldss->s_sys.ss_agg_sys;

        if (do_raw == 0)
                fstr = " %2.0f %2.0f %2.0f %2.0f";
        else
                fstr = "%.0f,%.0f,%.0f,%.0f";

        idle = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_idle");
        user = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_user");
        kern = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_kernel");
        dtrace = kstat_delta(oldks, &newss->s_sys.ss_agg_sys,
            "cpu_nsec_dtrace");
        nsec_elapsed = newss->s_sys.ss_agg_sys.ks_snaptime -
            (oldks == NULL ? 0 : oldks->ks_snaptime);
        push_out(fstr, user * percent, kern * percent,
            dtrace * 100.0 / nsec_elapsed / newss->s_nr_active_cpus,
            idle * percent);
}

/*
 * Emit the appropriate header.
 */
void
hdrout(void)
{
        if (do_raw == 0) {
                if (--tohdr == 0)
                        printhdr(0);
        } else if (hdr_out == 0) {
                printhdr(0);
                hdr_out = 1;
        }
}

/*
 * Write out disk errors when -E is specified.
 */
void
disk_errors(void)
{
        (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk_errors, NULL);
}

void
show_first_disk(void)
{
        int count = 0;

        show_disk_mode = SHOW_FIRST_ONLY;

        (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk, &count);
}

void
show_other_disks(void)
{
        int count = 0;

        show_disk_mode = SHOW_SECOND_ONWARDS;

        (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk, &count);
}

void
show_all_disks(void)
{
        int count = 0;

        show_disk_mode = SHOW_ALL;

        (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk, &count);
}

/*
 * Write a newline out and clear the lineout flag.
 */
static void
do_newline(void)
{
        if (lineout) {
                (void) putchar('\n');
                lineout = 0;
        }
}

/*
 * Generalized printf function that determines what extra
 * to print out if we're in raw mode. At this time we
 * don't care about errors.
 */
static void
push_out(const char *message, ...)
{
        va_list args;

        va_start(args, message);
        if (do_raw && lineout == 1)
                (void) putchar(',');
        (void) vprintf(message, args);
        va_end(args);
        lineout = 1;
}

/*
 * Emit the header string when -e is specified.
 */
static void
print_err_hdr(void)
{
        char obuf[SMALL_SCRATCH_BUFLEN];

        if (do_raw) {
                push_out("errors");
                return;
        }

        if (do_conversions == 0) {
                if (!(do_disk & DISK_EXTENDED)) {
                        (void) snprintf(obuf, sizeof (obuf),
                            "%11s", one_blank);
                        push_out(obuf);
                }
        } else if (do_disk == DISK_ERRORS)
                push_out(two_blanks);
        else
                push_out(one_blank);
        push_out("---- errors --- ");
}

/*
 * Emit the header string when -e is specified.
 */
static void
print_disk_header(void)
{
        push_out(disk_header);
}

/*
 * No, UINTMAX_MAX isn't the right thing here since
 * it is #defined to be either INT32_MAX or INT64_MAX
 * depending on the whether _LP64 is defined.
 *
 * We want to handle the odd future case of having
 * ulonglong_t be more than 64 bits but we have
 * no nice #define MAX value we can drop in place
 * without having to change this code in the future.
 */

u_longlong_t
ull_delta(u_longlong_t old, u_longlong_t new)
{
        if (new >= old)
                return (new - old);
        else
                return ((UINT64_MAX - old) + new + 1);
}

/*
 * Take the difference of an unsigned 32
 * bit int attempting to cater for
 * overflow.
 */
uint_t
u32_delta(uint_t old, uint_t new)
{
        if (new >= old)
                return (new - old);
        else
                return ((UINT32_MAX - old) + new + 1);
}

/*
 * This is exactly what is needed for standard iostat output,
 * but make sure to use it only for that
 */
#define EPSILON (0.1)
static int
fzero(double value)
{
        return (value >= 0.0 && value < EPSILON);
}

static int
safe_strtoi(char const *val, char *errmsg)
{
        char *end;
        long tmp;

        errno = 0;
        tmp = strtol(val, &end, 10);
        if (*end != '\0' || errno)
                fail(0, "%s %s", errmsg, val);
        return ((int)tmp);
}