/*
 * iostat: report CPU and I/O statistics
 * (C) 1998-2008 by Sebastien GODARD (sysstat <at> orange.fr)
 *
 ***************************************************************************
 * This program is free software; you can redistribute it and/or modify it *
 * under the terms of the GNU General Public License as published  by  the *
 * Free Software Foundation; either version 2 of the License, or (at  your *
 * option) any later version.                                              *
 *                                                                         *
 * This program is distributed in the hope that it  will  be  useful,  but *
 * WITHOUT ANY WARRANTY; without the implied warranty  of  MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
 * for more details.                                                       *
 *                                                                         *
 * You should have received a copy of the GNU General Public License along *
 * with this program; if not, write to the Free Software Foundation, Inc., *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA                   *
 ***************************************************************************
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utsname.h>

#include "version.h"
#include "iostat.h"
#include "common.h"
#include "ioconf.h"
#include "rd_stats.h"

#ifdef USE_NLS
#include <locale.h>
#include <libintl.h>
#define _(string) gettext(string)
#else
#define _(string) (string)
#endif

#define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__
char *sccsid(void) { return (SCCSID); }

struct stats_cpu *st_cpu[2];
unsigned long long uptime[2]  = {0, 0};
unsigned long long uptime0[2] = {0, 0};
struct io_stats *st_iodev[2];
struct io_nfs_stats *st_ionfs[2];
struct io_hdr_stats *st_hdr_iodev, *st_hdr_ionfs;
struct io_dlist *st_dev_list;

int iodev_nr = 0;	/* Nb of devices and partitions found */
int ionfs_nr = 0;	/* Nb of NFS mounted directories found */
int cpu_nr = 0;		/* Nb of processors on the machine */
int dlist_idx = 0;	/* Nb of devices entered on the command line */

long interval = 0;
char timestamp[64];


/*
 ***************************************************************************
 * Print usage and exit
 *
 * IN:
 * @progname	Name of sysstat command.
 ***************************************************************************
 */
void usage(char *progname)
{
	fprintf(stderr, _("Usage: %s [ options ] [ <interval> [ <count> ] ]\n"),
		progname);

	fprintf(stderr, _("Options are:\n"
			  "[ -c ] [ -d ] [ -N ] [ -n ] [ -h ] [ -k | -m ] [ -t ] [ -V ] [ -x ]\n"
			  "[ <device> [ ... ] | ALL ] [ -p [ <device> | ALL ] ]\n"));
	exit(1);
}

/*
 ***************************************************************************
 * SIGALRM signal handler
 *
 * IN:
 * @sig	Signal number. Set to 0 for the first time, then to SIGALRM.
 ***************************************************************************
 */
void alarm_handler(int sig)
{
	signal(SIGALRM, alarm_handler);
	alarm(interval);
}

/*
 ***************************************************************************
 * Initialize stats common structures
 ***************************************************************************
 */
void init_stats(void)
{
	int i;
	
	/* Allocate structures for CPUs "all" and 0 */
	for (i = 0; i < 2; i++) {
		if ((st_cpu[i] = (struct stats_cpu *) malloc(STATS_CPU_SIZE * 2)) == NULL) {
			perror("malloc");
			exit(4);
		}
		memset(st_cpu[i], 0, STATS_CPU_SIZE * 2);
	}
}

/*
 ***************************************************************************
 * Set every disk_io or nfs_io entry to inactive state (unregistered).
 *
 * IN:
 * @ioln_nr	Number of devices and partitions or NFS filesystems.
 * @st_hdr_ioln	Pointer on first structure describing a device/partition or
 * 		an NFS filesystem.
 ***************************************************************************
 */
void set_entries_inactive(int ioln_nr, struct io_hdr_stats *st_hdr_ioln)
{
	int i;
	struct io_hdr_stats *shi = st_hdr_ioln;

	for (i = 0; i < ioln_nr; i++, shi++) {
		shi->active = FALSE;
	}
}

/*
 ***************************************************************************
 * Free inactive entries (mark them as unused).
 *
 * IN:
 * @ioln_nr	Number of devices and partitions or NFS filesystems.
 * @st_hdr_ioln	Pointer on first structure describing a device/partition or
 * 		an NFS filesystem.
 ***************************************************************************
 */
void free_inactive_entries(int ioln_nr, struct io_hdr_stats *st_hdr_ioln)
{
	int i;
	struct io_hdr_stats *shi = st_hdr_ioln;

	for (i = 0; i < ioln_nr; i++, shi++) {
		if (!shi->active) {
			shi->used = FALSE;
		}
	}
}

/*
 ***************************************************************************
 * Allocate and init I/O devices structures
 *
 * IN:
 * @iodev_nr	Number of devices and partitions.
 ***************************************************************************
 */
void salloc_device(int iodev_nr)
{
	int i;

	for (i = 0; i < 2; i++) {
		if ((st_iodev[i] =
		     (struct io_stats *) malloc(IO_STATS_SIZE * iodev_nr)) == NULL) {
			perror("malloc");
			exit(4);
		}
		memset(st_iodev[i], 0, IO_STATS_SIZE * iodev_nr);
	}

	if ((st_hdr_iodev =
	     (struct io_hdr_stats *) malloc(IO_HDR_STATS_SIZE * iodev_nr)) == NULL) {
		perror("malloc");
		exit(4);
	}
	memset(st_hdr_iodev, 0, IO_HDR_STATS_SIZE * iodev_nr);
}

/*
 ***************************************************************************
 * Allocate and init I/O NFS directories structures
 *
 * IN:
 * @ionfs_nr	Number of NFS filesystems.
 ***************************************************************************
 */
void salloc_nfs(int ionfs_nr)
{
	int i;

	for (i = 0; i < 2; i++) {
		if ((st_ionfs[i] =
		     (struct io_nfs_stats *) malloc(IO_NFS_STATS_SIZE * ionfs_nr)) == NULL) {
			perror("malloc");
			exit(4);
		}
		memset(st_ionfs[i], 0, IO_NFS_STATS_SIZE * ionfs_nr);
	}

	if ((st_hdr_ionfs =
	     (struct io_hdr_stats *) malloc(IO_HDR_STATS_SIZE * ionfs_nr)) == NULL) {
		perror("malloc");
		exit(4);
	}
	memset(st_hdr_ionfs, 0, IO_HDR_STATS_SIZE * ionfs_nr);
}

/*
 ***************************************************************************
 * Allocate structures for devices entered on the command line
 *
 * IN:
 * @list_len	Number of arguments on the command line.
 ***************************************************************************
 */
void salloc_dev_list(int list_len)
{
	if ((st_dev_list = (struct io_dlist *) malloc(IO_DLIST_SIZE * list_len)) == NULL) {
		perror("malloc");
		exit(4);
	}
	memset(st_dev_list, 0, IO_DLIST_SIZE * list_len);
}

