OpenSolaris_b135/cmd/fs.d/nfs/nfsstat/nfsstat.c

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

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

/* LINTLIBRARY */
/* PROTOLIB1 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * nfsstat: Network File System statistics
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <kvm.h>
#include <kstat.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/t_lock.h>
#include <sys/tiuser.h>
#include <sys/statvfs.h>
#include <sys/mntent.h>
#include <sys/mnttab.h>
#include <sys/sysmacros.h>
#include <sys/mkdev.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <rpc/auth.h>
#include <rpc/clnt.h>
#include <nfs/nfs.h>
#include <nfs/nfs_clnt.h>
#include <nfs/nfs_sec.h>
#include <inttypes.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <strings.h>
#include <ctype.h>
#include <locale.h>

#include "statcommon.h"

static kstat_ctl_t *kc = NULL;		/* libkstat cookie */
static kstat_t *rpc_clts_client_kstat, *rpc_clts_server_kstat;
static kstat_t *rpc_cots_client_kstat, *rpc_cots_server_kstat;
static kstat_t *rpc_rdma_client_kstat, *rpc_rdma_server_kstat;
static kstat_t *nfs_client_kstat, *nfs_server_v2_kstat, *nfs_server_v3_kstat;
static kstat_t *nfs4_client_kstat, *nfs_server_v4_kstat;
static kstat_t *rfsproccnt_v2_kstat, *rfsproccnt_v3_kstat, *rfsproccnt_v4_kstat;
static kstat_t *rfsreqcnt_v2_kstat, *rfsreqcnt_v3_kstat, *rfsreqcnt_v4_kstat;
static kstat_t *aclproccnt_v2_kstat, *aclproccnt_v3_kstat;
static kstat_t *aclreqcnt_v2_kstat, *aclreqcnt_v3_kstat;
static kstat_t *ksum_kstat;

static void handle_sig(int);
static int getstats_rpc(void);
static int getstats_nfs(void);
static int getstats_rfsproc(int);
static int getstats_rfsreq(int);
static int getstats_aclproc(void);
static int getstats_aclreq(void);
static void putstats(void);
static void setup(void);
static void cr_print(int);
static void sr_print(int);
static void cn_print(int, int);
static void sn_print(int, int);
static void ca_print(int, int);
static void sa_print(int, int);
static void req_print(kstat_t *, kstat_t *, int, int, int);
static void req_print_v4(kstat_t *, kstat_t *, int, int);
static void stat_print(const char *, kstat_t *, kstat_t *, int, int);
static void nfsstat_kstat_sum(kstat_t *, kstat_t *, kstat_t *);
static void stats_timer(int);
static void safe_zalloc(void **, uint_t, int);
static int safe_strtoi(char const *, char *);


static void nfsstat_kstat_copy(kstat_t *, kstat_t *, int);
static kid_t safe_kstat_read(kstat_ctl_t *, kstat_t *, void *);
static kid_t safe_kstat_write(kstat_ctl_t *, kstat_t *, void *);

static void usage(void);
static void mi_print(void);
static int ignore(char *);
static int interval;		/* interval between stats */
static int count;		/* number of iterations the stat is printed */
#define	MAX_COLUMNS	80
#define	MAX_PATHS	50	/* max paths that can be taken by -m */

/*
 * MI4_MIRRORMOUNT is canonically defined in nfs4_clnt.h, but we cannot
 * include that file here.  Same with MI4_REFERRAL.
 */
#define	MI4_MIRRORMOUNT 0x4000
#define	MI4_REFERRAL	0x8000
#define	NFS_V4		4

static int req_width(kstat_t *, int);
static int stat_width(kstat_t *, int);
static char *path [MAX_PATHS] = {NULL};  /* array to store the multiple paths */

/*
 * Struct holds the previous kstat values so
 * we can compute deltas when using the -i flag
 */
typedef struct old_kstat
{
	kstat_t kst;
	int tot;
} old_kstat_t;

static old_kstat_t old_rpc_clts_client_kstat, old_rpc_clts_server_kstat;
static old_kstat_t old_rpc_cots_client_kstat, old_rpc_cots_server_kstat;
static old_kstat_t old_rpc_rdma_client_kstat, old_rpc_rdma_server_kstat;
static old_kstat_t old_nfs_client_kstat, old_nfs_server_v2_kstat;
static old_kstat_t old_nfs_server_v3_kstat, old_ksum_kstat;
static old_kstat_t old_nfs4_client_kstat, old_nfs_server_v4_kstat;
static old_kstat_t old_rfsproccnt_v2_kstat, old_rfsproccnt_v3_kstat;
static old_kstat_t old_rfsproccnt_v4_kstat, old_rfsreqcnt_v2_kstat;
static old_kstat_t old_rfsreqcnt_v3_kstat, old_rfsreqcnt_v4_kstat;
static old_kstat_t old_aclproccnt_v2_kstat, old_aclproccnt_v3_kstat;
static old_kstat_t old_aclreqcnt_v2_kstat, old_aclreqcnt_v3_kstat;

static uint_t timestamp_fmt = NODATE;

#if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"		/* Use this only if it isn't */
#endif

