/*
 * Copyright (c) 1998,1999,2000
 *      Traakan, Inc., Los Altos, CA
 *      All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Project:  NDMJOB
 * Ident:    $Id: $
 *
 * Description:
 *
 *****************************************************************
 *
 * NDMP Elements of a test-tape session
 *
 *                   +-----+     ###########
 *                   | Job |----># CONTROL #
 *                   +-----+     #  Agent  #
 *                               #         #
 *                               ###########
 *                                    |  |
 *                                    |  +---------------------+
 *                            control | connections            |
 *                                    V                        V
 *                               ############  +-------+   #########
 *                               #  TAPE    #  |       |   # ROBOT #
 *                               #  Agent   #  | ROBOT |<-># Agent #
 *                               #          #  |+-----+|   #       #
 *                               #        ======|DRIVE||   #       #
 *                               #          #  |+-----+|   #       #
 *                               ############  +-------+   #########
 *
 ****************************************************************
 */


#include "ndmagents.h"


#if !defined(NDMOS_OPTION_NO_CONTROL_AGENT) && \
    !defined(NDMOS_OPTION_NO_TEST_AGENTS)


extern int ndmca_tt_wrapper(struct ndm_session* sess,
                            int (*func)(struct ndm_session* sess));


extern int ndmca_op_test_tape(struct ndm_session* sess);
extern int ndmca_tt_openclose(struct ndm_session* sess);
extern int ndmca_tt_basic_getstate(struct ndm_session* sess);
extern int ndmca_tt_basic_write(struct ndm_session* sess);
extern int ndmca_tt_basic_read(struct ndm_session* sess);
extern int ndmca_tt_basic_write_and_read(struct ndm_session* sess);
extern int ndmca_tt_write(struct ndm_session* sess);
extern int ndmca_tt_read(struct ndm_session* sess);
extern int ndmca_tt_mtio(struct ndm_session* sess);

extern int ndmca_tt_check_fileno_recno(struct ndm_session* sess,
                                       char* what,
                                       uint32_t file_num,
                                       uint32_t blockno,
                                       char* note);

extern int ndmca_test_tape_open(struct ndm_session* sess,
                                ndmp9_error expect_err,
                                char* device,
                                int mode);
extern int ndmca_test_tape_close(struct ndm_session* sess,
                                 ndmp9_error expect_err);
extern int ndmca_test_tape_get_state(struct ndm_session* sess,
                                     ndmp9_error expect_err);
extern int ndmca_test_tape_mtio(struct ndm_session* sess,
                                ndmp9_error expect_err,
                                ndmp9_tape_mtio_op op,
                                uint32_t count,
                                uint32_t* resid);
extern int ndmca_check_tape_mtio(struct ndm_session* sess,
                                 ndmp9_error expect_err,
                                 ndmp9_tape_mtio_op op,
                                 uint32_t count,
                                 uint32_t resid);
extern int ndmca_test_tape_write(struct ndm_session* sess,
                                 ndmp9_error expect_err,
                                 char* buf,
                                 unsigned count);
extern int ndmca_test_tape_read(struct ndm_session* sess,
                                ndmp9_error expect_err,
                                char* buf,
                                unsigned count);
extern int ndmca_test_tape_read_2cnt(struct ndm_session* sess,
                                     ndmp9_error expect_err,
                                     char* buf,
                                     unsigned count,
                                     unsigned true_count);


struct series {
  unsigned n_rec;
  unsigned recsize;
};

struct series tt_series[] = {{1, 512}, {100, 1024}, {1, 512}, {100, 139},
                             {1, 512}, {99, 10240}, {1, 512}, {3, 32768},
                             {1, 512}, {0}};


int ndmca_op_test_tape(struct ndm_session* sess)
{
  struct ndmconn* conn;
  int (*save_call)(struct ndmconn * conn, struct ndmp_xa_buf * xa);
  int rc;

  rc = ndmca_test_load_tape(sess);
  if (rc) return rc;

  conn = sess->plumb.tape;
  save_call = conn->call;
  conn->call = ndma_call_no_tattle;

  if (rc == 0) rc = ndmca_tt_wrapper(sess, ndmca_tt_openclose);
  if (rc == 0) rc = ndmca_tt_wrapper(sess, ndmca_tt_basic_getstate);
  if (rc == 0) rc = ndmca_tt_wrapper(sess, ndmca_tt_basic_write);
  if (rc == 0) rc = ndmca_tt_wrapper(sess, ndmca_tt_basic_read);
  if (rc == 0) rc = ndmca_tt_wrapper(sess, ndmca_tt_basic_write_and_read);
  if (rc == 0) rc = ndmca_tt_wrapper(sess, ndmca_tt_write);
  if (rc == 0) rc = ndmca_tt_wrapper(sess, ndmca_tt_read);
  if (rc == 0) rc = ndmca_tt_wrapper(sess, ndmca_tt_mtio);

  ndmca_test_unload_tape(sess);

  ndmca_test_done_series(sess, "test-tape");

  conn->call = save_call;

  return 0;
}