/*
 ***************************************************************************
 * Look for the device in the device list and store it if necessary
 *
 * IN:
 * @dlist_idx	Length of the device list.
 * @device_name	Name of the device.
 *
 * OUT:
 * @dlist_idx	Length of the device list.
 *
 * RETURNS:
 * Position of the device in the list.
 ***************************************************************************
 */
int update_dev_list(int *dlist_idx, char *device_name)
{
	int i;
	struct io_dlist *sdli = st_dev_list;

	for (i = 0; i < *dlist_idx; i++, sdli++) {
		if (!strcmp(sdli->dev_name, device_name))
			break;
	}

	if (i == *dlist_idx) {
		/* Device not found: Store it */
		(*dlist_idx)++;
		strncpy(sdli->dev_name, device_name, MAX_NAME_LEN - 1);
	}

	return i;
}

/*
 ***************************************************************************
 * Allocate and init structures, according to system state
 *
 * IN:
 * @flags	Flags for common options and system state.
 *
 * OUT:
 * @flags	Flags for common options and system state.
 ***************************************************************************
 */
void io_sys_init(int *flags)
{
	int i;

	/* Allocate and init stat common counters */
	init_stats();

	/* How many processors on this machine? */
	cpu_nr = get_cpu_nr(~0);

	/* Get number of block devices and partitions in /proc/diskstats */
	if ((iodev_nr = get_diskstats_dev_nr(CNT_PART, CNT_ALL_DEV)) > 0) {
		*flags |= I_F_HAS_DISKSTATS;
		iodev_nr += NR_DEV_PREALLOC;
	}

	if (!HAS_DISKSTATS(*flags) ||
	    (DISPLAY_PARTITIONS(*flags) && !DISPLAY_PART_ALL(*flags))) {
		/*
		 * If /proc/diskstats exists but we also want stats for the partitions
		 * of a particular device, stats will have to be found in /sys. So we
		 * need to know if /sys is mounted or not, and set *flags accordingly.
		 */

		/* Get number of block devices (and partitions) in sysfs */
		if ((iodev_nr = get_sysfs_dev_nr(DISPLAY_PARTITIONS(*flags))) > 0) {
			*flags |= I_F_HAS_SYSFS;
			iodev_nr += NR_DEV_PREALLOC;
		}
		/*
		 * Get number of block devices and partitions in /proc/partitions,
		 * those with statistics...
		 */
		else if ((iodev_nr = get_ppartitions_dev_nr(CNT_PART)) > 0) {
			*flags |= I_F_HAS_PPARTITIONS;
			iodev_nr += NR_DEV_PREALLOC;
		}
		/* Get number of "disk_io:" entries in /proc/stat */
		else if ((iodev_nr = get_disk_io_nr()) > 0) {
			*flags |= I_F_PLAIN_KERNEL24;
			iodev_nr += NR_DISK_PREALLOC;
		}
		else {
			/* Assume we have an old kernel: stats for 4 disks are in /proc/stat */
			iodev_nr = 4;
			*flags |= I_F_OLD_KERNEL;
		}
	}
	/*
	 * Allocate structures for number of disks found.
	 * iodev_nr must be <> 0.
	 */
	salloc_device(iodev_nr);

	if (HAS_OLD_KERNEL(*flags)) {
		struct io_hdr_stats *shi = st_hdr_iodev;
		/*
		 * If we have an old kernel with the stats for the first four disks
		 * in /proc/stat, then set the devices names to hdisk[0..3].
		 */
		for (i = 0; i < 4; i++, shi++) {
			shi->used = TRUE;
			sprintf(shi->name, "%s%d", K_HDISK, i);
		}
	}

	/* Get number of NFS directories in /proc/self/mountstats */
	if (DISPLAY_NFS(*flags) &&
	    ((ionfs_nr = get_nfs_mount_nr()) > 0)) {
		*flags |= I_F_HAS_NFS;
		ionfs_nr += NR_NFS_PREALLOC;

		/* Allocate structures for number of NFS directories found */
		salloc_nfs(ionfs_nr);
	}
}

/*
 ***************************************************************************
 * Save stats for current device, partition or NFS filesystem
 *
 * IN:
 * @name	Name of the device/partition or NFS filesystem.
 * @curr	Index in array for current sample statistics.
 * @st_io	Structure with device, partition or NFS statistics to save.
 * @ioln_nr	Number of devices and partitions or NFS filesystems.
 * @st_hdr_ioln	Pointer on structures describing a device/partition or an
 *		NFS filesystem.
 *
 * OUT:
 * @st_hdr_ioln	Pointer on structures describing a device/partition or an
 *		NFS filesystem.
 ***************************************************************************
 */
void save_stats(char *name, int curr, void *st_io, int ioln_nr,
		struct io_hdr_stats *st_hdr_ioln)
{
	int i;
	struct io_hdr_stats *st_hdr_ioln_i;
	struct io_stats *st_iodev_i;
	struct io_nfs_stats *st_ionfs_i;

	/* Look for device or NFS directory in data table */
	for (i = 0; i < ioln_nr; i++) {
		st_hdr_ioln_i = st_hdr_ioln + i;
		if (!strcmp(st_hdr_ioln_i->name, name)) {
			break;
		}
	}
	
	if (i == ioln_nr) {
		/*
		 * This is a new device: look for an unused entry to store it.
		 * Thus we are able to handle dynamically registered devices.
		 */
		for (i = 0; i < ioln_nr; i++) {
			st_hdr_ioln_i = st_hdr_ioln + i;
			if (!st_hdr_ioln_i->used) {
				/* Unused entry found... */
				st_hdr_ioln_i->used = TRUE; /* Indicate it is now used */
				strcpy(st_hdr_ioln_i->name, name);
				if (st_hdr_ioln == st_hdr_iodev) {
					st_iodev_i = st_iodev[!curr] + i;
					memset(st_iodev_i, 0, IO_STATS_SIZE);
				}
				else {
					st_ionfs_i = st_ionfs[!curr] + i;
					memset(st_ionfs_i, 0, IO_NFS_STATS_SIZE);
				}
				break;
			}
		}
	}
	if (i < ioln_nr) {
		st_hdr_ioln_i = st_hdr_ioln + i;
		st_hdr_ioln_i->active = TRUE;
		if (st_hdr_ioln == st_hdr_iodev) {
			st_iodev_i = st_iodev[curr] + i;
			*st_iodev_i = *((struct io_stats *) st_io);
		}
		else {
			st_ionfs_i = st_ionfs[curr] + i;
			*st_ionfs_i = *((struct io_nfs_stats *) st_io);
		}
	}
	/*
	 * else it was a new device or NFS directory
	 * but there was no free structure to store it.
	 */
}

/*
 ***************************************************************************
 * Read stats from /proc/stat file...
 * Used to get disk stats if /sys not available.
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 ***************************************************************************
 */
