/*
 * pdrspeed.c
 *
 * Instrument drive read/write speed
 *
 * Author: Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1996,1997,1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>
#include <nasd/nasd_site.h>
#include <nasd/nasd_pdrive.h>
#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_pdrive_client_kpdev.h>
#include <nasd/nasd_pdev.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_getopt.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>

#if NASD_CMU_PDL > 0
#include <pdllib.h>
#endif /* NASD_CMU_PDL > 0 */

char *progname;

nasd_error_string_t error_text;
#define DEF_BLOCKSIZE 131072
int blocksize = DEF_BLOCKSIZE;
#define DEF_NITERS 10
int niters = DEF_NITERS;
nasd_identifier_t nasdid = NASD_ID_NULL;
int partnum = 1;
int suppress_noop = 0;

int do_write = 0;
int increment_offset = 0;
int nondefault_binding = 0;

nasd_uint64 bms_targ = 0;

char *server_name;
int ufs_fd;
int do_ufs = 0;
nasd_drive_handle_t h;
int binding_type;
int binding_args_len;
void *binding_args;
nasd_drive_param_kpdev_t kpbind;

int eject_obj = 0;
int flush_obj = 0;

nasd_cookie_t cookie;
nasd_sec_keyring_t keys;
nasd_security_param_t sec_param;
nasd_uint16 protection;

int skip_preop = 0;

nasd_offset_t g_offset = 0;
unsigned char bit_pattern = 0xff;
int do_compare = 0;
char *gbuf = NULL;

FILE *cdf_file = NULL;
char *cdf_name = NULL;
#if NASD_IDLE_SUPPORT > 0
FILE *idle_cdf_file = NULL;
char *idle_cdf_name = NULL;
#endif /* NASD_IDLE_SUPPORT > 0 */

FILE *data_file = NULL;
char *data_file_name = NULL;

typedef struct iter_info_s {
  double  tm;
#if NASD_IDLE_SUPPORT > 0
  double  idle;
#endif /* NASD_IDLE_SUPPORT > 0 */
} iter_info_t;

iter_info_t *iters;

#define CL_ERROR_STRING(_handle_,_status_,_str_) \
  ((_handle_) ? nasd_cl_error_string(_handle_, _status_, _str_) : ((_status_) ? "Unknown" : "Success"))

int
double_cmp(
  nasd_qsort_arg_type_t item1,
  nasd_qsort_arg_type_t item2)
{
  double *d1, *d2;

  d1 = (double *)item1;
  d2 = (double *)item2;
  if ((*d1) > (*d2))
    return(1);
  else
    return(-1);
}

void
usage()
{
  int i;
  fprintf(stderr, "USAGE: %s [options] servername master_password\n", progname);
  fprintf(stderr, "  -a identifier (default = root of partition)\n");
  fprintf(stderr, "  -b blocksize (default=%d)\n", DEF_BLOCKSIZE);
  fprintf(stderr, "  -i increment offsets on each iter\n");
  fprintf(stderr, "  -k use kpdev\n");
  fprintf(stderr, "  -l use colocated drive\n");
  fprintf(stderr, "  -M use message queues\n");
  fprintf(stderr, "  -n iterations (default=%d)\n", DEF_NITERS);
  fprintf(stderr, "  -N suppress noop\n");
  fprintf(stderr, "  -p partnum (default=%d)\n", partnum);
  fprintf(stderr, "  -t throttle value\n");
  fprintf(stderr, "  -v bit pattern\n");
  fprintf(stderr, "  -c do bit compare against bit pattern after I/O\n");
  fprintf(stderr, "  -w write\n");
  fprintf(stderr, "  -T use DCE-TCP\n");
  fprintf(stderr, "  -C cdf file\n");
#if NASD_IDLE_SUPPORT > 0
  fprintf(stderr, "  -I idle cdf file\n");
#endif /* NASD_IDLE_SUPPORT > 0 */
  fprintf(stderr, "  -D data file\n");
  fprintf(stderr, "  -e eject obj before each iter\n");
  fprintf(stderr, "  -u ufs tests\n");
  fprintf(stderr, "  -f flush obj between iters\n");
  fprintf(stderr, "  -F kpdev fake rw binding\n");
  fprintf(stderr, "  -P skip drive pre-op\n");
  fprintf(stderr, "  -s security level\n");
  for(i = 0; i <= NASD_MAX_SECURITY_LEVEL; i++) {
    fprintf(stderr, "     %d %s\n", i, nasd_sec_level_string(i));
  }
  fflush(stderr);
  exit(1);
}