int ndmca_tt_wrapper(struct ndm_session* sess,
                     int (*func)(struct ndm_session* sess))
{
  int rc;

  rc = (*func)(sess);

  if (rc != 0) { ndmalogf(sess, "Test", 1, "Failure"); }

  ndmca_test_done_phase(sess);

  /* clean up mess */
  ndmca_test_log_note(sess, 2, "Cleaning up...");

  ndmca_tape_open(sess); /* Open the tape, OK if already opened */
  ndmca_tape_mtio(sess, NDMP9_MTIO_REW, 1, 0);
  rc = ndmca_tape_close(sess); /* close, collective error */
  if (rc != 0) {
    ndmca_test_log_note(sess, 0, "Cleaning up failed, quiting");
  } else {
    ndmca_test_log_note(sess, 2, "Cleaning up done");
  }

  return rc;
}


int ndmca_tt_openclose(struct ndm_session* sess)
{
  int rc;

  ndmca_test_phase(sess, "T-OC", "Tape Open/Close");

  rc = ndmca_test_tape_close(sess, NDMP9_DEV_NOT_OPEN_ERR);
  if (rc) return rc;

  rc = ndmca_test_tape_open(sess, NDMP9_NO_DEVICE_ERR, "bogus",
                            NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_open(sess, NDMP9_ILLEGAL_ARGS_ERR, 0, 123);
  if (rc) return rc;

  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_RDWR_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_open(sess, NDMP9_DEVICE_OPENED_ERR, 0,
                            NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  return 0; /* pass */
}

int ndmca_tt_basic_getstate(struct ndm_session* sess)
{
  int rc;

  ndmca_test_phase(sess, "T-BGS", "Tape Get State Basics");

  rc = ndmca_test_tape_get_state(sess, NDMP9_DEV_NOT_OPEN_ERR);
  if (rc) return rc;

  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_get_state(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  return 0; /* pass */
}

// Precedes tt_basic_read() so that we can make a "known" tape.
int ndmca_tt_basic_write(struct ndm_session* sess)
{
  int rc, ix;
  char buf[1024];
  ndmp9_error expect_errs[5];

  ndmca_test_phase(sess, "T-BW", "Tape Write Basics");

  rc = ndmca_test_tape_write(sess, NDMP9_DEV_NOT_OPEN_ERR, buf, 1024);
  if (rc) return rc;

  // Write w/ read-only open mode
  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_write(sess, NDMP9_PERMISSION_ERR, buf, 1024);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  // Write w/ bogus lengths
  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_RDWR_MODE);
  if (rc) return rc;

  /* OPEN Question: what does len==0 mean? */
  /* write/len=0 MUST be NDMP[234]_NO_ERR or NDMP[234]_ILLEGAL_ARGS */
  /* write/len=0 MUST be NDMP4_NO_ERR */
  ix = 0;
  if (sess->plumb.tape->protocol_version < 5) {
    expect_errs[ix++] = NDMP9_ILLEGAL_ARGS_ERR;
  }
  expect_errs[ix++] = NDMP9_NO_ERR;
  expect_errs[ix++] = -1;

  rc = ndmca_tape_write(sess, buf, 0);

  rc = ndmca_test_check_expect_errs(sess->plumb.tape, rc, expect_errs);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  // TODO: bogus length

  // Write works
  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_RDWR_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_write(sess, NDMP9_NO_ERR, buf, 1024);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_EOF, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  return 0; /* pass */
}


// Assumes tt_basic_write() passed. Uses resulting tape.

int ndmca_tt_basic_read(struct ndm_session* sess)
{
  int rc, ix;
  char buf[2048];
  ndmp9_error expect_errs[5];

  ndmca_test_phase(sess, "T-BR", "Tape Read Basics");

  rc = ndmca_test_tape_read(sess, NDMP9_DEV_NOT_OPEN_ERR, buf, 1024);
  if (rc) return rc;


  // Read w/ bogus lengths -- mode=READ_MODE
  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  /* read/len=0 MUST be NDMP[23]_NO_ERR or NDMP[23]_ILLEGAL_ARGS */
  /* read/len=0 MUST be NDMP4_NO_ERR */
  ix = 0;
  if (sess->plumb.tape->protocol_version < 4) {
    expect_errs[ix++] = NDMP9_ILLEGAL_ARGS_ERR;
  }
  expect_errs[ix++] = NDMP9_NO_ERR;
  expect_errs[ix++] = -1;

  rc = ndmca_tape_read(sess, buf, 0);

  rc = ndmca_test_check_expect_errs(sess->plumb.tape, rc, expect_errs);
  if (rc) return rc;

  rc = ndmca_test_tape_read(sess, NDMP9_ILLEGAL_ARGS_ERR, buf, 0x80000000);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  // Read works -- mode=WRITE_MODE (just to mix it up)
  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_RDWR_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_read(sess, NDMP9_NO_ERR, buf, 1024);
  if (rc) return rc;

  rc = ndmca_test_tape_read(sess, NDMP9_EOF_ERR, buf, 1024);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;


  /*
   * Read works w/ oversize -- mode=READ_MODE (just to mix it up)
   */
  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_read_2cnt(sess, NDMP9_NO_ERR, buf, 2048, 1024);
  if (rc) return rc;

  rc = ndmca_test_tape_read_2cnt(sess, NDMP9_EOF_ERR, buf, 2048, 1024);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;


  /*
   * Read works w/ undersize -- mode=READ_MODE (just to mix it up)
   */
  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_read_2cnt(sess, NDMP9_NO_ERR, buf, 512, 512);
  if (rc) return rc;

  rc = ndmca_test_tape_read_2cnt(sess, NDMP9_EOF_ERR, buf, 512, 512);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  return 0; /* pass */
}

#define CHECK_FILENO_RECNO(WHAT, FILENO, RECNO)                        \
  {                                                                    \
    what = WHAT;                                                       \
    rc = ndmca_tt_check_fileno_recno(sess, WHAT, FILENO, RECNO, note); \
    if (rc) return -1;                                                 \
  }

/*
 * Assumes tt_basic_read() and tt_basic_write() have been done verifying
 * READ and WRITE operations work...
 */
int ndmca_tt_basic_write_and_read(struct ndm_session* sess)
{
  int rc, i, f, pass;
  char buf[64 * 1024];
  char* p;

  ndmca_test_phase(sess, "T-BWR", "Tape Write and Read Basics");

  /*
   * check EOF and EOM by rewinding and putting on 1 EOF mark
   */
  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_RDWR_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_check_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_BSR, 100, 100);
  if (rc) return rc;

  rc = ndmca_check_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_BSF, 100, 100);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_EOF, 1, 0);
  if (rc) return rc;

  rc = ndmca_check_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_BSF, 100, 99);
  if (rc) return rc;

  rc = ndmca_check_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_FSF, 100, 99);
  if (rc) return rc;

  /* we are at EOM */
  if (sess->plumb.tape->protocol_version < 4) {
    rc = ndmca_test_tape_read(sess, NDMP9_EOF_ERR, buf, sizeof(buf));
    if (rc) return rc;

    /* check it again */
    rc = ndmca_test_tape_read(sess, NDMP9_EOF_ERR, buf, 1024);
    if (rc) return rc;

  } else {
    rc = ndmca_test_tape_read(sess, NDMP9_EOM_ERR, buf, sizeof(buf));
    if (rc) return rc;

    /* check it again */
    rc = ndmca_test_tape_read(sess, NDMP9_EOM_ERR, buf, 1024);
    if (rc) return rc;
  }

  /* rewind and place 1 record in tape -- no EOF marker by seeking */

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_write(sess, NDMP9_NO_ERR, buf, 512);
  if (rc) return rc;

  rc = ndmca_check_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_BSR, 100, 99);
  if (rc) return rc;

  rc = ndmca_check_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_FSR, 100, 99);
  if (rc) return rc;

  rc = ndmca_check_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_FSR, 100, 100);
  if (rc) return rc;

  rc = ndmca_check_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_FSF, 100, 100);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  /*
   * perform tape label type processing with positioning ops
   */
  for (pass = 0; pass < 2; pass++) {
    /*
     * open the tape and write 1 record and close it
     */
    rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_RDWR_MODE);
    if (rc) return rc;

    rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
    if (rc) return rc;

    for (p = buf, i = 0; i < 1024; i++, p++) *p = ((i - 4) & 0xff);

    rc = ndmca_test_tape_write(sess, NDMP9_NO_ERR, buf, 1024);
    if (rc) return rc;

    rc = ndmca_tape_mtio(sess, NDMP9_MTIO_EOF, 1, 0);
    if (rc) return rc;

    rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
    if (rc) return rc;

    /*
     * open the tape and read it
     */
    rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_RDWR_MODE);
    if (rc) return rc;

    rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
    if (rc) return rc;

    if (pass == 1)
      rc =
          ndmca_test_tape_read_2cnt(sess, NDMP9_NO_ERR, buf, sizeof(buf), 1024);
    else
      rc = ndmca_test_tape_read(sess, NDMP9_NO_ERR, buf, 1024);
    if (rc) return rc;

    for (p = buf, f = i = 0; f < 64 && i < 1024; i++, p++)
      if (*p != ((i - 4) & 0xff)) {
        char tmp[80];
        snprintf(tmp, sizeof(tmp), "%d: 0x%x => 0x%x", i, ((i - 4) & 0xff), *p);
        ndmalogf(sess, "DATA", 6, tmp);
        f++;
      }
    if (f > 0) {
      ndmca_test_fail(sess, "Failed compare");
      return -1;
    }

    rc = ndmca_test_tape_read(sess, NDMP9_EOF_ERR, buf, 1024);
    if (rc) return rc;

    /* check EOM */
    if (sess->plumb.tape->protocol_version < 4) {
      rc = ndmca_test_tape_read(sess, NDMP9_EOF_ERR, buf, 1024);
      if (rc) return rc;
    } else {
      /* skip over filemark */
      rc = ndmca_tape_mtio(sess, NDMP9_MTIO_FSF, 1, 0);
      /* read EOM */
      rc = ndmca_test_tape_read(sess, NDMP9_EOM_ERR, buf, 1024);
      if (rc) return rc;
    }

    rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
    if (rc) return rc;
  }

  return 0; /* pass */
}