int
main(int argc, char *argv[])
{
	int c, go_forever, j;
	int cflag = 0;		/* client stats */
	int sflag = 0;		/* server stats */
	int nflag = 0;		/* nfs stats */
	int rflag = 0;		/* rpc stats */
	int mflag = 0;		/* mount table stats */
	int aflag = 0;		/* print acl statistics */
	int vflag = 0;		/* version specified, 0 specifies all */
	int zflag = 0;		/* zero stats after printing */
	char *split_line = "*******************************************"
	    "*************************************";

	interval = 0;
	count = 0;
	go_forever = 0;

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	while ((c = getopt(argc, argv, "cnrsmzav:T:")) != EOF) {
		switch (c) {
		case 'c':
			cflag++;
			break;
		case 'n':
			nflag++;
			break;
		case 'r':
			rflag++;
			break;
		case 's':
			sflag++;
			break;
		case 'm':
			mflag++;
			break;
		case 'z':
			if (geteuid())
				fail(0, "Must be root for z flag\n");
			zflag++;
			break;
		case 'a':
			aflag++;
			break;
		case 'v':
			vflag = atoi(optarg);
			if ((vflag < 2) || (vflag > 4))
				fail(0, "Invalid version number\n");
			break;
		case 'T':
			if (optarg) {
				if (*optarg == 'u')
					timestamp_fmt = UDATE;
				else if (*optarg == 'd')
					timestamp_fmt = DDATE;
				else
					usage();
			} else {
				usage();
			}
			break;
		case '?':
		default:
			usage();
		}
	}

	if (((argc - optind) > 0) && !mflag) {

		interval = safe_strtoi(argv[optind], "invalid interval");
		if (interval < 1)
			fail(0, "invalid interval\n");
		optind++;

		if ((argc - optind) > 0) {
			count = safe_strtoi(argv[optind], "invalid count");
			if ((count <= 0) || (count == NULL))
				fail(0, "invalid count\n");
		}
		optind++;

		if ((argc - optind) > 0)
			usage();

		/*
		 * no count number was set, so we will loop infinitely
		 * at interval specified
		 */
		if (!count)
			go_forever = 1;
		stats_timer(interval);
	} else if (mflag) {

		if (cflag || rflag || sflag || zflag || nflag || aflag || vflag)
			fail(0,
			    "The -m flag may not be used with any other flags");

		for (j = 0; (argc - optind > 0) && (j < (MAX_PATHS - 1)); j++) {
			path[j] =  argv[optind];
			if (*path[j] != '/')
				fail(0, "Please fully qualify your pathname "
				    "with a leading '/'");
			optind++;
		}
		path[j] = NULL;
		if (argc - optind > 0)
			fprintf(stderr, "Only the first 50 paths "
			    "will be searched for\n");
	}

	setup();

	do {
		if (mflag) {
			mi_print();
		} else {
			if (timestamp_fmt != NODATE)
				print_timestamp(timestamp_fmt);

			if (sflag &&
			    (rpc_clts_server_kstat == NULL ||
			    nfs_server_v4_kstat == NULL)) {
				fprintf(stderr,
				    "nfsstat: kernel is not configured with "
				    "the server nfs and rpc code.\n");
			}

			/* if s and nothing else, all 3 prints are called */
			if (sflag || (!sflag && !cflag)) {
				if (rflag || (!rflag && !nflag && !aflag))
					sr_print(zflag);
				if (nflag || (!rflag && !nflag && !aflag))
					sn_print(zflag, vflag);
				if (aflag || (!rflag && !nflag && !aflag))
					sa_print(zflag, vflag);
			}
			if (cflag &&
			    (rpc_clts_client_kstat == NULL ||
			    nfs_client_kstat == NULL)) {
				fprintf(stderr,
				    "nfsstat: kernel is not configured with"
				    " the client nfs and rpc code.\n");
			}
			if (cflag || (!sflag && !cflag)) {
				if (rflag || (!rflag && !nflag && !aflag))
					cr_print(zflag);
				if (nflag || (!rflag && !nflag && !aflag))
					cn_print(zflag, vflag);
				if (aflag || (!rflag && !nflag && !aflag))
					ca_print(zflag, vflag);
			}
		}

		if (zflag)
			putstats();
		if (interval)
			printf("%s\n", split_line);

		if (interval > 0)
			(void) pause();
	} while ((--count > 0) || go_forever);

	kstat_close(kc);
	free(ksum_kstat);
	return (0);
}


static int
getstats_rpc(void)
{
	int field_width = 0;

	if (rpc_clts_client_kstat != NULL) {
		safe_kstat_read(kc, rpc_clts_client_kstat, NULL);
		field_width = stat_width(rpc_clts_client_kstat, field_width);
	}

	if (rpc_cots_client_kstat != NULL) {
		safe_kstat_read(kc, rpc_cots_client_kstat, NULL);
		field_width = stat_width(rpc_cots_client_kstat, field_width);
	}

	if (rpc_rdma_client_kstat != NULL) {
		safe_kstat_read(kc, rpc_rdma_client_kstat, NULL);
		field_width = stat_width(rpc_rdma_client_kstat, field_width);
	}

	if (rpc_clts_server_kstat != NULL) {
		safe_kstat_read(kc, rpc_clts_server_kstat, NULL);
		field_width =  stat_width(rpc_clts_server_kstat, field_width);
	}
	if (rpc_cots_server_kstat != NULL) {
		safe_kstat_read(kc, rpc_cots_server_kstat, NULL);
		field_width = stat_width(rpc_cots_server_kstat, field_width);
	}
	if (rpc_rdma_server_kstat != NULL) {
		safe_kstat_read(kc, rpc_rdma_server_kstat, NULL);
		field_width = stat_width(rpc_rdma_server_kstat, field_width);
	}
	return (field_width);
}

static int
getstats_nfs(void)
{
	int field_width = 0;

	if (nfs_client_kstat != NULL) {
		safe_kstat_read(kc, nfs_client_kstat, NULL);
		field_width = stat_width(nfs_client_kstat, field_width);
	}
	if (nfs4_client_kstat != NULL) {
		safe_kstat_read(kc, nfs4_client_kstat, NULL);
		field_width = stat_width(nfs4_client_kstat, field_width);
	}
	if (nfs_server_v2_kstat != NULL) {
		safe_kstat_read(kc, nfs_server_v2_kstat, NULL);
		field_width = stat_width(nfs_server_v2_kstat, field_width);
	}
	if (nfs_server_v3_kstat != NULL) {
		safe_kstat_read(kc, nfs_server_v3_kstat, NULL);
		field_width = stat_width(nfs_server_v3_kstat, field_width);
	}
	if (nfs_server_v4_kstat != NULL) {
		safe_kstat_read(kc, nfs_server_v4_kstat, NULL);
		field_width = stat_width(nfs_server_v4_kstat, field_width);
	}
	return (field_width);
}