void read_proc_stat(int curr)
{
	FILE *fp;
	char line[8192];
	int pos, i;
	unsigned long v_tmp[4];
	unsigned int v_major, v_index;
	struct io_stats *st_iodev_tmp[4];

	/*
	 * Prepare pointers on the 4 disk structures in case we have a
	 * /proc/stat file with "disk_rblk", etc. entries.
	 */
	for (i = 0; i < 4; i++) {
		st_iodev_tmp[i] = st_iodev[curr] + i;
	}

	if ((fp = fopen(STAT, "r")) == NULL) {
		fprintf(stderr, _("Cannot open %s: %s\n"), STAT, strerror(errno));
		exit(2);
	}

	while (fgets(line, 8192, fp) != NULL) {

		if (!strncmp(line, "disk_rblk ", 10)) {
			/*
			 * Read the number of blocks read from disk.
			 * A block is of indeterminate size.
			 * The size may vary depending on the device type.
			 */
			sscanf(line + 10, "%lu %lu %lu %lu",
			       &v_tmp[0], &v_tmp[1], &v_tmp[2], &v_tmp[3]);

			st_iodev_tmp[0]->dk_drive_rblk = v_tmp[0];
			st_iodev_tmp[1]->dk_drive_rblk = v_tmp[1];
			st_iodev_tmp[2]->dk_drive_rblk = v_tmp[2];
			st_iodev_tmp[3]->dk_drive_rblk = v_tmp[3];
		}

		else if (!strncmp(line, "disk_wblk ", 10)) {
			/* Read the number of blocks written to disk */
			sscanf(line + 10, "%lu %lu %lu %lu",
			       &v_tmp[0], &v_tmp[1], &v_tmp[2], &v_tmp[3]);
	
			st_iodev_tmp[0]->dk_drive_wblk = v_tmp[0];
			st_iodev_tmp[1]->dk_drive_wblk = v_tmp[1];
			st_iodev_tmp[2]->dk_drive_wblk = v_tmp[2];
			st_iodev_tmp[3]->dk_drive_wblk = v_tmp[3];
		}

		else if (!strncmp(line, "disk ", 5)) {
			/* Read the number of I/O done since the last reboot */
			sscanf(line + 5, "%lu %lu %lu %lu",
			       &v_tmp[0], &v_tmp[1], &v_tmp[2], &v_tmp[3]);
			
			st_iodev_tmp[0]->dk_drive = v_tmp[0];
			st_iodev_tmp[1]->dk_drive = v_tmp[1];
			st_iodev_tmp[2]->dk_drive = v_tmp[2];
			st_iodev_tmp[3]->dk_drive = v_tmp[3];
		}

		else if (!strncmp(line, "disk_io: ", 9)) {
			struct io_stats sdev;
			char dev_name[MAX_NAME_LEN];

			pos = 9;

			/* Every disk_io entry is potentially unregistered */
			set_entries_inactive(iodev_nr, st_hdr_iodev);
	
			/* Read disks I/O statistics (for 2.4 kernels) */
			while (pos < strlen(line) - 1) {
				/* Beware: a CR is already included in the line */
				sscanf(line + pos, "(%u,%u):(%lu,%*u,%lu,%*u,%lu) ",
				       &v_major, &v_index, &v_tmp[0], &v_tmp[1], &v_tmp[2]);

				sprintf(dev_name, "dev%d-%d", v_major, v_index);
				sdev.dk_drive      = v_tmp[0];
				sdev.dk_drive_rblk = v_tmp[1];
				sdev.dk_drive_wblk = v_tmp[2];
				save_stats(dev_name, curr, &sdev, iodev_nr, st_hdr_iodev);

				pos += strcspn(line + pos, " ") + 1;
			}

			/* Free structures corresponding to unregistered disks */
			free_inactive_entries(iodev_nr, st_hdr_iodev);
		}
	}

	fclose(fp);
}

/*
 ***************************************************************************
 * Read sysfs stat for current block device or partition
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @filename	File name where stats will be read.
 * @dev_name	Device or partition name.
 * @dev_type	Device type (device or partition).
 *
 * RETURNS:
 * 0 if file couldn't be opened, 1 otherwise.
 ***************************************************************************
 */
int read_sysfs_file_stat(int curr, char *filename, char *dev_name,
			  int dev_type)
{
	FILE *fp;
	struct io_stats sdev;
	int i;

	/* Try to read given stat file */
	if ((fp = fopen(filename, "r")) == NULL)
		return 0;
	
	if (dev_type == DT_DEVICE) {
		i = (fscanf(fp, "%lu %lu %llu %lu %lu %lu %llu %lu %lu %lu %lu",
			    &sdev.rd_ios, &sdev.rd_merges,
			    &sdev.rd_sectors, &sdev.rd_ticks,
			    &sdev.wr_ios, &sdev.wr_merges,
			    &sdev.wr_sectors, &sdev.wr_ticks,
			    &sdev.ios_pgr, &sdev.tot_ticks, &sdev.rq_ticks) == 11);
	}
	else {
		i = (fscanf(fp, "%lu %llu %lu %llu",
			    &sdev.rd_ios, &sdev.rd_sectors,
			    &sdev.wr_ios, &sdev.wr_sectors) == 4);
	}

	if (i) {
		save_stats(dev_name, curr, &sdev, iodev_nr, st_hdr_iodev);
	}

	fclose(fp);

	return 1;
}

/*
 ***************************************************************************
 * Read sysfs stats for all the partitions of a device
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @dev_name	Device name.
 ***************************************************************************
 */
void read_sysfs_dlist_part_stat(int curr, char *dev_name)
{
	DIR *dir;
	struct dirent *drd;
	char dfile[MAX_PF_NAME], filename[MAX_PF_NAME];

	snprintf(dfile, MAX_PF_NAME, "%s/%s", SYSFS_BLOCK, dev_name);
	dfile[MAX_PF_NAME - 1] = '\0';

	/* Open current device directory in /sys/block */
	if ((dir = opendir(dfile)) == NULL)
		return;

	/* Get current entry */
	while ((drd = readdir(dir)) != NULL) {
		if (!strcmp(drd->d_name, ".") || !strcmp(drd->d_name, ".."))
			continue;
		snprintf(filename, MAX_PF_NAME, "%s/%s/%s", dfile, drd->d_name, S_STAT);
		filename[MAX_PF_NAME - 1] = '\0';

		/* Read current partition stats */
		read_sysfs_file_stat(curr, filename, drd->d_name, DT_PARTITION);
	}

	/* Close device directory */
	closedir(dir);
}

/*
 ***************************************************************************
 * Read stats from the sysfs filesystem for the devices entered on the
 * command line
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 ***************************************************************************
 */
void read_sysfs_dlist_stat(int curr)
{
	int dev, ok;
	char filename[MAX_PF_NAME];
	struct io_dlist *st_dev_list_i;

	/* Every I/O device (or partition) is potentially unregistered */
	set_entries_inactive(iodev_nr, st_hdr_iodev);

	for (dev = 0; dev < dlist_idx; dev++) {
		st_dev_list_i = st_dev_list + dev;
		snprintf(filename, MAX_PF_NAME, "%s/%s/%s",
			 SYSFS_BLOCK, st_dev_list_i->dev_name, S_STAT);
		filename[MAX_PF_NAME - 1] = '\0';

		/* Read device stats */
		ok = read_sysfs_file_stat(curr, filename, st_dev_list_i->dev_name, DT_DEVICE);

		if (ok && st_dev_list_i->disp_part) {
			/* Also read stats for its partitions */
			read_sysfs_dlist_part_stat(curr, st_dev_list_i->dev_name);
		}
	}

	/* Free structures corresponding to unregistered devices */
	free_inactive_entries(iodev_nr, st_hdr_iodev);
}