/*
 * Precedes tt_read() so that we can make a "known" tape.
 */
int ndmca_tt_write(struct ndm_session* sess)
{
  int rc;
  unsigned n_rec;
  unsigned recsize;
  unsigned fileno, recno;
  char* what;
  char note[128];
  char buf[64 * 1024];

  ndmca_test_phase(sess, "T-WRITE", "Tape Write Series");

  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_RDWR_MODE);
  if (rc) return rc;

  for (fileno = 0; tt_series[fileno].n_rec > 0; fileno++) {
    n_rec = tt_series[fileno].n_rec;
    recsize = tt_series[fileno].recsize;

    snprintf(note, sizeof(note), "Write tape file %d", fileno + 1);
    ndmca_test_open(sess, note, 0);

    snprintf(note, sizeof(note), "file #%d, %d records, %d bytes/rec",
             fileno + 1, n_rec, recsize);
    ndmca_test_log_note(sess, 2, note);

    for (recno = 0; recno < n_rec; recno++) {
      ndmca_test_fill_data(buf, recsize, recno, fileno);

      what = "write";
      rc = ndmca_tape_write(sess, buf, recsize);
      if (rc) goto fail;

      CHECK_FILENO_RECNO("write", fileno, recno + 1);
    }

    what = "write filemark";
    rc = ndmca_tape_mtio(sess, NDMP9_MTIO_EOF, 1, 0);
    if (rc) goto fail;

    CHECK_FILENO_RECNO("wfm", fileno + 1, 0);

    /* no test calls so the file operation is the test */
    snprintf(buf, sizeof(buf), "Passed tape write %s", note);
    ndmca_test_log_step(sess, 2, buf);
  }

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  return 0;

