Imported Upstream version 3.1.0
[debian/amanda] / ndmp-src / ndma_ctst_tape.c
diff --git a/ndmp-src/ndma_ctst_tape.c b/ndmp-src/ndma_ctst_tape.c
new file mode 100644 (file)
index 0000000..592fa44
--- /dev/null
@@ -0,0 +1,1262 @@
+/*
+ * 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"
+
+
+#ifndef NDMOS_OPTION_NO_CONTROL_AGENT
+
+
+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, u_long file_num, u_long 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, u_long count, u_long *resid);
+extern int     ndmca_check_tape_mtio (struct ndm_session *sess,
+                       ndmp9_error expect_err,
+                       ndmp9_tape_mtio_op op, u_long count, u_long 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];
+               sprintf (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;
+
+               sprintf (note, "Write tape file %d", fileno+1);
+               ndmca_test_open (sess, note, 0);
+
+               sprintf (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 */
+               sprintf (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:
+       sprintf (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;
+
+               sprintf (note, "Read tape file %d", fileno+1);
+               ndmca_test_open (sess, note, 0);
+
+               sprintf (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 0
+                       if (bcmp (buf, pbuf, recsize) != 0)
+                               goto fail;
+#else
+                       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];
+                                       sprintf (tmp,
+                                                "%d: 0x%x => 0x%x",
+                                                i, *expect_p, *got_p);
+                                       ndmalogf (sess, "DATA", 6, tmp);
+                                       f++;
+                                   }
+                               }
+                               goto fail;
+                       }
+#endif
+               }
+
+               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);
+               }
+
+               sprintf (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:
+       sprintf (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;
+       u_long          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;
+
+               sprintf (note, "Seek around tape file %d", fileno+1);
+               ndmca_test_open (sess, note, 0);
+
+               sprintf (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;
+               }
+
+               sprintf (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:
+       sprintf (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, u_long file_num, u_long 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:
+       sprintf (buf, "Failed %s while testing %s", oper, what);
+       ndmca_test_log_note (sess, 1, buf);
+       if (ts) {
+               sprintf (buf, "    expect file_num=%ld got file_num=%ld",
+                       (long)file_num, (long)ts->file_num.value);
+               ndmca_test_log_note (sess, 1, buf);
+
+               sprintf (buf, "    expect blockno=%ld got blockno=%ld",
+                       (long)blockno, (long)ts->blockno.value);
+               ndmca_test_log_note (sess, 1, buf);
+       }
+
+       sprintf (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, u_long count, u_long *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, u_long count, u_long resid)
+{
+    struct ndmconn *   conn = sess->plumb.tape;
+    u_long 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];
+       sprintf (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 /* !NDMOS_OPTION_NO_CONTROL_AGENT */