/*
 ***************************************************************************
 * Read stats from the sysfs filesystem for every block devices found
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @flags	Flags for common options and system state.
 ***************************************************************************
 */
void read_sysfs_stat(int curr, int flags)
{
	DIR *dir;
	struct dirent *drd;
	char filename[MAX_PF_NAME];
	int ok;

	/* Every I/O device entry is potentially unregistered */
	set_entries_inactive(iodev_nr, st_hdr_iodev);

	/* Open /sys/block directory */
	if ((dir = opendir(SYSFS_BLOCK)) != NULL) {

		/* Get current entry */
		while ((drd = readdir(dir)) != NULL) {
			if (!strcmp(drd->d_name, ".") || !strcmp(drd->d_name, ".."))
				continue;
			snprintf(filename, MAX_PF_NAME, "%s/%s/%s",
				 SYSFS_BLOCK, drd->d_name, S_STAT);
			filename[MAX_PF_NAME - 1] = '\0';
	
			/* If current entry is a directory, try to read its stat file */
			ok = read_sysfs_file_stat(curr, filename, drd->d_name, DT_DEVICE);
	
			/*
			 * If '-p ALL' was entered on the command line,
			 * also try to read stats for its partitions
			 */
			if (ok && DISPLAY_PART_ALL(flags)) {
				read_sysfs_dlist_part_stat(curr, drd->d_name);
			}
		}

		/* Close /sys/block directory */
		closedir(dir);
	}

	/* Free structures corresponding to unregistered devices */
	free_inactive_entries(iodev_nr, st_hdr_iodev);
}

/*
 ***************************************************************************
 * Read stats from /proc/diskstats
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @flags	Flags for common options and system state.
 ***************************************************************************
 */
void read_diskstats_stat(int curr, int flags)
{
	FILE *fp;
	char line[256], dev_name[MAX_NAME_LEN];
	char *dm_name;
	struct io_stats sdev;
	int i;
	unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec, wr_ios;
	unsigned long ios_pgr, tot_ticks, rq_ticks, wr_merges, wr_ticks;
	unsigned long long rd_sec_or_wr_ios, wr_sec;
	char *ioc_dname;
	unsigned int major, minor;

	/* Every I/O device entry is potentially unregistered */
	set_entries_inactive(iodev_nr, st_hdr_iodev);

	if ((fp = fopen(DISKSTATS, "r")) == NULL)
		return;

	while (fgets(line, 256, fp) != NULL) {

		/* major minor name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq */
		i = sscanf(line, "%u %u %s %lu %lu %llu %lu %lu %lu %llu %lu %lu %lu %lu",
			   &major, &minor, dev_name,
			   &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios, &rd_ticks_or_wr_sec,
			   &wr_ios, &wr_merges, &wr_sec, &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks);

		if (i == 14) {
			/* Device */
			sdev.rd_ios     = rd_ios;
			sdev.rd_merges  = rd_merges_or_rd_sec;
			sdev.rd_sectors = rd_sec_or_wr_ios;
			sdev.rd_ticks   = rd_ticks_or_wr_sec;
			sdev.wr_ios     = wr_ios;
			sdev.wr_merges  = wr_merges;
			sdev.wr_sectors = wr_sec;
			sdev.wr_ticks   = wr_ticks;
			sdev.ios_pgr    = ios_pgr;
			sdev.tot_ticks  = tot_ticks;
			sdev.rq_ticks   = rq_ticks;
		}
		else if (i == 7) {
			/* Partition */
			if (DISPLAY_EXTENDED(flags) ||
			    (!dlist_idx && !DISPLAY_PARTITIONS(flags)))
				continue;

			sdev.rd_ios     = rd_ios;
			sdev.rd_sectors = rd_merges_or_rd_sec;
			sdev.wr_ios     = rd_sec_or_wr_ios;
			sdev.wr_sectors = rd_ticks_or_wr_sec;
		}
		else
			/* Unknown entry: Ignore it */
			continue;

		if ((ioc_dname = ioc_name(major, minor)) != NULL) {
			if (strcmp(dev_name, ioc_dname) && strcmp(ioc_dname, K_NODEV)) {
				/*
				 * No match: Use name generated from sysstat.ioconf data
				 * (if different from "nodev") works around known issues
				 * with EMC PowerPath.
				 */
				strncpy(dev_name, ioc_dname, MAX_NAME_LEN);
			}
		}

		if ((DISPLAY_DEVMAP_NAME(flags)) && (major == DEVMAP_MAJOR)) {
			/*
			 * If the device is a device mapper device, try to get its
			 * assigned name of its logical device.
			 */
			dm_name = transform_devmapname(major, minor);
			if (dm_name) {
				strcpy(dev_name, dm_name);
			}
		}

		save_stats(dev_name, curr, &sdev, iodev_nr, st_hdr_iodev);
	}
	fclose(fp);

	/* Free structures corresponding to unregistered devices */
	free_inactive_entries(iodev_nr, st_hdr_iodev);
}

/*
 ***************************************************************************
 * Read stats from /proc/partitions
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @flags	Flags for common options and system state.
 ***************************************************************************
 */
void read_ppartitions_stat(int curr, int flags)
{
	FILE *fp;
	char line[256], dev_name[MAX_NAME_LEN];
	struct io_stats sdev;
	unsigned long rd_ios, rd_merges, rd_ticks, wr_ios, wr_merges, wr_ticks;
	unsigned long ios_pgr, tot_ticks, rq_ticks;
	unsigned long long rd_sec, wr_sec;
	char *ioc_dname, *dm_name;
	unsigned int major, minor;

	/* Every I/O device entry is potentially unregistered */
	set_entries_inactive(iodev_nr, st_hdr_iodev);

	if ((fp = fopen(PPARTITIONS, "r")) == NULL)
		return;

	while (fgets(line, 256, fp) != NULL) {
		/* major minor #blocks name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq */
		if (sscanf(line, "%u %u %*u %s %lu %lu %llu %lu %lu %lu %llu"
			   " %lu %lu %lu %lu",
			   &major, &minor, dev_name,
			   &rd_ios, &rd_merges, &rd_sec, &rd_ticks, &wr_ios, &wr_merges,
			   &wr_sec, &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks) == 14) {
			/* Device or partition */
			sdev.rd_ios     = rd_ios;  sdev.rd_merges = rd_merges;
			sdev.rd_sectors = rd_sec;  sdev.rd_ticks  = rd_ticks;
			sdev.wr_ios     = wr_ios;  sdev.wr_merges = wr_merges;
			sdev.wr_sectors = wr_sec;  sdev.wr_ticks  = wr_ticks;
			sdev.ios_pgr    = ios_pgr; sdev.tot_ticks = tot_ticks;
			sdev.rq_ticks   = rq_ticks;
		}
		else
			/* Unknown entry: Ignore it */
			continue;

		if ((ioc_dname = ioc_name(major, minor)) != NULL) {
			if (strcmp(dev_name, ioc_dname) && strcmp(ioc_dname, K_NODEV)) {
				/* Compensate for EMC PowerPath driver bug */
				strncpy(dev_name, ioc_dname, MAX_NAME_LEN);
			}
		}

		if ((DISPLAY_DEVMAP_NAME(flags)) && (major == DEVMAP_MAJOR)) {
			/* Get device mapper logical name */
			dm_name = transform_devmapname(major, minor);
			if (dm_name) {
				strcpy(dev_name, dm_name);
			}
		}

		save_stats(dev_name, curr, &sdev, iodev_nr, st_hdr_iodev);
	}
	fclose(fp);

	/* Free structures corresponding to unregistered devices */
	free_inactive_entries(iodev_nr, st_hdr_iodev);
}