void
dousleep(
  int  usec)
{
  struct timeval tv;
  tv.tv_sec = usec/1000000;
  tv.tv_usec = usec%1000000;
  select(1, NULL, NULL, NULL, &tv);
}

nasd_identifier_t
get_root_object()
{
  nasd_security_param_t sp;
  nasd_status_t rc;
  nasd_ctrl_part_info_t ptinfo;

  sp.partnum = partnum;
  sp.actual_protection = protection;
  if(protection) {
    sp.type = NASD_BLACK_KEY;
  }

  rc = nasd_cl_p_ctrl_get_part_info(h, keys.black_key, &sp, NULL,
                                    partnum, &ptinfo);

  if ((rc == NASD_CTRL_ID_CHECK_FAILED) && (ptinfo.ctrl_id == 0)) {
    /* got back a page of zeroes - no such partition */
    fprintf(stderr, "ERROR: partition %d does not exist\n", partnum);
    fflush(stderr);
    exit(1);
  }

  if (rc) {
    fprintf(stderr, "ERROR: got 0x%x (%s) getting partition info\n",
            rc, nasd_error_string(rc));
    fflush(stderr);
    exit(1);
  }

  if (ptinfo.num_obj == 0) {
    fprintf(stderr, "ERROR: partition has no objects!\n");
    fflush(stderr);
    exit(1);
  }

  return ptinfo.first_obj;
}

void
doflush(
  nasd_identifier_t   nasdid,
  nasd_status_t      *nasd_status,
  nasd_rpc_status_t  *status)
{
  if (do_ufs == 0) {
    nasd_p_flush_obj_dr_args_t args;
    nasd_p_flush_obj_dr_res_t res;

    args.in_identifier = nasdid;
    args.in_partnum = partnum;
    nasd_cl_p_flush_obj_dr(h, cookie.key, &sec_param, &cookie.capability,
                           &args, &res, status);
    *nasd_status = res.nasd_status;
  }
  else {
    int rc;

    ufs_fd = open(server_name, O_RDWR|O_CREAT);
    if (ufs_fd < 0) {
      fprintf(stderr, "ERROR from ufs open, errno=%d\n", errno);
      exit(1);
    }
    rc = fsync(ufs_fd);
    if (rc < 0) {
      fprintf(stderr, "ERROR from ufs fsync, errno=%d\n", errno);
      exit(1);
    }
    close(ufs_fd);
    ufs_fd = (-1);
    sync(); sync(); sync();
    *nasd_status = NASD_SUCCESS;
    *status = 0;
  }
}

void
doeject(
  nasd_identifier_t   nasdid,
  nasd_status_t      *nasd_status,
  nasd_rpc_status_t  *status)
{
  if (do_ufs == 0) {
    nasd_p_eject_obj_dr_args_t args;
    nasd_p_eject_obj_dr_res_t res;

    args.in_identifier = nasdid;
    args.in_partnum = partnum;
    nasd_cl_p_eject_obj_dr(h, cookie.key, &sec_param, &cookie.capability,
                           &args, &res, status);
    *nasd_status = res.nasd_status;
  }
  else {
    *nasd_status = NASD_OP_NOT_SUPPORTED;
    *status = 0;
#if defined(DEC_OSF) && (NASD_CMU_PDL > 0)
  {
    int rc, i;

    ufs_fd = open(server_name, O_RDWR|O_CREAT);
    if (ufs_fd < 0) {
      fprintf(stderr, "ERROR from ufs open, errno=%d\n", errno);
      exit(1);
    }
    rc = fsync(ufs_fd);
    if (rc < 0) {
      fprintf(stderr, "ERROR from ufs fsync, errno=%d\n", errno);
      exit(1);
    }
    close(ufs_fd);
    ufs_fd = (-1);
    for(i=0;i<3;i++) {
      sync(); sync(); sync();
      dousleep(400000);
      rc = PDL_cflush();
      if (rc) {
        *nasd_status = NASD_FAIL;
        *status = 0;
        return;
      }
    }
    sync(); sync(); sync();
    *nasd_status = NASD_SUCCESS;
    *status = 0;
  }
#endif /* DEC_OSF && (NASD_CMU_PDL > 0) */
    /*
     * Add appropriate ways for other
     * systems here
     */
  }
}