fail:
  snprintf(buf, sizeof(buf), "Failed %s recno=%d; %s", what, recno, note);
  ndmca_test_fail(sess, buf);
  return -1;
}


/*
 * Assumes tt_write() passed
 */
int ndmca_tt_read(struct ndm_session* sess)
{
  int rc;
  unsigned n_rec;
  unsigned recsize;
  unsigned fileno, recno;
  char* what;
  char note[128];
  char pbuf[64 * 1024];
  char buf[64 * 1024];

  ndmca_test_phase(sess, "T-READ", "Tape Read Series");

  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  for (fileno = 0; tt_series[fileno].n_rec > 0; fileno++) {
    n_rec = tt_series[fileno].n_rec;
    recsize = tt_series[fileno].recsize;

    snprintf(note, sizeof(note), "Read tape file %d", fileno + 1);
    ndmca_test_open(sess, note, 0);

    snprintf(note, sizeof(note), "file #%d, %d records, %d bytes/rec",
             fileno + 1, n_rec, recsize);
    ndmca_test_log_note(sess, 2, note);

    for (recno = 0; recno < n_rec; recno++) {
      ndmca_test_fill_data(pbuf, recsize, recno, fileno);

      what = "read";
      rc = ndmca_tape_read(sess, buf, recsize);
      if (rc) goto fail;

      CHECK_FILENO_RECNO("read", fileno, recno + 1);

      what = "compare";
      if (bcmp(buf, pbuf, recsize) != 0) {
        unsigned char* expect_p = (unsigned char*)pbuf;
        unsigned char* got_p = (unsigned char*)buf;
        unsigned int i, f;
        for (f = i = 0; f < 64 && i < recsize; i++, expect_p++, got_p++) {
          if (*expect_p != *got_p) {
            char tmp[80];
            snprintf(tmp, sizeof(tmp), "%d: 0x%x => 0x%x", i, *expect_p,
                     *got_p);
            ndmalogf(sess, "DATA", 6, tmp);
            f++;
          }
        }
        goto fail;
      }
    }

    what = "eof read";
    rc = ndmca_test_tape_read(sess, NDMP9_EOF_ERR, buf, recsize);
    if (rc) goto fail;

    if (sess->plumb.tape->protocol_version > 3) {
      CHECK_FILENO_RECNO("eof", fileno, -1);

      what = "skip filemark";
      rc = ndmca_tape_mtio(sess, NDMP9_MTIO_FSF, 1, 0);
      if (rc) goto fail;

      CHECK_FILENO_RECNO("skip", fileno + 1, 0);
    } else {
      CHECK_FILENO_RECNO("eof", fileno + 1, 0);
    }

    snprintf(buf, sizeof(buf), "Passed tape read %s", note);
    ndmca_test_log_step(sess, 2, buf);
  }

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  return 0;

fail:
  snprintf(buf, sizeof(buf), "Failed %s recno=%d; %s", what, recno, note);
  ndmca_test_fail(sess, buf);
  return -1;
}