/*
 ***************************************************************************
 * Read NFS-mount directories stats from /proc/self/mountstats
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @flags	Flags for common options and system state.
 ***************************************************************************
 */
void read_nfs_stat(int curr, int flags)
{
	FILE *fp;
	int sw = 0;
	char line[8192];
	char *xprt_line;
	char nfs_name[MAX_NAME_LEN];
	char mount[10], on[10], prefix[10], aux[32];
	char operation[16];
	struct io_nfs_stats snfs;
	long int v1;

	/* Every I/O NFS entry is potentially unregistered */
	set_entries_inactive(ionfs_nr, st_hdr_ionfs);

	if ((fp = fopen(NFSMOUNTSTATS, "r")) == NULL)
		return;

	sprintf(aux, "%%%ds %%10s %%10s",
		MAX_NAME_LEN < 200 ? MAX_NAME_LEN : 200);

	while (fgets(line, 256, fp) != NULL) {

		/* read NFS directory name */
		if (!strncmp(line, "device", 6)) {
			sw = 0;
			sscanf(line + 6, aux, nfs_name, mount, on);
			if ((!strncmp(mount, "mounted", 7)) && (!strncmp(on, "on", 2))) {
				sw = 1;
			}
		}

		sscanf(line, "%10s", prefix);
		if (sw && (!strncmp(prefix, "bytes:", 6))) {
			/* Read the stats for the last NFS-mounted directory */
			sscanf(strstr(line, "bytes:") + 6, "%llu %llu %llu %llu %llu %llu",
			       &snfs.rd_normal_bytes, &snfs.wr_normal_bytes,
			       &snfs.rd_direct_bytes, &snfs.wr_direct_bytes,
			       &snfs.rd_server_bytes, &snfs.wr_server_bytes);
			sw = 2;
		}

		if ((sw == 2) && (!strncmp(prefix, "xprt:", 5))) {
			/*
			 * Read extended statistic for the last NFS-mounted directory
			 * - number of sent rpc requests.
			 */
			xprt_line = (strstr(line, "xprt:") + 6);
			/* udp, tcp or rdma data */
			if (!strncmp(xprt_line, "udp", 3)) {
				/* port bind_count sends recvs (bad_xids req_u bklog_u) */
				sscanf(strstr(xprt_line, "udp") + 4, "%*u %*u %lu",
				       &snfs.rpc_sends);
			}
			if (!strncmp(xprt_line, "tcp", 3)) {
				/*
				 * port bind_counter connect_count connect_time idle_time
				 * sends recvs (bad_xids req_u bklog_u)
				 */
				sscanf(strstr(xprt_line, "tcp") + 4,
				       "%*u %*u %*u %*u %*d %lu",
				       &snfs.rpc_sends);
			}
			if (!strncmp(xprt_line,"rdma", 4)) {
				/*
				 * 0(port) bind_count connect_count connect_time idle_time
				 * sends recvs (bad_xids req_u bklog_u...)
				 */
				sscanf(strstr(xprt_line, "rdma") + 5,
				       "%*u %*u %*u %*u %*d %lu",
				       &snfs.rpc_sends);
			}
			sw = 3;
		}

		if ((sw == 3) && (!strncmp(prefix, "per-op", 6))) {
			sw = 4;
			while (sw == 4) {
				fgets(line, 256, fp);
				sscanf(line, "%15s %lu", operation, &v1);
				if (!strncmp(operation, "READ:", 5)) {
					snfs.nfs_rops = v1;
				}
				else if (!strncmp(operation, "WRITE:", 6)) {
					snfs.nfs_wops = v1;
					save_stats(nfs_name, curr, &snfs, ionfs_nr, st_hdr_ionfs);
					sw = 0;
				}
			}
		}
	}

	fclose(fp);

	/* Free structures corresponding to unregistered devices */
	free_inactive_entries(ionfs_nr, st_hdr_ionfs);
}

/*
 ***************************************************************************
 * Display CPU utilization
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @itv		Interval of time.
 ***************************************************************************
 */
void write_cpu_stat(int curr, unsigned long long itv)
{
	printf("avg-cpu:  %%user   %%nice %%system %%iowait  %%steal   %%idle\n");

	printf("         %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f\n\n",
	       ll_sp_value(st_cpu[!curr]->cpu_user,   st_cpu[curr]->cpu_user,   itv),
	       ll_sp_value(st_cpu[!curr]->cpu_nice,   st_cpu[curr]->cpu_nice,   itv),
	       ll_sp_value(st_cpu[!curr]->cpu_sys + st_cpu[!curr]->cpu_softirq +
			   st_cpu[!curr]->cpu_hardirq,
			   st_cpu[curr]->cpu_sys + st_cpu[curr]->cpu_softirq +
			   st_cpu[curr]->cpu_hardirq, itv),
	       ll_sp_value(st_cpu[!curr]->cpu_iowait, st_cpu[curr]->cpu_iowait, itv),
	       ll_sp_value(st_cpu[!curr]->cpu_steal,  st_cpu[curr]->cpu_steal,  itv),
	       (st_cpu[curr]->cpu_idle < st_cpu[!curr]->cpu_idle) ?
	       0.0 :
	       ll_sp_value(st_cpu[!curr]->cpu_idle,   st_cpu[curr]->cpu_idle,   itv));
}

/*
 ***************************************************************************
 * Display disk stats header
 *
 * IN:
 * @flags	Flags for common options and system state.
 *
 * OUT:
 * @fctr	Conversion factor.
 ***************************************************************************
 */
