/*
 * 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 */