void
doufsread(
  int                 fd,
  char               *buf,
  int                 len,
  nasd_status_t      *nasd_status,
  nasd_rpc_status_t  *status)
{
  int rc, remain;
  char *ubuf;

  remain = len;
  ubuf = buf;
  while(remain) {
    rc = read(fd, ubuf, remain);
    if (rc <= 0) {
      *nasd_status = NASD_FAIL;
      *status = 0;
      return;
    }
    remain -= rc;
    ubuf += rc;
  }
  *nasd_status = NASD_SUCCESS;
  *status = 0;
}

void
doufswrite(
  int                 fd,
  char               *buf,
  int                 len,
  nasd_status_t      *nasd_status,
  nasd_rpc_status_t  *status)
{
  int rc, remain;
  char *ubuf;

  remain = len;
  ubuf = buf;
  while(remain) {
    rc = write(fd, ubuf, remain);
    if (rc <= 0) {
      *nasd_status = NASD_FAIL;
      *status = 0;
      return;
    }
    remain -= rc;
    ubuf += rc;
  }
  *nasd_status = NASD_SUCCESS;
  *status = 0;
}

void
dofastread(
  nasd_identifier_t   nasdid,
  char               *buf,
  int                 len,
  nasd_status_t      *nasd_status,
  nasd_rpc_status_t  *status)
{
  nasd_p_thrtl_op_dr_args_t trd_args;
  nasd_p_smpl_op_dr_args_t rd_args;
  nasd_p_fastread_dr_res_t rd_res;
  nasd_len_t out_len;

  if (bms_targ) {
    trd_args.in_partnum = partnum;
    trd_args.in_identifier = nasdid;
    trd_args.in_offset = g_offset;
    trd_args.in_bms_targ = bms_targ;
    trd_args.in_len = len;
    nasd_cl_p_tread_simple_dr(h, cookie.key, &sec_param, &cookie.capability,
                              &trd_args, buf, &rd_res, status);
    out_len = rd_res.out_datalen;
    *nasd_status = rd_res.nasd_status;
  }
  else {
    rd_args.in_partnum = partnum;
    rd_args.in_identifier = nasdid;
    rd_args.in_offset = g_offset;
    rd_args.in_len = len;
    nasd_cl_p_read_simple_dr(h, cookie.key, &sec_param, &cookie.capability,
                             &rd_args, buf, &rd_res, status);
    out_len = rd_res.out_datalen;
    *nasd_status = rd_res.nasd_status;
  }

  if ((*nasd_status == NASD_SUCCESS) && (*status == 0)) {
    if (out_len != len)
      fprintf(stderr, "WARNING: len=%ld out_len=%ld\n",
        (long)len, (long)out_len);
  }
  else {
    fprintf(stderr, "ERROR: read got nasd_status 0x%x (%s) status 0x%x (%s)\n",
      *nasd_status, nasd_error_string(*nasd_status), *status,
      CL_ERROR_STRING(h, *status, error_text));
    exit(1);
  }
}

void
dofastwrite(
  nasd_identifier_t   nasdid,
  char               *buf,
  int                 len,
  nasd_status_t      *nasd_status,
  nasd_rpc_status_t  *status)
{
  nasd_p_smpl_op_dr_args_t wr_args;
  nasd_p_fastwrite_dr_res_t wr_res;
  nasd_len_t out_len;

  if (bms_targ) {
    fprintf(stderr, "%s: throttling not supported on writes\n", progname);
    fflush(stderr);
    exit(1);
  }
  else {
    wr_args.in_partnum = partnum;
    wr_args.in_identifier = nasdid;
    wr_args.in_offset = g_offset;
    wr_args.in_len = len;
    nasd_cl_p_write_simple_dr(h, cookie.key, &sec_param, &cookie.capability,
                              &wr_args, buf, &wr_res, status);
    out_len = wr_res.out_datalen;
    *nasd_status = wr_res.nasd_status;
  }

  if (*nasd_status || *status) {
    fprintf(stderr, "ERROR: write got nasd_status 0x%x (%s) status 0x%x (%s)\n",
      *nasd_status, nasd_error_string(*nasd_status), *status,
      CL_ERROR_STRING(h, *status, error_text));
    exit(1);
  }

  if (out_len != len)
    fprintf(stderr, "WARNING: len=%ld out_len=%ld\n",
      (long)len, (long)out_len);
}