static int
getstats_rfsproc(int ver)
{
	int field_width = 0;

	if ((ver == 2) && (rfsproccnt_v2_kstat != NULL)) {
		safe_kstat_read(kc, rfsproccnt_v2_kstat, NULL);
		field_width = req_width(rfsproccnt_v2_kstat, field_width);
	}
	if ((ver == 3) && (rfsproccnt_v3_kstat != NULL)) {
		safe_kstat_read(kc, rfsproccnt_v3_kstat, NULL);
		field_width = req_width(rfsproccnt_v3_kstat, field_width);
	}
	if ((ver == 4) && (rfsproccnt_v4_kstat != NULL)) {
		safe_kstat_read(kc, rfsproccnt_v4_kstat, NULL);
		field_width = req_width(rfsproccnt_v4_kstat, field_width);
	}
	return (field_width);
}

static int
getstats_rfsreq(int ver)
{
	int field_width = 0;
	if ((ver == 2) && (rfsreqcnt_v2_kstat != NULL)) {
		safe_kstat_read(kc, rfsreqcnt_v2_kstat, NULL);
		field_width = req_width(rfsreqcnt_v2_kstat, field_width);
	}
	if ((ver == 3) && (rfsreqcnt_v3_kstat != NULL)) {
		safe_kstat_read(kc, rfsreqcnt_v3_kstat, NULL);
		field_width = req_width(rfsreqcnt_v3_kstat,  field_width);
	}
	if ((ver == 4) && (rfsreqcnt_v4_kstat != NULL)) {
		safe_kstat_read(kc, rfsreqcnt_v4_kstat, NULL);
		field_width = req_width(rfsreqcnt_v4_kstat, field_width);
	}
	return (field_width);
}

static int
getstats_aclproc(void)
{
	int field_width = 0;
	if (aclproccnt_v2_kstat != NULL) {
		safe_kstat_read(kc, aclproccnt_v2_kstat, NULL);
		field_width = req_width(aclproccnt_v2_kstat, field_width);
	}
	if (aclproccnt_v3_kstat != NULL) {
		safe_kstat_read(kc, aclproccnt_v3_kstat, NULL);
		field_width = req_width(aclproccnt_v3_kstat, field_width);
	}
	return (field_width);
}

static int
getstats_aclreq(void)
{
	int field_width = 0;
	if (aclreqcnt_v2_kstat != NULL) {
		safe_kstat_read(kc, aclreqcnt_v2_kstat, NULL);
		field_width = req_width(aclreqcnt_v2_kstat, field_width);
	}
	if (aclreqcnt_v3_kstat != NULL) {
		safe_kstat_read(kc, aclreqcnt_v3_kstat, NULL);
		field_width = req_width(aclreqcnt_v3_kstat, field_width);
	}
	return (field_width);
}

static void
putstats(void)
{
	if (rpc_clts_client_kstat != NULL)
		safe_kstat_write(kc, rpc_clts_client_kstat, NULL);
	if (rpc_cots_client_kstat != NULL)
		safe_kstat_write(kc, rpc_cots_client_kstat, NULL);
	if (rpc_rdma_client_kstat != NULL)
		safe_kstat_write(kc, rpc_rdma_client_kstat, NULL);
	if (nfs_client_kstat != NULL)
		safe_kstat_write(kc, nfs_client_kstat, NULL);
	if (nfs4_client_kstat != NULL)
		safe_kstat_write(kc, nfs4_client_kstat, NULL);
	if (rpc_clts_server_kstat != NULL)
		safe_kstat_write(kc, rpc_clts_server_kstat, NULL);
	if (rpc_cots_server_kstat != NULL)
		safe_kstat_write(kc, rpc_cots_server_kstat, NULL);
	if (rpc_rdma_server_kstat != NULL)
		safe_kstat_write(kc, rpc_rdma_server_kstat, NULL);
	if (nfs_server_v2_kstat != NULL)
		safe_kstat_write(kc, nfs_server_v2_kstat, NULL);
	if (nfs_server_v3_kstat != NULL)
		safe_kstat_write(kc, nfs_server_v3_kstat, NULL);
	if (nfs_server_v4_kstat != NULL)
		safe_kstat_write(kc, nfs_server_v4_kstat, NULL);
	if (rfsproccnt_v2_kstat != NULL)
		safe_kstat_write(kc, rfsproccnt_v2_kstat, NULL);
	if (rfsproccnt_v3_kstat != NULL)
		safe_kstat_write(kc, rfsproccnt_v3_kstat, NULL);
	if (rfsproccnt_v4_kstat != NULL)
		safe_kstat_write(kc, rfsproccnt_v4_kstat, NULL);
	if (rfsreqcnt_v2_kstat != NULL)
		safe_kstat_write(kc, rfsreqcnt_v2_kstat, NULL);
	if (rfsreqcnt_v3_kstat != NULL)
		safe_kstat_write(kc, rfsreqcnt_v3_kstat, NULL);
	if (rfsreqcnt_v4_kstat != NULL)
		safe_kstat_write(kc, rfsreqcnt_v4_kstat, NULL);
	if (aclproccnt_v2_kstat != NULL)
		safe_kstat_write(kc, aclproccnt_v2_kstat, NULL);
	if (aclproccnt_v3_kstat != NULL)
		safe_kstat_write(kc, aclproccnt_v3_kstat, NULL);
	if (aclreqcnt_v2_kstat != NULL)
		safe_kstat_write(kc, aclreqcnt_v2_kstat, NULL);
	if (aclreqcnt_v3_kstat != NULL)
		safe_kstat_write(kc, aclreqcnt_v3_kstat, NULL);
}