/*
 * Assumes tt_write() passed
 */
int ndmca_tt_mtio(struct ndm_session* sess)
{
  int rc;
  unsigned n_rec;
  unsigned recsize;
  unsigned fileno, recno;
  uint32_t count, resid;
  char* what;
  char note[128];
  char pbuf[64 * 1024];
  char buf[64 * 1024];

  ndmca_test_phase(sess, "T-MTIO", "Tape MTIO");

  rc = ndmca_test_tape_open(sess, NDMP9_NO_ERR, 0, NDMP9_TAPE_READ_MODE);
  if (rc) return rc;

  rc = ndmca_test_tape_mtio(sess, NDMP9_NO_ERR, NDMP9_MTIO_REW, 1, 0);
  if (rc) return rc;

  for (fileno = 0; tt_series[fileno].n_rec > 0; fileno++) {
    n_rec = tt_series[fileno].n_rec;
    recsize = tt_series[fileno].recsize;

    snprintf(note, sizeof(note), "Seek around tape file %d", fileno + 1);
    ndmca_test_open(sess, note, 0);

    snprintf(note, sizeof(note), "file #%d, %d records, %d bytes/rec",
             fileno + 1, n_rec, recsize);
    ndmca_test_log_note(sess, 2, note);

    what = "rew";
    count = 1;
    rc = ndmca_tape_mtio(sess, NDMP9_MTIO_REW, count, &resid);
    if (rc) goto fail;

    what = "rew resid";
    if (resid != 0) goto fail;

    CHECK_FILENO_RECNO("rew", 0, 0);


    what = "fsf(n)";
    count = fileno;
    rc = ndmca_tape_mtio(sess, NDMP9_MTIO_FSF, count, &resid);
    if (rc) goto fail;

    what = "fsf(n) resid";
    if (resid != 0) goto fail;

    CHECK_FILENO_RECNO("fsf", fileno, 0);


    what = "fsr(1m)";
    count = 1000000;
    rc = ndmca_tape_mtio(sess, NDMP9_MTIO_FSR, count, &resid);
    if (rc) goto fail;

    what = "fsr(1m) resid";
    if (n_rec + resid != count) goto fail;

    if (sess->plumb.tape->protocol_version < 4) {
      CHECK_FILENO_RECNO("fsr(1m)", fileno + 1, 0);

      what = "bsf 1 after fsr(1m)";
      count = 1;
      rc = ndmca_tape_mtio(sess, NDMP9_MTIO_BSF, count, 0);
      if (rc) goto fail;

      CHECK_FILENO_RECNO(what, fileno, -1);

      recno = n_rec;
    } else {
      /* EOT side of EOF marker */
      recno = n_rec;
      CHECK_FILENO_RECNO("fsr(1m)", fileno, recno);
    }

    what = "bsr(1m)";
    count = 1000000;
    rc = ndmca_tape_mtio(sess, NDMP9_MTIO_BSR, count, &resid);
    if (rc) goto fail;

    what = "bsr(1m) resid";
    if (n_rec + resid != count) goto fail;

    if ((fileno > 0) && (sess->plumb.tape->protocol_version < 4)) {
      /* at BOT side of EOF marker (not BOT) */
      CHECK_FILENO_RECNO("bsr(1m)", fileno - 1, -1);

      what = "fsf 1 after bsr(1m)";
      count = 1;
      rc = ndmca_tape_mtio(sess, NDMP9_MTIO_FSF, count, 0);
      if (rc) goto fail;
    }

    recno = 0;
    CHECK_FILENO_RECNO("bsr(1m)", fileno, recno);

    what = "fsr(0)";
    count = 0;
    rc = ndmca_tape_mtio(sess, NDMP9_MTIO_FSR, count, &resid);
    if (rc) goto fail;

    what = "fsr(0) resid";
    if (resid != 0) goto fail;

    recno = 0;
    CHECK_FILENO_RECNO("fsr(0)", fileno, recno);


    what = "fsr(x)";
    count = n_rec / 2;
    rc = ndmca_tape_mtio(sess, NDMP9_MTIO_FSR, count, &resid);
    if (rc) goto fail;

    what = "fsr(x) resid";
    if (resid != 0) goto fail;

    recno = n_rec / 2;
    CHECK_FILENO_RECNO("fsr(x)", fileno, recno);

    what = "fsr(x) read";
    rc = ndmca_tape_read(sess, buf, recsize);
    if (rc) goto fail;

    what = "fsr(x) compare";
    ndmca_test_fill_data(pbuf, recsize, recno, fileno);
    if (bcmp(buf, pbuf, recsize) != 0) goto fail;

    recno++; /* caused by tape_read */

    if (recno > 1) {
      what = "bsr(2)";
      count = 2;
      rc = ndmca_tape_mtio(sess, NDMP9_MTIO_BSR, count, &resid);
      if (rc) goto fail;

      what = "bsr(2) resid";
      if (resid != 0) goto fail;

      recno -= count;
      CHECK_FILENO_RECNO("bsr(2)", fileno, recno);

      what = "bsr(2) read";
      rc = ndmca_tape_read(sess, buf, recsize);
      if (rc) goto fail;

      what = "bsr(2) compare";
      ndmca_test_fill_data(pbuf, recsize, recno, fileno);
      if (bcmp(buf, pbuf, recsize) != 0) goto fail;
    }

    snprintf(buf, sizeof(buf), "Passed %s", note);
    ndmca_test_log_step(sess, 2, buf);
  }

  rc = ndmca_test_tape_close(sess, NDMP9_NO_ERR);
  if (rc) return rc;

  return 0;

fail:
  snprintf(buf, sizeof(buf), "Failed %s: %s", what, note);
  ndmca_test_fail(sess, buf);
  return -1;
}