void write_disk_stat_header(int flags, int *fctr)
{
	if (DISPLAY_EXTENDED(flags)) {
		/* Extended stats */
		printf("Device:         rrqm/s   wrqm/s     r/s     w/s");
		if (DISPLAY_MEGABYTES(flags)) {
			printf("    rMB/s    wMB/s");
			*fctr = 2048;
		}
		else if (DISPLAY_KILOBYTES(flags)) {
			printf("    rkB/s    wkB/s");
			*fctr = 2;
		}
		else {
			printf("   rsec/s   wsec/s");
		}
		printf(" avgrq-sz avgqu-sz   await  svctm  %%util\n");
	}
	else {
		/* Basic stats */
		printf("Device:            tps");
		if (DISPLAY_KILOBYTES(flags)) {
			printf("    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn\n");
			*fctr = 2;
		}
		else if (DISPLAY_MEGABYTES(flags)) {
			printf("    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn\n");
			*fctr = 2048;
		}
		else {
			printf("   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn\n");
		}
	}
}

/*
 ***************************************************************************
 * Display NFS stats header
 *
 * IN:
 * @flags	Flags for common options and system state.
 *
 * OUT:
 * @fctr	Conversion factor.
 ***************************************************************************
 */
void write_nfs_stat_header(int flags, int *fctr)
{
	printf("Filesystem:           ");
	if (DISPLAY_KILOBYTES(flags)) {
		printf("    rkB_nor/s    wkB_nor/s    rkB_dir/s    wkB_dir/s"
		       "    rkB_svr/s    wkB_svr/s");
		*fctr = 1024;
	}
	else if (DISPLAY_MEGABYTES(flags)) {
		printf("    rMB_nor/s    wMB_nor/s    rMB_dir/s    wMB_dir/s"
		       "    rMB_svr/s    wMB_svr/s");
		*fctr = 1024 * 1024;
	}
	else {
		printf("   rBlk_nor/s   wBlk_nor/s   rBlk_dir/s   wBlk_dir/s"
		       "   rBlk_svr/s   wBlk_svr/s");
		*fctr = 512;
	}
	printf("     ops/s    rops/s    wops/s\n");
}

/*
 ***************************************************************************
 * Display extended stats, read from /proc/{diskstats,partitions} or /sys
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @itv		Interval of time.
 * @flags	Flags for common options and system state.
 * @fctr	Conversion factor.
 * @shi		Structures describing the devices and partitions.
 * @ioi		Current sample statistics.
 * @ioj		Previous sample statistics.
 ***************************************************************************
 */
void write_ext_stat(int curr, unsigned long long itv, int flags, int fctr,
		    struct io_hdr_stats *shi, struct io_stats *ioi,
		    struct io_stats *ioj)
{
	struct stats_disk sdc, sdp;
	struct ext_disk_stats xds;
	
	/*
	 * Counters overflows are possible, but don't need to be handled in
	 * a special way: the difference is still properly calculated if the
	 * result is of the same type as the two values.
	 * Exception is field rq_ticks which is incremented by the number of
	 * I/O in progress times the number of milliseconds spent doing I/O.
	 * But the number of I/O in progress (field ios_pgr) happens to be
	 * sometimes negative...
	 */
	sdc.nr_ios    = ioi->rd_ios + ioi->wr_ios;
	sdp.nr_ios    = ioj->rd_ios + ioj->wr_ios;

	sdc.tot_ticks = ioi->tot_ticks;
	sdp.tot_ticks = ioj->tot_ticks;

	sdc.rd_ticks  = ioi->rd_ticks;
	sdp.rd_ticks  = ioj->rd_ticks;
	sdc.wr_ticks  = ioi->wr_ticks;
	sdp.wr_ticks  = ioj->wr_ticks;

	sdc.rd_sect   = ioi->rd_sectors;
	sdp.rd_sect   = ioj->rd_sectors;
	sdc.wr_sect   = ioi->wr_sectors;
	sdp.wr_sect   = ioj->wr_sectors;
	
	compute_ext_disk_stats(&sdc, &sdp, itv, &xds);
	
	/*      DEV   rrq/s wrq/s   r/s   w/s  rsec  wsec  rqsz  qusz await svctm %util */
	printf("%-13s %8.2f %8.2f %7.2f %7.2f %8.2f %8.2f %8.2f %8.2f %7.2f %6.2f %6.2f\n",
	       shi->name,
	       S_VALUE(ioj->rd_merges, ioi->rd_merges, itv),
	       S_VALUE(ioj->wr_merges, ioi->wr_merges, itv),
	       S_VALUE(ioj->rd_ios, ioi->rd_ios, itv),
	       S_VALUE(ioj->wr_ios, ioi->wr_ios, itv),
	       ll_s_value(ioj->rd_sectors, ioi->rd_sectors, itv) / fctr,
	       ll_s_value(ioj->wr_sectors, ioi->wr_sectors, itv) / fctr,
	       xds.arqsz,
	       S_VALUE(ioj->rq_ticks, ioi->rq_ticks, itv) / 1000.0,
	       xds.await,
	       /* The ticks output is biased to output 1000 ticks per second */
	       xds.svctm,
	       /* Again: Ticks in milliseconds */
	       xds.util / 10.0);
}

/*
 ***************************************************************************
 * Write basic stats, read from /proc/stat, /proc/{diskstats,partitions}
 * or from sysfs
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @itv		Interval of time.
 * @flags	Flags for common options and system state.
 * @fctr	Conversion factor.
 * @shi		Structures describing the devices and partitions.
 * @ioi		Current sample statistics.
 * @ioj		Previous sample statistics.
 ***************************************************************************
 */
void write_basic_stat(int curr, unsigned long long itv, int flags, int fctr,
		      struct io_hdr_stats *shi, struct io_stats *ioi,
		      struct io_stats *ioj)
{
	unsigned long long rd_sec, wr_sec;

	printf("%-13s", shi->name);

	if (HAS_SYSFS(flags) ||
	    HAS_DISKSTATS(flags) || HAS_PPARTITIONS(flags)) {
		/* Print stats coming from /sys or /proc/{diskstats,partitions} */
		rd_sec = ioi->rd_sectors - ioj->rd_sectors;
		if ((ioi->rd_sectors < ioj->rd_sectors) && (ioj->rd_sectors <= 0xffffffff)) {
			rd_sec &= 0xffffffff;
		}
		wr_sec = ioi->wr_sectors - ioj->wr_sectors;
		if ((ioi->wr_sectors < ioj->wr_sectors) && (ioj->wr_sectors <= 0xffffffff)) {
			wr_sec &= 0xffffffff;
		}

		printf(" %8.2f %12.2f %12.2f %10llu %10llu\n",
		       S_VALUE(ioj->rd_ios + ioj->wr_ios, ioi->rd_ios + ioi->wr_ios, itv),
		       ll_s_value(ioj->rd_sectors, ioi->rd_sectors, itv) / fctr,
		       ll_s_value(ioj->wr_sectors, ioi->wr_sectors, itv) / fctr,
		       (unsigned long long) rd_sec / fctr,
		       (unsigned long long) wr_sec / fctr);
	}
	else {
		/* Print stats coming from /proc/stat */
		printf(" %8.2f %12.2f %12.2f %10lu %10lu\n",
		       S_VALUE(ioj->dk_drive, ioi->dk_drive, itv),
		       S_VALUE(ioj->dk_drive_rblk, ioi->dk_drive_rblk, itv) / fctr,
		       S_VALUE(ioj->dk_drive_wblk, ioi->dk_drive_wblk, itv) / fctr,
		       (ioi->dk_drive_rblk - ioj->dk_drive_rblk) / fctr,
		       (ioi->dk_drive_wblk - ioj->dk_drive_wblk) / fctr);
	}
}