static void
setup(void)
{
	if ((kc = kstat_open()) == NULL)
		fail(1, "kstat_open(): can't open /dev/kstat");

	/* alloc space for our temporary kstat */
	safe_zalloc((void **)&ksum_kstat, sizeof (kstat_t), 0);
	rpc_clts_client_kstat = kstat_lookup(kc, "unix", 0, "rpc_clts_client");
	rpc_clts_server_kstat = kstat_lookup(kc, "unix", 0, "rpc_clts_server");
	rpc_cots_client_kstat = kstat_lookup(kc, "unix", 0, "rpc_cots_client");
	rpc_cots_server_kstat = kstat_lookup(kc, "unix", 0, "rpc_cots_server");
	rpc_rdma_client_kstat = kstat_lookup(kc, "unix", 0, "rpc_rdma_client");
	rpc_rdma_server_kstat = kstat_lookup(kc, "unix", 0, "rpc_rdma_server");
	nfs_client_kstat = kstat_lookup(kc, "nfs", 0, "nfs_client");
	nfs4_client_kstat = kstat_lookup(kc, "nfs", 0, "nfs4_client");
	nfs_server_v2_kstat = kstat_lookup(kc, "nfs", 2, "nfs_server");
	nfs_server_v3_kstat = kstat_lookup(kc, "nfs", 3, "nfs_server");
	nfs_server_v4_kstat = kstat_lookup(kc, "nfs", 4, "nfs_server");
	rfsproccnt_v2_kstat = kstat_lookup(kc, "nfs", 0, "rfsproccnt_v2");
	rfsproccnt_v3_kstat = kstat_lookup(kc, "nfs", 0, "rfsproccnt_v3");
	rfsproccnt_v4_kstat = kstat_lookup(kc, "nfs", 0, "rfsproccnt_v4");
	rfsreqcnt_v2_kstat = kstat_lookup(kc, "nfs", 0, "rfsreqcnt_v2");
	rfsreqcnt_v3_kstat = kstat_lookup(kc, "nfs", 0, "rfsreqcnt_v3");
	rfsreqcnt_v4_kstat = kstat_lookup(kc, "nfs", 0, "rfsreqcnt_v4");
	aclproccnt_v2_kstat = kstat_lookup(kc, "nfs_acl", 0, "aclproccnt_v2");
	aclproccnt_v3_kstat = kstat_lookup(kc, "nfs_acl", 0, "aclproccnt_v3");
	aclreqcnt_v2_kstat = kstat_lookup(kc, "nfs_acl", 0, "aclreqcnt_v2");
	aclreqcnt_v3_kstat = kstat_lookup(kc, "nfs_acl", 0, "aclreqcnt_v3");
	if (rpc_clts_client_kstat == NULL && rpc_cots_server_kstat == NULL &&
	    rfsproccnt_v2_kstat == NULL && rfsreqcnt_v3_kstat == NULL)
		fail(0, "Multiple kstat lookups failed."
		    "Your kernel module may not be loaded\n");
}

static int
req_width(kstat_t *req, int field_width)
{
	int i, nreq, per, len;
	char fixlen[128];
	kstat_named_t *knp;
	uint64_t tot;

	tot = 0;
	knp = KSTAT_NAMED_PTR(req);
	for (i = 0; i < req->ks_ndata; i++)
		tot += knp[i].value.ui64;

	knp = kstat_data_lookup(req, "null");
	nreq = req->ks_ndata - (knp - KSTAT_NAMED_PTR(req));

	for (i = 0; i < nreq; i++) {
		len = strlen(knp[i].name) + 1;
		if (field_width < len)
			field_width = len;
		if (tot)
			per = (int)(knp[i].value.ui64 * 100 / tot);
		else
			per = 0;
		(void) sprintf(fixlen, "%" PRIu64 " %d%%",
		    knp[i].value.ui64, per);
		len = strlen(fixlen) + 1;
		if (field_width < len)
			field_width = len;
	}
	return (field_width);
}

static int
stat_width(kstat_t *req, int field_width)
{
	int i, nreq, len;
	char fixlen[128];
	kstat_named_t *knp;

	knp = KSTAT_NAMED_PTR(req);
	nreq = req->ks_ndata;

	for (i = 0; i < nreq; i++) {
		len = strlen(knp[i].name) + 1;
		if (field_width < len)
			field_width = len;
		(void) sprintf(fixlen, "%" PRIu64, knp[i].value.ui64);
		len = strlen(fixlen) + 1;
		if (field_width < len)
			field_width = len;
	}
	return (field_width);
}

static void
cr_print(int zflag)
{
	int field_width;

	field_width = getstats_rpc();
	if (field_width == 0)
		return;

	stat_print("\nClient rpc:\nConnection oriented:",
	    rpc_cots_client_kstat,
	    &old_rpc_cots_client_kstat.kst, field_width, zflag);
	stat_print("Connectionless:", rpc_clts_client_kstat,
	    &old_rpc_clts_client_kstat.kst, field_width, zflag);
	stat_print("RDMA based:", rpc_rdma_client_kstat,
	    &old_rpc_rdma_client_kstat.kst, field_width, zflag);
}

static void
sr_print(int zflag)
{
	int field_width;

	field_width = getstats_rpc();
	if (field_width == 0)
		return;

	stat_print("\nServer rpc:\nConnection oriented:", rpc_cots_server_kstat,
	    &old_rpc_cots_server_kstat.kst, field_width, zflag);
	stat_print("Connectionless:", rpc_clts_server_kstat,
	    &old_rpc_clts_server_kstat.kst, field_width, zflag);
	stat_print("RDMA based:", rpc_rdma_server_kstat,
	    &old_rpc_rdma_server_kstat.kst, field_width, zflag);
}

static void
cn_print(int zflag, int vflag)
{
	int field_width;

	field_width = getstats_nfs();
	if (field_width == 0)
		return;

	if (vflag == 0) {
		nfsstat_kstat_sum(nfs_client_kstat, nfs4_client_kstat,
		    ksum_kstat);
		stat_print("\nClient nfs:", ksum_kstat, &old_ksum_kstat.kst,
		    field_width, zflag);
	}

	if (vflag == 2 || vflag == 3) {
		stat_print("\nClient nfs:", nfs_client_kstat,
		    &old_nfs_client_kstat.kst, field_width, zflag);
	}

	if (vflag == 4) {
		stat_print("\nClient nfs:", nfs4_client_kstat,
		    &old_nfs4_client_kstat.kst, field_width, zflag);
	}

	if (vflag == 2 || vflag == 0) {
		field_width = getstats_rfsreq(2);
		req_print(rfsreqcnt_v2_kstat, &old_rfsreqcnt_v2_kstat.kst,
		    2, field_width, zflag);
	}

	if (vflag == 3 || vflag == 0) {
		field_width = getstats_rfsreq(3);
		req_print(rfsreqcnt_v3_kstat, &old_rfsreqcnt_v3_kstat.kst, 3,
		    field_width, zflag);
	}

	if (vflag == 4 || vflag == 0) {
		field_width = getstats_rfsreq(4);
		req_print_v4(rfsreqcnt_v4_kstat, &old_rfsreqcnt_v4_kstat.kst,
		    field_width, zflag);
	}
}