/*
 * Check the tape_state accurately reflects position
 */

int ndmca_tt_check_fileno_recno(struct ndm_session* sess,
                                char* what,
                                uint32_t file_num,
                                uint32_t blockno,
                                char* note)
{
  struct ndm_control_agent* ca = sess->control_acb;
  struct ndmp9_tape_get_state_reply* ts = 0;
  char buf[100];
  int rc;
  char* oper;

  oper = "get_state";
  rc = ndmca_tape_get_state(sess);
  if (rc) goto fail;

  ts = &ca->tape_state;

  oper = "check file_num";
  if (ts->file_num.value != file_num) goto fail;

  oper = "check blockno";
  if ((ts->blockno.value != blockno) &&
      (ts->blockno.value != NDMP9_INVALID_U_LONG))
    goto fail;

  return 0;

fail:
  snprintf(buf, sizeof(buf), "Failed %s while testing %s", oper, what);
  ndmca_test_log_note(sess, 1, buf);
  if (ts) {
    snprintf(buf, sizeof(buf), "    expect file_num=%ld got file_num=%ld",
             (long)file_num, (long)ts->file_num.value);
    ndmca_test_log_note(sess, 1, buf);

    snprintf(buf, sizeof(buf), "    expect blockno=%ld got blockno=%ld",
             (long)blockno, (long)ts->blockno.value);
    ndmca_test_log_note(sess, 1, buf);
  }

  snprintf(buf, sizeof(buf), "    note: %s", note);
  ndmca_test_fail(sess, buf);
  return -1;
}