/*
 ***************************************************************************
 * Write NFS stats read from /proc/self/mountstats
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @itv		Interval of time.
 * @flags	Flags for common options and system state.
 * @fctr	Conversion factor.
 * @shi		Structures describing the NFS filesystems.
 * @ioi		Current sample statistics.
 * @ioj		Previous sample statistics.
 ***************************************************************************
 */
void write_nfs_stat(int curr, unsigned long long itv, int flags, int fctr,
		    struct io_hdr_stats *shi, struct io_nfs_stats *ioni,
		    struct io_nfs_stats *ionj)
{
	if (DISPLAY_HUMAN_READ(flags)) {
		printf("%-22s\n%23s", shi-> name, "");
	}
	else {
		printf("%-22s ", shi->name);
	}
	printf("%12.2f %12.2f %12.2f %12.2f %12.2f %12.2f %9.2f %9.2f %9.2f\n",
	       S_VALUE(ionj->rd_normal_bytes, ioni->rd_normal_bytes, itv) / fctr,
	       S_VALUE(ionj->wr_normal_bytes, ioni->wr_normal_bytes, itv) / fctr,
	       S_VALUE(ionj->rd_direct_bytes, ioni->rd_direct_bytes, itv) / fctr,
	       S_VALUE(ionj->wr_direct_bytes, ioni->wr_direct_bytes, itv) / fctr,
	       S_VALUE(ionj->rd_server_bytes, ioni->rd_server_bytes, itv) / fctr,
	       S_VALUE(ionj->wr_server_bytes, ioni->wr_server_bytes, itv) / fctr,
	       S_VALUE(ionj->rpc_sends, ioni->rpc_sends, itv),
	       S_VALUE(ionj->nfs_rops,  ioni->nfs_rops,  itv),
	       S_VALUE(ionj->nfs_wops,  ioni->nfs_wops,  itv));
}

/*
 ***************************************************************************
 * Print everything now (stats and uptime)
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @flags	Flags for common options and system state.
 * @rectime	Current date and time.
 ***************************************************************************
 */
void write_stats(int curr, int flags, struct tm *rectime)
{
	int dev, i, fctr = 1;
	unsigned long long itv;
	struct io_hdr_stats *shi;
	struct io_dlist *st_dev_list_i;

	/* Test stdout */
	TEST_STDOUT(STDOUT_FILENO);

	/* Print time stamp */
	if (DISPLAY_TIMESTAMP(flags)) {
		if (DISPLAY_ISO(flags)) {
			strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime);
		}
		else {
			strftime(timestamp, sizeof(timestamp), "%X", rectime);
		}
		printf(_("Time: %s\n"), timestamp);
	}

	/* Interval is multiplied by the number of processors */
	itv = get_interval(uptime[!curr], uptime[curr]);

	if (DISPLAY_CPU(flags)) {
		/* Display CPU utilization */
		write_cpu_stat(curr, itv);
	}

	if (cpu_nr > 1) {
		/* On SMP machines, reduce itv to one processor (see note above) */
		itv = get_interval(uptime0[!curr], uptime0[curr]);
	}

	if (DISPLAY_DISK(flags)) {
		struct io_stats *ioi, *ioj;

		shi = st_hdr_iodev;

		/* Display disk stats header */
		write_disk_stat_header(flags, &fctr);

		if (DISPLAY_EXTENDED(flags) &&
		    (HAS_OLD_KERNEL(flags) || HAS_PLAIN_KERNEL24(flags))) {
			/* No extended stats with old 2.2-2.4 kernels */
			printf("\n");
			return;
		}

		for (i = 0; i < iodev_nr; i++, shi++) {
			if (shi->used) {
	
				if (dlist_idx && !HAS_SYSFS(flags)) {
					/*
					 * With sysfs, only stats for the requested
					 * devices are read.
					 * With /proc/{diskstats,partitions}, stats for
					 * every device are read. Thus we need to check
					 * if stats for current device are to be displayed.
					 */
					for (dev = 0; dev < dlist_idx; dev++) {
						st_dev_list_i = st_dev_list + dev;
						if (!strcmp(shi->name, st_dev_list_i->dev_name))
							break;
					}
					if (dev == dlist_idx)
						/* Device not found in list: Don't display it */
						continue;
				}
	
				ioi = st_iodev[curr] + i;
				ioj = st_iodev[!curr] + i;

				if (!DISPLAY_UNFILTERED(flags)) {
					if (HAS_OLD_KERNEL(flags) ||
					    HAS_PLAIN_KERNEL24(flags)) {
						if (!ioi->dk_drive)
							continue;
					}
					else {
						if (!ioi->rd_ios && !ioi->wr_ios)
							continue;
					}
				}

				if (DISPLAY_EXTENDED(flags)) {
					write_ext_stat(curr, itv, flags, fctr,
						       shi, ioi, ioj);
				}
				else {
					write_basic_stat(curr, itv, flags, fctr,
							 shi, ioi, ioj);
				}
			}
		}
		printf("\n");
	}

	if (DISPLAY_NFS(flags)) {
		struct io_nfs_stats *ioni, *ionj;

		shi = st_hdr_ionfs;

		/* Display NFS stats header */
		write_nfs_stat_header(flags, &fctr);

		if (!HAS_NFS(flags)) {
			/* No NFS stats */
			printf("\n");
			return;
		}

		for (i = 0; i < ionfs_nr; i++, shi++) {
			if (shi->used) {
	
				ioni = st_ionfs[curr] + i;
				ionj = st_ionfs[!curr] + i;
				write_nfs_stat(curr, itv, flags, fctr, shi, ioni, ionj);
			}
		}
		printf("\n");
	}
}

/*
 ***************************************************************************
 * Main loop: Read I/O stats from the relevant sources and display them
 *
 * IN:
 * @flags	Flags for common options and system state.
 * @count	Number of lines of stats to print.
 * @rectime	Current date and time.
 ***************************************************************************
 */