static void
sn_print(int zflag, int vflag)
{
	int  field_width;

	field_width = getstats_nfs();
	if (field_width == 0)
		return;

	if (vflag == 2 || vflag == 0) {
		stat_print("\nServer NFSv2:", nfs_server_v2_kstat,
		    &old_nfs_server_v2_kstat.kst, field_width, zflag);
	}

	if (vflag == 3 || vflag == 0) {
		stat_print("\nServer NFSv3:", nfs_server_v3_kstat,
		    &old_nfs_server_v3_kstat.kst, field_width, zflag);
	}

	if (vflag == 4 || vflag == 0) {
		stat_print("\nServer NFSv4:", nfs_server_v4_kstat,
		    &old_nfs_server_v4_kstat.kst, field_width, zflag);
	}

	if (vflag == 2 || vflag == 0) {
		field_width = getstats_rfsproc(2);
		req_print(rfsproccnt_v2_kstat, &old_rfsproccnt_v2_kstat.kst,
		    2, field_width, zflag);
	}

	if (vflag == 3 || vflag == 0) {
		field_width = getstats_rfsproc(3);
		req_print(rfsproccnt_v3_kstat, &old_rfsproccnt_v3_kstat.kst,
		    3, field_width, zflag);
	}

	if (vflag == 4 || vflag == 0) {
		field_width = getstats_rfsproc(4);
		req_print_v4(rfsproccnt_v4_kstat, &old_rfsproccnt_v4_kstat.kst,
		    field_width, zflag);
	}
}

static void
ca_print(int zflag, int vflag)
{
	int  field_width;

	field_width = getstats_aclreq();
	if (field_width == 0)
		return;

	printf("\nClient nfs_acl:\n");

	if (vflag == 2 || vflag == 0) {
		req_print(aclreqcnt_v2_kstat, &old_aclreqcnt_v2_kstat.kst, 2,
		    field_width, zflag);
	}

	if (vflag == 3 || vflag == 0) {
		req_print(aclreqcnt_v3_kstat, &old_aclreqcnt_v3_kstat.kst,
		    3, field_width, zflag);
	}
}

static void
sa_print(int zflag, int vflag)
{
	int  field_width;

	field_width = getstats_aclproc();
	if (field_width == 0)
		return;

	printf("\nServer nfs_acl:\n");

	if (vflag == 2 || vflag == 0) {
		req_print(aclproccnt_v2_kstat, &old_aclproccnt_v2_kstat.kst,
		    2, field_width, zflag);
	}

	if (vflag == 3 || vflag == 0) {
		req_print(aclproccnt_v3_kstat, &old_aclproccnt_v3_kstat.kst,
		    3, field_width, zflag);
	}
}

#define	MIN(a, b)	((a) < (b) ? (a) : (b))

static void
req_print(kstat_t *req, kstat_t *req_old, int ver, int field_width,
    int zflag)
{
	int i, j, nreq, per, ncolumns;
	uint64_t tot, old_tot;
	char fixlen[128];
	kstat_named_t *knp;
	kstat_named_t *kptr;
	kstat_named_t *knp_old;

	if (req == NULL)
		return;

	if (field_width == 0)
		return;

	ncolumns = (MAX_COLUMNS -1)/field_width;
	knp = kstat_data_lookup(req, "null");
	knp_old = KSTAT_NAMED_PTR(req_old);

	kptr = KSTAT_NAMED_PTR(req);
	nreq = req->ks_ndata - (knp - KSTAT_NAMED_PTR(req));

	tot = 0;
	old_tot = 0;

	if (knp_old == NULL) {
		old_tot = 0;
	}

	for (i = 0; i < req->ks_ndata; i++)
		tot += kptr[i].value.ui64;

	if (interval && knp_old != NULL) {
		for (i = 0; i < req_old->ks_ndata; i++)
			old_tot += knp_old[i].value.ui64;
		tot -= old_tot;
	}

	printf("Version %d: (%" PRIu64 " calls)\n", ver, tot);

	for (i = 0; i < nreq; i += ncolumns) {
		for (j = i; j < MIN(i + ncolumns, nreq); j++) {
			printf("%-*s", field_width, knp[j].name);
		}
		printf("\n");
		for (j = i; j < MIN(i + ncolumns, nreq); j++) {
			if (tot && interval && knp_old != NULL)
				per = (int)((knp[j].value.ui64 -
				    knp_old[j].value.ui64) * 100 / tot);
			else if (tot)
				per = (int)(knp[j].value.ui64 * 100 / tot);
			else
				per = 0;
			(void) sprintf(fixlen, "%" PRIu64 " %d%% ",
			    ((interval && knp_old != NULL) ?
			    (knp[j].value.ui64 - knp_old[j].value.ui64)
			    : knp[j].value.ui64), per);
			printf("%-*s", field_width, fixlen);
		}
		printf("\n");
	}
	if (zflag) {
		for (i = 0; i < req->ks_ndata; i++)
			knp[i].value.ui64 = 0;
	}
	if (knp_old != NULL)
		nfsstat_kstat_copy(req, req_old, 1);
	else
		nfsstat_kstat_copy(req, req_old, 0);
}

/*
 * Separate version of the req_print() to deal with V4 and its use of
 * procedures and operations.  It looks odd to have the counts for
 * both of those lumped into the same set of statistics so this
 * function (copy of req_print() does the separation and titles).
 */

#define	COUNT	2