void
perform_io(
  nasd_identifier_t   nasdid,
  char               *buf,
  int                 len,
  nasd_status_t      *nasd_statusp,
  nasd_rpc_status_t  *statusp)
{
  int i, b;
  char c;

  if (do_ufs) {
    ufs_fd = open(server_name, O_RDWR|O_CREAT);
    if (ufs_fd < 0) {
      fprintf(stderr, "ERROR from ufs open, errno=%d\n", errno);
      exit(1);
    }
  }

  if (do_write) {
    if (do_ufs)
      doufswrite(ufs_fd, buf, blocksize, nasd_statusp, statusp);
    else
      dofastwrite(nasdid, buf, blocksize, nasd_statusp, statusp);
  }
  else {
    if (do_compare) {
      c = bit_pattern + 1;
      for(i=0;i<blocksize;i++)
        buf[i] = c;
    }
    if (do_ufs)
      doufsread(ufs_fd, buf, blocksize, nasd_statusp, statusp);
    else
      dofastread(nasdid, buf, blocksize, nasd_statusp, statusp);
  }

  if (do_compare) {
    printf("comparing buf, bit pattern 0x%x\n", bit_pattern&0xff);
    for(b=i=0;i<blocksize;i++) {
      if ((buf[i]&0xff) != (bit_pattern&0xff)) {
        fprintf(stderr, "Failed buf compare buf[%d]=0x%x!\n", i, buf[i]&0xff);
        fflush(stderr);
        exit(1);
      }
    }
  }

  if (do_ufs) {
    close(ufs_fd);
    ufs_fd = (-1);
  }
}