void rw_io_stat_loop(int flags, long int count, struct tm *rectime)
{
	int curr = 1;

	/* Don't buffer data if redirected to a pipe */
	setbuf(stdout, NULL);
	
	do {
		if (cpu_nr > 1) {
			/*
			 * Read system uptime (only for SMP machines).
			 * Init uptime0. So if /proc/uptime cannot fill it,
			 * this will be done by /proc/stat.
			 */
			uptime0[curr] = 0;
			read_uptime(&(uptime0[curr]));
		}

		/* Read stats for CPU "all" and 0 */
		read_stat_cpu(st_cpu[curr], 2, &(uptime[curr]), &(uptime0[curr]));
		/*
		 * Time spent in system mode also included time spent servicing
		 * hard and soft interrupts.
		 */
		st_cpu[curr]->cpu_sys += st_cpu[curr]->cpu_hardirq + st_cpu[curr]->cpu_softirq;
		
		/*
		 * If we don't want extended statistics, and if /proc/diskstats and
		 * /proc/partitions don't exist, and /sys is not mounted, then
		 * we try to get disks stats from /proc/stat.
		 */
		if (!DISPLAY_EXTENDED(flags) && !HAS_DISKSTATS(flags) &&
		    !HAS_PPARTITIONS(flags) && !HAS_SYSFS(flags)) {
			read_proc_stat(curr);
		}

		if (dlist_idx) {
			/*
			 * A device or partition name was entered on the command line,
			 * with or without -p option (but not -p ALL).
			 */
			if (HAS_DISKSTATS(flags) && !DISPLAY_PARTITIONS(flags)) {
				read_diskstats_stat(curr, flags);
			}
			else if (HAS_SYSFS(flags)) {
				read_sysfs_dlist_stat(curr);
			}
			else if (HAS_PPARTITIONS(flags) && !DISPLAY_PARTITIONS(flags)) {
				read_ppartitions_stat(curr, flags);
			}
		}
		else {
			/*
			 * No devices nor partitions entered on the command line
			 * (for example if -p ALL was used).
			 */
			if (HAS_DISKSTATS(flags)) {
				read_diskstats_stat(curr, flags);
			}
			else if (HAS_SYSFS(flags)) {
				read_sysfs_stat(curr,flags);
			}
			else if (HAS_PPARTITIONS(flags)) {
				read_ppartitions_stat(curr, flags);
			}
		}

		/* Read NFS directories stats */
		if (HAS_NFS(flags)) {
			read_nfs_stat(curr, flags);
		}

		/* Get time */
		get_localtime(rectime);

		/* Print results */
		write_stats(curr, flags, rectime);

		if (count > 0) {
			count--;
		}
		if (count) {
			curr ^= 1;
			pause();
		}
	}
	while (count);
}

/*
 ***************************************************************************
 * Main entry to the iostat program
 ***************************************************************************
 */
int main(int argc, char **argv)
{
	int it = 0, flags = 0;
	int opt = 1;
	int i;
	long count = 1;
	struct utsname header;
	struct io_dlist *st_dev_list_i;
	struct tm rectime;

#ifdef USE_NLS
	/* Init National Language Support */
	init_nls();
#endif

	/* Get HZ */
	get_HZ();

	/* Allocate structures for device list */
	if (argc > 1) {
		salloc_dev_list(argc - 1);
	}

	/* Process args... */
	while (opt < argc) {

		if (!strcmp(argv[opt], "-p")) {
			flags |= I_D_PARTITIONS;
			if (argv[++opt] &&
			    (strspn(argv[opt], DIGITS) != strlen(argv[opt])) &&
			    (strncmp(argv[opt], "-", 1))) {
				flags |= I_D_UNFILTERED;
				if (!strcmp(argv[opt], K_ALL)) {
					flags |= I_D_PART_ALL;
					opt++;
				}
				else {
					/* Store device name */
					i = update_dev_list(&dlist_idx, device_name(argv[opt++]));
					st_dev_list_i = st_dev_list + i;
					st_dev_list_i->disp_part = TRUE;
				}
			}
			else {
				flags |= I_D_PART_ALL;
			}
		}

		else if (!strncmp(argv[opt], "-", 1)) {
			for (i = 1; *(argv[opt] + i); i++) {

				switch (*(argv[opt] + i)) {

				case 'c':
					/* Display cpu usage */
					flags |= I_D_CPU;
					break;

				case 'd':
					/* Display disk utilization */
					flags |= I_D_DISK;
					break;

				case 'h':
					/* Display an easy-to-read NFS report */
					flags |= I_D_HUMAN_READ;
					break;
	
				case 'k':
					if (DISPLAY_MEGABYTES(flags)) {
						usage(argv[0]);
					}
					/* Display stats in kB/s */
					flags |= I_D_KILOBYTES;
					break;

				case 'm':
					if (DISPLAY_KILOBYTES(flags)) {
						usage(argv[0]);
					}
					/* Display stats in MB/s */
					flags |= I_D_MEGABYTES;
					break;
	
				case 'N':
					/* Display device mapper logical name */
					flags |= I_D_DEVMAP_NAME;
					break;
	
				case 'n':
					/* Display NFS stats */
					flags |= I_D_NFS;
					break;

				case 't':
					/* Display timestamp */
					flags |= I_D_TIMESTAMP;
					break;
	
				case 'x':
					/* Display extended stats */
					flags |= I_D_EXTENDED;
					break;

				case 'V':
					/* Print version number and exit */
					print_version();
					break;
	
				default:
					usage(argv[0]);
				}
			}
			opt++;
		}

		else if (!isdigit(argv[opt][0])) {
			flags |= I_D_UNFILTERED;
			if (strcmp(argv[opt], K_ALL)) {
				/* Store device name */
				update_dev_list(&dlist_idx, device_name(argv[opt++]));
			}
			else {
				opt++;
			}
		}

		else if (!it) {
			interval = atol(argv[opt++]);
			if (interval < 0) {
				usage(argv[0]);
			}
			count = -1;
			it = 1;
		}

		else if (it > 0) {
			count = atol(argv[opt++]);
			if ((count < 1) || !interval) {
				usage(argv[0]);
			}
			it = -1;
		}
		else {
			usage(argv[0]);
		}
	}

	if (!interval) {
		count = 1;
	}
	
	/* Default: Display CPU and DISK reports */
	if (!DISPLAY_CPU(flags) && !DISPLAY_DISK(flags) && !DISPLAY_NFS(flags)) {
		flags |= I_D_CPU + I_D_DISK;
	}
	/*
	 * Also display DISK reports if options -p, -x or a device has been entered
	 * on the command line.
	 */
	if (DISPLAY_PARTITIONS(flags) || DISPLAY_EXTENDED(flags) ||
	    DISPLAY_UNFILTERED(flags)) {
		flags |= I_D_DISK;
	}

	/* Linux does not provide extended stats for partitions nor for NFS directories */
	if (DISPLAY_PARTITIONS(flags) && DISPLAY_EXTENDED(flags)) {
		fprintf(stderr, _("-x and -p options are mutually exclusive\n"));
		exit(1);
	}

	/* Ignore device list if '-p ALL' entered on the command line */
	if (DISPLAY_PART_ALL(flags)) {
		dlist_idx = 0;
	}

	/* Init structures according to machine architecture */
	io_sys_init(&flags);

	get_localtime(&rectime);

	/* Get system name, release number and hostname */
	uname(&header);
	if (print_gal_header(&rectime, header.sysname, header.release,
			     header.nodename, header.machine)) {
		flags |= I_D_ISO;
	}
	printf("\n");

	/* Set a handler for SIGALRM */
	alarm_handler(0);

	/* Main loop */
	rw_io_stat_loop(flags, count, &rectime);

	return 0;
}