static void
req_print_v4(kstat_t *req, kstat_t *req_old, int field_width, int zflag)
{
	int i, j, nreq, per, ncolumns;
	uint64_t tot, tot_ops, old_tot, old_tot_ops;
	char fixlen[128];
	kstat_named_t *kptr;
	kstat_named_t *knp;
	kstat_named_t *kptr_old;

	if (req == NULL)
		return;

	if (field_width == 0)
		return;

	ncolumns = (MAX_COLUMNS)/field_width;
	kptr = KSTAT_NAMED_PTR(req);
	kptr_old = KSTAT_NAMED_PTR(req_old);

	if (kptr_old == NULL) {
		old_tot_ops = 0;
		old_tot = 0;
	} else {
		old_tot =  kptr_old[0].value.ui64 + kptr_old[1].value.ui64;
		for (i = 2, old_tot_ops = 0; i < req_old->ks_ndata; i++)
			old_tot_ops += kptr_old[i].value.ui64;
	}

	/* Count the number of operations sent */
	for (i = 2, tot_ops = 0; i < req->ks_ndata; i++)
		tot_ops += kptr[i].value.ui64;
	/* For v4 NULL/COMPOUND are the only procedures */
	tot = kptr[0].value.ui64 + kptr[1].value.ui64;

	if (interval) {
		tot -= old_tot;
		tot_ops -= old_tot_ops;
	}

	printf("Version 4: (%" PRIu64 " calls)\n", tot);

	knp = kstat_data_lookup(req, "null");
	nreq = req->ks_ndata - (knp - KSTAT_NAMED_PTR(req));

	for (i = 0; i < COUNT; i += ncolumns) {
		for (j = i; j < MIN(i + ncolumns, 2); j++) {
			printf("%-*s", field_width, knp[j].name);
		}
		printf("\n");
		for (j = i; j < MIN(i + ncolumns, 2); j++) {
			if (tot && interval && kptr_old != NULL)
				per = (int)((knp[j].value.ui64 -
				    kptr_old[j].value.ui64) * 100 / tot);
			else if (tot)
				per = (int)(knp[j].value.ui64 * 100 / tot);
			else
				per = 0;
			(void) sprintf(fixlen, "%" PRIu64 " %d%% ",
			    ((interval && kptr_old != NULL) ?
			    (knp[j].value.ui64 - kptr_old[j].value.ui64)
			    : knp[j].value.ui64), per);
			printf("%-*s", field_width, fixlen);
		}
		printf("\n");
	}

	printf("Version 4: (%" PRIu64 " operations)\n", tot_ops);
	for (i = 2; i < nreq; i += ncolumns) {
		for (j = i; j < MIN(i + ncolumns, nreq); j++) {
			printf("%-*s", field_width, knp[j].name);
		}
		printf("\n");
		for (j = i; j < MIN(i + ncolumns, nreq); j++) {
			if (tot_ops && interval && kptr_old != NULL)
				per = (int)((knp[j].value.ui64 -
				    kptr_old[j].value.ui64) * 100 / tot_ops);
			else if (tot_ops)
				per = (int)(knp[j].value.ui64 * 100 / tot_ops);
			else
				per = 0;
			(void) sprintf(fixlen, "%" PRIu64 " %d%% ",
			    ((interval && kptr_old != NULL) ?
			    (knp[j].value.ui64 - kptr_old[j].value.ui64)
			    : knp[j].value.ui64), per);
			printf("%-*s", field_width, fixlen);
		}
		printf("\n");
	}
	if (zflag) {
		for (i = 0; i < req->ks_ndata; i++)
			kptr[i].value.ui64 = 0;
	}
	if (kptr_old != NULL)
		nfsstat_kstat_copy(req, req_old, 1);
	else
		nfsstat_kstat_copy(req, req_old, 0);
}

static void
stat_print(const char *title_string, kstat_t *req, kstat_t  *req_old,
    int field_width, int zflag)
{
	int i, j, nreq, ncolumns;
	char fixlen[128];
	kstat_named_t *knp;
	kstat_named_t *knp_old;

	if (req == NULL)
		return;

	if (field_width == 0)
		return;

	printf("%s\n", title_string);
	ncolumns = (MAX_COLUMNS -1)/field_width;

	/* MEANS knp =  (kstat_named_t *)req->ks_data */
	knp = KSTAT_NAMED_PTR(req);
	nreq = req->ks_ndata;
	knp_old = KSTAT_NAMED_PTR(req_old);

	for (i = 0; i < nreq; i += ncolumns) {
		/* prints out the titles of the columns */
		for (j = i; j < MIN(i + ncolumns, nreq); j++) {
			printf("%-*s", field_width, knp[j].name);
		}
		printf("\n");
		/* prints out the stat numbers */
		for (j = i; j < MIN(i + ncolumns, nreq); j++) {
			(void) sprintf(fixlen, "%" PRIu64 " ",
			    (interval && knp_old != NULL) ?
			    (knp[j].value.ui64 - knp_old[j].value.ui64)
			    : knp[j].value.ui64);
			printf("%-*s", field_width, fixlen);
		}
		printf("\n");

	}
	if (zflag) {
		for (i = 0; i < req->ks_ndata; i++)
			knp[i].value.ui64 = 0;
	}

	if (knp_old != NULL)
		nfsstat_kstat_copy(req, req_old, 1);
	else
		nfsstat_kstat_copy(req, req_old, 0);
}

static void
nfsstat_kstat_sum(kstat_t *kstat1, kstat_t *kstat2, kstat_t *sum)
{
	int i;
	kstat_named_t *knp1, *knp2, *knpsum;
	if (kstat1 == NULL || kstat2 == NULL)
		return;

	knp1 = KSTAT_NAMED_PTR(kstat1);
	knp2 = KSTAT_NAMED_PTR(kstat2);
	if (sum->ks_data == NULL)
		nfsstat_kstat_copy(kstat1, sum, 0);
	knpsum = KSTAT_NAMED_PTR(sum);

	for (i = 0; i < (kstat1->ks_ndata); i++)
		knpsum[i].value.ui64 =  knp1[i].value.ui64 + knp2[i].value.ui64;
}

/*
 * my_dir and my_path could be pointers
 */
struct myrec {
	ulong_t my_fsid;
	char my_dir[MAXPATHLEN];
	char *my_path;
	char *ig_path;
	struct myrec *next;
};

/*
 * Print the mount table info
 */