#define NDMTEST_CALL(CONN) ndmca_test_call(CONN, xa, expect_err);


int ndmca_test_tape_open(struct ndm_session* sess,
                         ndmp9_error expect_err,
                         char* device,
                         int mode)
{
  struct ndmconn* conn = sess->plumb.tape;
  struct ndm_control_agent* ca = sess->control_acb;
  int rc;

  /* close previous test if there is one */
  ndmca_test_close(sess);

  switch (conn->protocol_version) {
    default:
      return -1234;

#ifndef NDMOS_OPTION_NO_NDMP2
    case NDMP2VER:
      NDMC_WITH(ndmp2_tape_open, NDMP2VER)
      if (device)
        request->device.name = device;
      else
        request->device.name = ca->job.tape_device;
      if (mode != -1)
        request->mode = mode;
      else
        request->mode = ca->tape_mode;
      rc = NDMTEST_CALL(conn);
      NDMC_ENDWITH
      break;
#endif /* !NDMOS_OPTION_NO_NDMP2 */
#ifndef NDMOS_OPTION_NO_NDMP3
    case NDMP3VER:
      NDMC_WITH(ndmp3_tape_open, NDMP3VER)
      if (device)
        request->device = device;
      else
        request->device = ca->job.tape_device;
      if (mode != -1)
        request->mode = mode;
      else
        request->mode = ca->tape_mode;
      rc = NDMTEST_CALL(conn);
      NDMC_ENDWITH
      break;
#endif /* !NDMOS_OPTION_NO_NDMP3 */
#ifndef NDMOS_OPTION_NO_NDMP4
    case NDMP4VER:
      NDMC_WITH(ndmp4_tape_open, NDMP4VER)
      if (device)
        request->device = device;
      else
        request->device = ca->job.tape_device;
      if (mode != -1)
        request->mode = mode;
      else
        request->mode = ca->tape_mode;
      rc = NDMTEST_CALL(conn);
      NDMC_ENDWITH
      break;
#endif /* !NDMOS_OPTION_NO_NDMP4 */
  }

  return rc;
}

int ndmca_test_tape_close(struct ndm_session* sess, ndmp9_error expect_err)
{
  struct ndmconn* conn = sess->plumb.tape;
  int rc;

  /* close previous test if there is one */
  ndmca_test_close(sess);

  rc = ndmca_tape_close(sess);

  rc = ndmca_test_check_expect(conn, rc, expect_err);

  return rc;
}

int ndmca_test_tape_get_state(struct ndm_session* sess, ndmp9_error expect_err)
{
  struct ndmconn* conn = sess->plumb.tape;
  int rc;

  /* close previous test if there is one */
  ndmca_test_close(sess);

  rc = ndmca_tape_get_state(sess);

  rc = ndmca_test_check_expect(conn, rc, expect_err);

  return rc;
}

int ndmca_test_tape_mtio(struct ndm_session* sess,
                         ndmp9_error expect_err,
                         ndmp9_tape_mtio_op op,
                         uint32_t count,
                         uint32_t* resid)
{
  struct ndmconn* conn = sess->plumb.tape;
  int rc;

  /* close previous test if there is one */
  ndmca_test_close(sess);

  rc = ndmca_tape_mtio(sess, op, count, resid);

  rc = ndmca_test_check_expect(conn, rc, expect_err);

  return rc;
}