int
main(
  int     argc,
  char  **argv)
{
  double accum_time, d, avg, avg_rate;
  double variance, stddev=0.0;
  double *cdf_srt;
#if NASD_IDLE_SUPPORT > 0
  nasd_timespec_t idle_ts1, idle_ts2;
  double *idle_cdf_srt;
  double idle_variance, idle_stddev=0.0, avg_idle;
  double id1, id2, idle_accum, id, idle_pcg;
#endif /* NASD_IDLE_SUPPORT > 0 */
  nasd_drive_param_kpdev_t kpdev_args;
  nasd_status_t nasd_status;
  nasd_rpc_status_t status;
  nasd_timespec_t diff;
  nasd_timer_t timer;
  char c, *buf, *buf1;
  int done, i, rc;
  int sec_level=0;
  int sum;
  nasd_offset_t maxoff;
  char *master_password;
  nasd_timespec_t tm;

  progname = argv[0];
  
  bzero((char *)&cookie, sizeof(nasd_cookie_t));
  bzero(&sec_param, sizeof(nasd_security_param_t));

  rc = nasd_mem_init();
  if (rc) {
    fprintf(stderr,
      "ERROR: could not initialize NASD memory subsystem rc=0x%x (%s)\n",
      rc, nasd_error_string(rc));
    exit(1);
  }

  binding_type = NASD_BIND_DEFAULT;
  binding_args = NULL;
  binding_args_len = 0;

  cdf_srt = NULL;
#if NASD_IDLE_SUPPORT > 0
  idle_cdf_srt = NULL;
#endif /* NASD_IDLE_SUPPORT > 0 */

  while (nasd_getopt(argc, argv, "a:b:cefiklMn:No:p:t:v:ws:TC:I:D:uFP", &c)) {
    switch(c) {
      case 'a':
        rc = nasd_str_to_nasd_id(nasd_optarg, &nasdid);
        if (rc) {
          fprintf(stderr, "ERROR: \"%s\" is not a valid NASD identifier\n",
            nasd_optarg);
          usage();
        }
        break;
      case 'b':
        if (sscanf(nasd_optarg, "%d", &blocksize) != 1) {
          usage();
        }
        break;
      case 'u':
        do_ufs = 1;
        break;
      case 'e':
        eject_obj = 1;
        break;
      case 'C':
        cdf_name = nasd_optarg;
        break;
#if NASD_IDLE_SUPPORT > 0
      case 'I':
        idle_cdf_name = nasd_optarg;
        break;
#endif /* NASD_IDLE_SUPPORT > 0 */
      case 'D':
        data_file_name = nasd_optarg;
        break;
      case 'P':
        skip_preop = 1;
        break;
      case 's':
        if (sscanf(nasd_optarg, "%d", &sec_level) != 1) {
          usage();
        }
        break;
      case 'c':
        do_compare = 1;
        break;
      case 'k':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_KPDEV_DEFAULT;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'M':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_MSGQ;
        break;
      case 'f':
        flush_obj = 1;
        break;
      case 'F':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_KPDEV_DEFAULT_FAKE_RW;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'l':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_COLOCATE;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'n':
        if (sscanf(nasd_optarg, "%d", &niters) != 1) {
          usage();
        }
        break;
      case 'N':
        suppress_noop = 1;
        break;
      case 'o':
        if (sscanf(nasd_optarg, "%"NASD_64s_FMT, &g_offset) != 1) {
          usage();
        }
        break;
      case 'p':
        if (sscanf(nasd_optarg, "%d", &partnum) != 1) {
          usage();
        }
        break;
      case 't':
        if (sscanf(nasd_optarg, "%"NASD_64u_FMT, &bms_targ) != 1) {
          usage();
        }
        if (bms_targ == 0)
          usage();
        break;
      case 'v':
        bit_pattern = (unsigned char)nasd_optarg[0];
        break;
      case 'w':
        do_write = 1;
        break;
      case 'i':
        increment_offset=1;
        break;
#if NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE
      case 'T':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_DCE_DIRECT_TCP;
        break;
#endif /* NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE */
      default:
        usage();
    }
  }

  if (nasd_optind >= argc)
    usage();
  server_name = argv[nasd_optind];
  nasd_optind++;

  if (nasd_optind >= argc)
    usage();
  master_password=argv[nasd_optind];
  nasd_optind++;

  nasd_sec_password_to_keys(master_password, partnum, &keys);

  if (nasd_optind < argc)
    usage();

  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if(rc) {
    fprintf(stderr, "Unknown security level %d\n", sec_level);
    usage();
  }
  printf("Security Level %d   protection 0x%x\n", sec_level, protection);

  if (blocksize < 1) {
    fprintf(stderr, "Bad blocksize %d\n", blocksize);
    usage();
  }
  if (niters < 1) {
    fprintf(stderr, "Bad niters %d\n", niters);
    usage();
  }

#if NASD_IDLE_SUPPORT > 0
  if (idle_cdf_name) {
    idle_cdf_file = fopen(idle_cdf_name, "w");
    if (idle_cdf_file == NULL) {
      fprintf(stderr, "ERROR: could not open %s\n", idle_cdf_name);
      fflush(stderr);
      exit(1);
    }
    printf("Writing idle cdf to %s\n", idle_cdf_name);
    NASD_Malloc(idle_cdf_srt, niters*sizeof(double), (double *));
    if (idle_cdf_srt == NULL) {
      fprintf(stderr, "ERROR: Cannot allocate buffer for idle cdf file\n");
      fflush(stderr);
      exit(1);
    }
  }
  else {
    idle_cdf_srt = NULL;
  }
#endif /* NASD_IDLE_SUPPORT > 0 */
 
  if (cdf_name) {
    cdf_file = fopen(cdf_name, "w");
    if (cdf_file == NULL) {
      fprintf(stderr, "ERROR: could not open %s\n", cdf_name);
      fflush(stderr);
      exit(1);
    }
    printf("Writing cdf to %s\n", cdf_name);
    NASD_Malloc(cdf_srt, niters*sizeof(double), (double *));
    if (cdf_srt == NULL) {
      fprintf(stderr, "ERROR: Cannot allocate buffer for cdf file\n");
      fflush(stderr);
      exit(1);
    }
  }
  else {
    cdf_srt = NULL;
  }

  if (data_file_name) {
    data_file = fopen(data_file_name, "w");
    if (data_file == NULL) {
      fprintf(stderr, "ERROR: could not open %s\n", data_file_name);
      fflush(stderr);
      exit(1);
    }
    printf("Writing data to %s\n", data_file_name);
  }

  printf("Using offset %"NASD_64s_FMT"\n", g_offset);
 
  printf("Using bit pattern 0x%x\n", bit_pattern&0xff);

  NASD_Malloc(iters, niters*sizeof(iter_info_t), (iter_info_t *));
  if (iters == NULL) {
    fprintf(stderr, "ERROR: unable to allocate iteration info array\n");
    fflush(stderr);
    exit(1);
  } 

  NASD_Malloc(buf, blocksize, (char *));
  if (buf == NULL) {
    fprintf(stderr, "ERROR: unable to allocate buffer\n");
    fflush(stderr);
    exit(1);
  }
  NASD_Malloc(buf1, blocksize, (char *));
  if (buf1 == NULL) {
    fprintf(stderr, "ERROR: unable to allocate buffer-1\n");
    fflush(stderr);
    exit(1);
  }
  for(i=0;i<blocksize;i++) {
    buf[i] = buf1[i] = (char)bit_pattern;
  }
  gbuf = buf1;

  if (do_ufs == 0) {
    rc = nasd_cl_p_init();
    if (rc) {
      printf("ERROR (%s:%d): cannot init client library, rc=0x%x (%s)\n",
        __FILE__, __LINE__, rc, nasd_error_string(rc));
      return(rc);
    }
  }
  else {
    rc = nasd_threads_init();
    if (rc) {
      printf("ERROR (%s:%d): cannot init client library, rc=0x%x (%s)\n",
        __FILE__, __LINE__, rc, nasd_error_string(rc));
      return(rc);
    }
  }

  maxoff=g_offset+(1+niters)*blocksize;

  if (do_ufs == 0) {

    rc = nasd_bind_to_drive(server_name, NASD_PDRIVE_PORT,
                            binding_type, binding_args, binding_args_len, &h);
    if (rc) {
      fprintf(stderr, "ERROR: cannot bind to server %s\n", server_name);
      fflush(stderr);
      exit(1);
    }

    if (suppress_noop == 0) {
      nasd_cl_p_null_dr(h, &nasd_status, &status);
      if (status || nasd_status) {
        fprintf(stderr, "ERROR from drive noop, status=0x%x (%s) nasd_status=0x%x (%s)\n",
                status, CL_ERROR_STRING(h, status, error_text),
                nasd_status, nasd_error_string(nasd_status));
        exit(1);
      }
    }

    if (nasdid == NASD_ID_NULL) {
      nasdid = get_root_object();
    }

  }
  else {
    /* ufs */
    h = NULL;
    ufs_fd = (-1);
  }

  /* Fill in capability and security param */
  nasd_drive_handle_get_time(h, &tm);
  tm.ts_sec+=(60*60);
  nasd_sec_build_capability(partnum, nasdid,
                            (NASD_ACCESS_RIGHTS_READ |
                             NASD_ACCESS_RIGHTS_WRITE |
                             NASD_ACCESS_RIGHTS_FLUSH |
                             NASD_ACCESS_RIGHTS_EJECT),
                            0, tm.ts_sec,
                            protection,
                            NASD_RED_CAPABILITY,
                            0, maxoff,
                            0, keys.red_key,
                            &cookie);
  sec_param.type = NASD_RED_CAPABILITY;
  sec_param.partnum = partnum;
  sec_param.actual_protection = protection;

  if (skip_preop == 0) {
    perform_io(nasdid, buf, blocksize, &nasd_status, &status);
    if (status || nasd_status) {
      fprintf(stderr, "ERROR from drive pre-op, status=0x%x (%s) nasd_status=0x%x (%s)\n",
        status, CL_ERROR_STRING(h, status, error_text),
        nasd_status, nasd_error_string(nasd_status));
      exit(1);
    }
  }
  done = 0;
  accum_time = (double)0.0;
#if NASD_IDLE_SUPPORT > 0
  idle_accum = (double)0.0;
#endif /* NASD_IDLE_SUPPORT > 0 */
  for(i=0;i<niters;i++) {
    if (eject_obj) {
      doeject(nasdid, &nasd_status, &status);
      if (nasd_status || status) {
        fprintf(stderr,
          "ERROR from object eject (i=%d) nasd_status=0x%x (%s) status=0x%x (%s)\n",
          i, nasd_status, nasd_error_string(nasd_status), status,
          CL_ERROR_STRING(h, status, error_text));
        fflush(stderr);
        exit(1);
      }
    }
    if (increment_offset) {
      g_offset += blocksize;
    }
#if NASD_IDLE_SUPPORT > 0
    nasd_status = nasd_get_total_idle_time(&idle_ts1);
    if (nasd_status) {
      fprintf(stderr, "ERROR: got 0x%x (%s) from nasd_get_total_idle_time\n",
        nasd_status, nasd_error_string(nasd_status));
      exit(1);
    }
#endif /* NASD_IDLE_SUPPORT > 0 */
    NASD_TM_START(&timer);
    perform_io(nasdid, buf, blocksize, &nasd_status, &status);
    if (status || nasd_status) {
      fprintf(stderr, "ERROR from drive op, offset=%" NASD_64u_FMT
        " status=0x%x (%s) nasd_status=0x%x (%s)\n",
        g_offset, status, CL_ERROR_STRING(h, status, error_text),
        nasd_status, nasd_error_string(nasd_status));
      exit(1);
    }
    NASD_TM_STOP(&timer);
#if NASD_IDLE_SUPPORT > 0
    nasd_status = nasd_get_total_idle_time(&idle_ts2);
    if (nasd_status) {
      fprintf(stderr, "ERROR: got 0x%x (%s) from nasd_get_total_idle_time\n",
        nasd_status, nasd_error_string(nasd_status));
      exit(1);
    }
#endif /* NASD_IDLE_SUPPORT > 0 */
    NASD_TM_ELAPSED_TS(&timer, &diff);
    done++;
    d = (double)diff.ts_nsec;
    d /= (double)1000000000.0;
    d += (double)diff.ts_sec;

#if NASD_IDLE_SUPPORT > 0
    id1 = (double)idle_ts1.ts_nsec;
    id1 /= (double)1000000000.0;
    id1 += (double)idle_ts1.ts_sec;
    id2 = (double)idle_ts2.ts_nsec;
    id2 /= (double)1000000000.0;
    id2 += (double)idle_ts2.ts_sec;
    id = id2 - id1;
#endif /* NASD_IDLE_SUPPORT > 0 */

    accum_time += d;
#if NASD_IDLE_SUPPORT > 0
    idle_accum += id;
#endif /* NASD_IDLE_SUPPORT > 0 */
    iters[i].tm = d;
#if NASD_IDLE_SUPPORT > 0
    iters[i].idle = id;
#endif /* NASD_IDLE_SUPPORT > 0 */

    if (flush_obj) {
      doflush(nasdid, &nasd_status, &status);
      if (nasd_status || status) {
        fprintf(stderr,
          "ERROR from object flush (i=%d) nasd_status=0x%x (%s) status=0x%x (%s)\n",
          i, nasd_status, nasd_error_string(nasd_status), status,
          CL_ERROR_STRING(h, status, error_text));
        fflush(stderr);
        exit(1);
      }
    }
  }

  printf("%f seconds to do %d ops\n", accum_time, done);
  NASD_ASSERT(done == niters);
  d = (double)done;
  avg = accum_time / d;
#if NASD_IDLE_SUPPORT > 0
  avg_idle = idle_accum / d;
  printf("%f seconds average (%f idle)\n", avg, avg_idle);
#else /* NASD_IDLE_SUPPORT > 0 */
  printf("%f seconds average\n", avg);
#endif /* NASD_IDLE_SUPPORT > 0 */
  printf("%f ops / sec\n", (d/accum_time));
  printf("blocksize = %d\n", blocksize);
  avg_rate = (double)blocksize;
  avg_rate *= (double)done;
  avg_rate /= (double)1024.0;
  avg_rate /= accum_time;
  printf("avg rate = %.2f kbytes/sec (%.2f 8k-chunks/sec)\n",
    avg_rate, avg_rate/((double)8.0));
#if NASD_IDLE_SUPPORT > 0
  idle_pcg = idle_accum/accum_time;
  idle_pcg *= (double)100.0;
  printf("idle %f of %f elapsed (%.2f%%)\n", idle_accum, accum_time,
    idle_pcg);
#endif /* NASD_IDLE_SUPPORT > 0 */

  variance = (double)0.0;
#if NASD_IDLE_SUPPORT > 0
  idle_variance = (double)0.0;
#endif /* NASD_IDLE_SUPPORT > 0 */
  if (niters > 1) {
    d = (double)(niters - 1);
    for(i=0;i<niters;i++) {
      variance += (iters[i].tm - avg) * (iters[i].tm - avg) / d;
#if NASD_IDLE_SUPPORT > 0
      idle_variance += (iters[i].idle - avg_idle) * (iters[i].idle - avg_idle) / d;
#endif /* NASD_IDLE_SUPPORT > 0 */
      if (cdf_srt)
        cdf_srt[i] = iters[i].tm;
#if NASD_IDLE_SUPPORT > 0
      if (idle_cdf_srt)
        idle_cdf_srt[i] = iters[i].idle;
#endif /* NASD_IDLE_SUPPORT > 0 */
    }
    stddev = sqrt(variance);
#if NASD_IDLE_SUPPORT > 0
    idle_stddev = sqrt(idle_variance);
#endif /* NASD_IDLE_SUPPORT > 0 */
  }
  else {
    if (cdf_srt)
      cdf_srt[0] = iters[0].tm;
#if NASD_IDLE_SUPPORT > 0
    if (idle_cdf_srt)
      idle_cdf_srt[0] = iters[0].idle;
#endif /* NASD_IDLE_SUPPORT > 0 */
    variance = (double)0.0;
#if NASD_IDLE_SUPPORT > 0
    idle_variance = (double)0.0;
#endif /* NASD_IDLE_SUPPORT > 0 */
  }
  printf("standard deviation: %f   variance: %.9f\n", stddev, variance);
#if NASD_IDLE_SUPPORT > 0
  printf("idle standard deviation: %f   variance: %.9f\n", idle_stddev, idle_variance);
#endif /* NASD_IDLE_SUPPORT > 0 */

  if (cdf_srt && (niters > 1))
    qsort((void *)cdf_srt, niters, sizeof(double), double_cmp);
#if NASD_IDLE_SUPPORT > 0
  if (idle_cdf_srt && (niters > 1))
    qsort((void *)idle_cdf_srt, niters, sizeof(double), double_cmp);
#endif /* NASD_IDLE_SUPPORT > 0 */

  if (cdf_file) {
    sum = 0;
    for(i=0;i<niters;i++) {
      sum++;
      fprintf(cdf_file, "%f %d.00\n", cdf_srt[i], sum);
    }
    fflush(cdf_file);
    fclose(cdf_file);
  }

#if NASD_IDLE_SUPPORT > 0
  if (idle_cdf_file) {
    sum = 0;
    for(i=0;i<niters;i++) {
      sum++;
      fprintf(idle_cdf_file, "%f %d.00\n", idle_cdf_srt[i], sum);
    }
    fflush(idle_cdf_file);
    fclose(idle_cdf_file);
  }
#endif /* NASD_IDLE_SUPPORT > 0 */

  if (data_file) {
    for(i=0;i<niters;i++) {
#if NASD_IDLE_SUPPORT > 0
      /*
       * iter time idle
       */
      fprintf(data_file, "%d %.9f %.9f\n", i, iters[i].tm, iters[i].idle);
#else /* NASD_IDLE_SUPPORT > 0 */
      /*
       * iter time
       */
      fprintf(data_file, "%d %.9f\n", i, iters[i].tm);
#endif /* NASD_IDLE_SUPPORT > 0 */
    }
    fflush(data_file);
    fclose(data_file);
  }

  if (do_ufs == 0) {
    rc = nasd_unbind_drive(&h);
    if (rc) {
      fprintf(stderr, "ERROR: got 0x%x (%s) unbinding drive\n",
        rc, nasd_error_string(rc));
      exit(1);
    }
    nasd_cl_p_shutdown();
  }
  else {
    nasd_threads_shutdown();
  }

#if NASD_IDLE_SUPPORT > 0
  if (idle_cdf_srt) {
    NASD_Free(idle_cdf_srt, niters*sizeof(double));
  }
#endif /* NASD_IDLE_SUPPORT > 0 */
  if (cdf_srt) {
    NASD_Free(cdf_srt, niters*sizeof(double));
  }
  NASD_Free(iters, niters*sizeof(iter_info_t));
  NASD_Free(buf, blocksize);
  NASD_Free(buf1, blocksize);

  nasd_mem_shutdown();

  exit(0);
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