static void
mi_print(void)
{
	FILE *mt;
	struct extmnttab m;
	struct myrec *list, *mrp, *pmrp;
	char *flavor;
	int ignored = 0;
	seconfig_t nfs_sec;
	kstat_t *ksp;
	struct mntinfo_kstat mik;
	int transport_flag = 0;
	int path_count;
	int found;
	char *timer_name[] = {
		"Lookups",
		"Reads",
		"Writes",
		"All"
	};

	mt = fopen(MNTTAB, "r");
	if (mt == NULL) {
		perror(MNTTAB);
		exit(0);
	}

	list = NULL;
	resetmnttab(mt);

	while (getextmntent(mt, &m, sizeof (struct extmnttab)) == 0) {
		/* ignore non "nfs" and save the "ignore" entries */
		if (strcmp(m.mnt_fstype, MNTTYPE_NFS) != 0)
			continue;
		/*
		 * Check to see here if user gave a path(s) to
		 * only show the mount point they wanted
		 * Iterate through the list of paths the user gave and see
		 * if any of them match our current nfs mount
		 */
		if (path[0] != NULL) {
			found = 0;
			for (path_count = 0; path[path_count] != NULL;
			    path_count++) {
				if (strcmp(path[path_count], m.mnt_mountp)
				    == 0) {
					found = 1;
					break;
				}
			}
			if (!found)
				continue;
		}

		if ((mrp = malloc(sizeof (struct myrec))) == 0) {
			fprintf(stderr, "nfsstat: not enough memory\n");
			exit(1);
		}
		mrp->my_fsid = makedev(m.mnt_major, m.mnt_minor);
		if (ignore(m.mnt_mntopts)) {
			/*
			 * ignored entries cannot be ignored for this
			 * option. We have to display the info for this
			 * nfs mount. The ignore is an indication
			 * that the actual mount point is different and
			 * something is in between the nfs mount.
			 * So save the mount point now
			 */
			if ((mrp->ig_path = malloc(
			    strlen(m.mnt_mountp) + 1)) == 0) {
				fprintf(stderr, "nfsstat: not enough memory\n");
				exit(1);
			}
			(void) strcpy(mrp->ig_path, m.mnt_mountp);
			ignored++;
		} else {
			mrp->ig_path = 0;
			(void) strcpy(mrp->my_dir, m.mnt_mountp);
		}
		if ((mrp->my_path = strdup(m.mnt_special)) == NULL) {
			fprintf(stderr, "nfsstat: not enough memory\n");
			exit(1);
		}
		mrp->next = list;
		list = mrp;
	}

	/*
	 * If something got ignored, go to the beginning of the mnttab
	 * and look for the cachefs entries since they are the one
	 * causing this. The mount point saved for the ignored entries
	 * is matched against the special to get the actual mount point.
	 * We are interested in the acutal mount point so that the output
	 * look nice too.
	 */
	if (ignored) {
		rewind(mt);
		resetmnttab(mt);
		while (getextmntent(mt, &m, sizeof (struct extmnttab)) == 0) {

			/* ignore non "cachefs" */
			if (strcmp(m.mnt_fstype, MNTTYPE_CACHEFS) != 0)
				continue;

			for (mrp = list; mrp; mrp = mrp->next) {
				if (mrp->ig_path == 0)
					continue;
				if (strcmp(mrp->ig_path, m.mnt_special) == 0) {
					mrp->ig_path = 0;
					(void) strcpy(mrp->my_dir,
					    m.mnt_mountp);
				}
			}
		}
		/*
		 * Now ignored entries which do not have
		 * the my_dir initialized are really ignored; This never
		 * happens unless the mnttab is corrupted.
		 */
		for (pmrp = 0, mrp = list; mrp; mrp = mrp->next) {
			if (mrp->ig_path == 0)
				pmrp = mrp;
			else if (pmrp)
				pmrp->next = mrp->next;
			else
				list = mrp->next;
		}
	}

	(void) fclose(mt);


	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
		int i;

		if (ksp->ks_type != KSTAT_TYPE_RAW)
			continue;
		if (strcmp(ksp->ks_module, "nfs") != 0)
			continue;
		if (strcmp(ksp->ks_name, "mntinfo") != 0)
			continue;

		for (mrp = list; mrp; mrp = mrp->next) {
			if ((mrp->my_fsid & MAXMIN) == ksp->ks_instance)
				break;
		}
		if (mrp == 0)
			continue;

		if (safe_kstat_read(kc, ksp, &mik) == -1)
			continue;

		printf("%s from %s\n", mrp->my_dir, mrp->my_path);

		/*
		 * for printing rdma transport and provider string.
		 * This way we avoid modifying the kernel mntinfo_kstat
		 * struct for protofmly.
		 */
		if (strcmp(mik.mik_proto, "ibtf") == 0) {
			printf(" Flags:		vers=%u,proto=rdma",
			    mik.mik_vers);
			transport_flag = 1;
		} else {
			printf(" Flags:		vers=%u,proto=%s",
			    mik.mik_vers, mik.mik_proto);
			transport_flag = 0;
		}

		/*
		 *  get the secmode name from /etc/nfssec.conf.
		 */
		if (!nfs_getseconfig_bynumber(mik.mik_secmod, &nfs_sec)) {
			flavor = nfs_sec.sc_name;
		} else
			flavor = NULL;

		if (flavor != NULL)
			printf(",sec=%s", flavor);
		else
			printf(",sec#=%d", mik.mik_secmod);

		printf(",%s", (mik.mik_flags & MI_HARD) ? "hard" : "soft");
		if (mik.mik_flags & MI_PRINTED)
			printf(",printed");
		printf(",%s", (mik.mik_flags & MI_INT) ? "intr" : "nointr");
		if (mik.mik_flags & MI_DOWN)
			printf(",down");
		if (mik.mik_flags & MI_NOAC)
			printf(",noac");
		if (mik.mik_flags & MI_NOCTO)
			printf(",nocto");
		if (mik.mik_flags & MI_DYNAMIC)
			printf(",dynamic");
		if (mik.mik_flags & MI_LLOCK)
			printf(",llock");
		if (mik.mik_flags & MI_GRPID)
			printf(",grpid");
		if (mik.mik_flags & MI_RPCTIMESYNC)
			printf(",rpctimesync");
		if (mik.mik_flags & MI_LINK)
			printf(",link");
		if (mik.mik_flags & MI_SYMLINK)
			printf(",symlink");
		if (mik.mik_vers < NFS_V4 && mik.mik_flags & MI_READDIRONLY)
			printf(",readdironly");
		if (mik.mik_flags & MI_ACL)
			printf(",acl");
		if (mik.mik_flags & MI_DIRECTIO)
			printf(",forcedirectio");

		if (mik.mik_vers >= NFS_V4) {
			if (mik.mik_flags & MI4_MIRRORMOUNT)
				printf(",mirrormount");
			if (mik.mik_flags & MI4_REFERRAL)
				printf(",referral");
		}

		printf(",rsize=%d,wsize=%d,retrans=%d,timeo=%d",
		    mik.mik_curread, mik.mik_curwrite, mik.mik_retrans,
		    mik.mik_timeo);
		printf("\n");
		printf(" Attr cache:	acregmin=%d,acregmax=%d"
		    ",acdirmin=%d,acdirmax=%d\n", mik.mik_acregmin,
		    mik.mik_acregmax, mik.mik_acdirmin, mik.mik_acdirmax);

		if (transport_flag) {
			printf(" Transport:	proto=rdma, plugin=%s\n",
			    mik.mik_proto);
		}

#define	srtt_to_ms(x) x, (x * 2 + x / 2)
#define	dev_to_ms(x) x, (x * 5)

		for (i = 0; i < NFS_CALLTYPES + 1; i++) {
			int j;

			j = (i == NFS_CALLTYPES ? i - 1 : i);
			if (mik.mik_timers[j].srtt ||
			    mik.mik_timers[j].rtxcur) {
				printf(" %s:     srtt=%d (%dms), "
				    "dev=%d (%dms), cur=%u (%ums)\n",
				    timer_name[i],
				    srtt_to_ms(mik.mik_timers[i].srtt),
				    dev_to_ms(mik.mik_timers[i].deviate),
				    mik.mik_timers[i].rtxcur,
				    mik.mik_timers[i].rtxcur * 20);
			}
		}

		if (strchr(mrp->my_path, ','))
			printf(
			    " Failover:	noresponse=%d,failover=%d,"
			    "remap=%d,currserver=%s\n",
			    mik.mik_noresponse, mik.mik_failover,
			    mik.mik_remap, mik.mik_curserver);
		printf("\n");
	}
}