int ndmca_check_tape_mtio(struct ndm_session* sess,
                          ndmp9_error expect_err,
                          ndmp9_tape_mtio_op op,
                          uint32_t count,
                          uint32_t resid)
{
  struct ndmconn* conn = sess->plumb.tape;
  uint32_t got_resid;
  int rc;

  /* close previous test if there is one */
  ndmca_test_close(sess);

  got_resid = ~resid;

  rc = ndmca_tape_mtio(sess, op, count, &got_resid);

  rc = ndmca_test_check_expect(conn, rc, expect_err);
  if (rc) return rc;

  if (resid != got_resid) {
    char tmp[128];
    snprintf(tmp, sizeof(tmp), "Residual incorrect, got %lu expected %lu",
             got_resid, resid);
    ndmca_test_fail(sess, tmp);
    return -1;
  }

  return rc;
}


int ndmca_test_tape_write(struct ndm_session* sess,
                          ndmp9_error expect_err,
                          char* buf,
                          unsigned count)
{
  struct ndmconn* conn = sess->plumb.tape;
  int rc;

  /* close previous test if there is one */
  ndmca_test_close(sess);

  rc = ndmca_tape_write(sess, buf, count);

  rc = ndmca_test_check_expect(conn, rc, expect_err);

  return rc;
}

int ndmca_test_tape_read(struct ndm_session* sess,
                         ndmp9_error expect_err,
                         char* buf,
                         unsigned count)
{
  struct ndmconn* conn = sess->plumb.tape;
  int rc;

  /* close previous test if there is one */
  ndmca_test_close(sess);

  rc = ndmca_tape_read(sess, buf, count);

  rc = ndmca_test_check_expect(conn, rc, expect_err);

  return rc;
}

int ndmca_test_tape_read_2cnt(struct ndm_session* sess,
                              ndmp9_error expect_err,
                              char* buf,
                              unsigned count,
                              unsigned true_count)
{
  struct ndmconn* conn = sess->plumb.tape;
  int rc;

  /* close previous test if there is one */
  ndmca_test_close(sess);

  switch (conn->protocol_version) {
    default:
      return -1234;

#ifndef NDMOS_OPTION_NO_NDMP2
    case NDMP2VER:
      NDMC_WITH(ndmp2_tape_read, NDMP2VER)
      request->count = count;
      rc = NDMTEST_CALL(conn);
      if (rc == 0 && expect_err == NDMP9_NO_ERR) {
        if (reply->data_in.data_in_len == true_count) {
          bcopy(reply->data_in.data_in_val, buf, true_count);
        } else {
          rc = -1;
        }
      }
      NDMC_FREE_REPLY();
      NDMC_ENDWITH
      break;
#endif /* !NDMOS_OPTION_NO_NDMP2 */
#ifndef NDMOS_OPTION_NO_NDMP3
    case NDMP3VER:
      NDMC_WITH(ndmp3_tape_read, NDMP3VER)
      request->count = count;
      rc = NDMTEST_CALL(conn);
      if (rc == 0 && expect_err == NDMP9_NO_ERR) {
        if (reply->data_in.data_in_len == true_count) {
          bcopy(reply->data_in.data_in_val, buf, true_count);
        } else {
          rc = -1;
        }
      }
      NDMC_FREE_REPLY();
      NDMC_ENDWITH
      break;
#endif /* !NDMOS_OPTION_NO_NDMP3 */
#ifndef NDMOS_OPTION_NO_NDMP4
    case NDMP4VER:
      NDMC_WITH(ndmp4_tape_read, NDMP4VER)
      request->count = count;
      rc = NDMTEST_CALL(conn);
      if (rc == 0 && expect_err == NDMP9_NO_ERR) {
        if (reply->data_in.data_in_len == true_count) {
          bcopy(reply->data_in.data_in_val, buf, true_count);
        } else {
          rc = -1;
        }
      }
      NDMC_FREE_REPLY();
      NDMC_ENDWITH
      break;
#endif /* !NDMOS_OPTION_NO_NDMP4 */
  }

  return rc;
}
#endif /* !defined(NDMOS_OPTION_NO_CONTROL_AGENT) && \
          !defined(NDMOS_OPTION_NO_TEST_AGENTS) */