static char *mntopts[] = { MNTOPT_IGNORE, MNTOPT_DEV, NULL };
#define	IGNORE  0
#define	DEV	1

/*
 * Return 1 if "ignore" appears in the options string
 */
static int
ignore(char *opts)
{
	char *value;
	char *s;

	if (opts == NULL)
		return (0);
	s = strdup(opts);
	if (s == NULL)
		return (0);
	opts = s;

	while (*opts != '\0') {
		if (getsubopt(&opts, mntopts, &value) == IGNORE) {
			free(s);
			return (1);
		}
	}

	free(s);
	return (0);
}

void
usage(void)
{
	fprintf(stderr, "Usage: nfsstat [-cnrsza [-v version] "
	    "[-T d|u] [interval [count]]\n");
	fprintf(stderr, "Usage: nfsstat -m [pathname..]\n");
	exit(1);
}

void
fail(int do_perror, char *message, ...)
{
	va_list args;

	va_start(args, message);
	fprintf(stderr, "nfsstat: ");
	vfprintf(stderr, message, args);
	va_end(args);
	if (do_perror)
		fprintf(stderr, ": %s", strerror(errno));
	fprintf(stderr, "\n");
	exit(1);
}

kid_t
safe_kstat_read(kstat_ctl_t *kc, kstat_t *ksp, void *data)
{
	kid_t kstat_chain_id = kstat_read(kc, ksp, data);

	if (kstat_chain_id == -1)
		fail(1, "kstat_read(%x, '%s') failed", kc, ksp->ks_name);
	return (kstat_chain_id);
}

kid_t
safe_kstat_write(kstat_ctl_t *kc, kstat_t *ksp, void *data)
{
	kid_t kstat_chain_id = 0;

	if (ksp->ks_data != NULL) {
		kstat_chain_id = kstat_write(kc, ksp, data);

		if (kstat_chain_id == -1)
			fail(1, "kstat_write(%x, '%s') failed", kc,
			    ksp->ks_name);
	}
	return (kstat_chain_id);
}

void
stats_timer(int interval)
{
	timer_t t_id;
	itimerspec_t time_struct;
	struct sigevent sig_struct;
	struct sigaction act;

	bzero(&sig_struct, sizeof (struct sigevent));
	bzero(&act, sizeof (struct sigaction));

	/* Create timer */
	sig_struct.sigev_notify = SIGEV_SIGNAL;
	sig_struct.sigev_signo = SIGUSR1;
	sig_struct.sigev_value.sival_int = 0;

	if (timer_create(CLOCK_REALTIME, &sig_struct, &t_id) != 0) {
		fail(1, "Timer creation failed");
	}

	act.sa_handler = handle_sig;

	if (sigaction(SIGUSR1, &act, NULL) != 0) {
		fail(1, "Could not set up signal handler");
	}

	time_struct.it_value.tv_sec = interval;
	time_struct.it_value.tv_nsec = 0;
	time_struct.it_interval.tv_sec = interval;
	time_struct.it_interval.tv_nsec = 0;

	/* Arm timer */
	if ((timer_settime(t_id, 0, &time_struct, NULL)) != 0) {
		fail(1, "Setting timer failed");
	}
}

void
handle_sig(int x)
{
}

static void
nfsstat_kstat_copy(kstat_t *src, kstat_t *dst, int fr)
{

	if (fr)
		free(dst->ks_data);

	*dst = *src;

	if (src->ks_data != NULL) {
		safe_zalloc(&dst->ks_data, src->ks_data_size, 0);
		(void) memcpy(dst->ks_data, src->ks_data, src->ks_data_size);
	} else {
		dst->ks_data = NULL;
		dst->ks_data_size = 0;
	}
}

/*
 * "Safe" allocators - if we return we're guaranteed to have the desired space
 * allocated and zero-filled. We exit via fail if we can't get the space.
 */
void
safe_zalloc(void **ptr, uint_t size, int free_first)
{
	if (ptr == NULL)
		fail(1, "invalid pointer");
	if (free_first && *ptr != NULL)
		free(*ptr);
	if ((*ptr = (void *)malloc(size)) == NULL)
		fail(1, "malloc failed");
	(void) memset(*ptr, 0, size);
}

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);
}