/*
 * 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:
 *	Think of ndma_dispatch_request() as a parser (like yacc(1) input).
 *	This parses (audits) the sequence of requests. If the requests
 *	conform to the "grammar", semantic actions are taken.
 *
 *	This is, admittedly, a huge source file. The idea
 *	is to have all audits and associated errors here. This
 *	makes review, study, comparing to the specification, and
 *	discussion easier because we don't get balled up in
 *	the implementation of semantics. Further, with the hope
 *	of wide-scale deployment, revisions of this source file
 *	can readily be integrated into derivative works without
 *	disturbing other portions.
 */


#include "ndmagents.h"


extern struct ndm_dispatch_version_table ndma_dispatch_version_table[];

static int		connect_open_common (struct ndm_session *sess,
				struct ndmp_xa_buf *xa,
				struct ndmconn *ref_conn,
				int protocol_version);



int
ndma_dispatch_request (struct ndm_session *sess,
  struct ndmp_xa_buf *arg_xa, struct ndmconn *ref_conn)
{
	struct ndm_dispatch_request_table *drt;
	struct ndmp_xa_buf *	xa = arg_xa;
	struct ndmp_xa_buf	xl_xa;
	struct reqrep_xlate *	rrxl = 0;
	unsigned		protocol_version = ref_conn->protocol_version;
	unsigned		msg = xa->request.header.message;
	int			rc;

	NDMOS_MACRO_ZEROFILL (&xa->reply);

	xa->reply.protocol_version = xa->request.protocol_version;
	xa->reply.flags |= NDMNMB_FLAG_NO_FREE;

	xa->reply.header.sequence = 0;		/* filled-in by xmit logic */
	xa->reply.header.time_stamp = 0;	/* filled-in by xmit logic */
	xa->reply.header.message_type = NDMP0_MESSAGE_REPLY;
	xa->reply.header.message = xa->request.header.message;
	xa->reply.header.reply_sequence = xa->request.header.sequence;
	xa->reply.header.error = NDMP0_NO_ERR;

	/* assume no error */
	ndmnmb_set_reply_error_raw (&xa->reply, NDMP0_NO_ERR);

	switch ((int)msg & 0xFFFFFF00) {
	case 0x0500:	/* notify */
	case 0x0600:	/* log */
	case 0x0700:	/* file history */
		xa->reply.flags |= NDMNMB_FLAG_NO_SEND;
		break;
	}

	/* sanity check */
	if (xa->request.protocol_version != protocol_version) {
		xa->reply.header.error = NDMP0_UNDEFINED_ERR;
		return 0;
	}

	/*
	 * If the session is not open and the message
	 * is anything other than CONNECT_OPEN, the client
	 * has implicitly agreed to the protocol_version
	 * offered by NOTIFY_CONNECTED (ref ndmconn_accept()).
	 * Effectively perform CONNECT_OPEN for that
	 * protocol_version.
	 */
	if (!sess->conn_open && msg != NDMP0_CONNECT_OPEN) {
		connect_open_common (sess, xa, ref_conn,
					ref_conn->protocol_version);
	}

	/*
	 * Give the OS/implementation specific module a chance
	 * to intercept the request. Some requests are only implemented
	 * by the module. Some requests are reimplemented by the module
	 * when the standard implementation is inadequate to the app.
	 */
	rc = ndmos_dispatch_request (sess, xa, ref_conn);
	if (rc >= 0) {
		return rc;	/* request intercepted */
	}

	/*
	 * See if there is a standard, protocol_version specific
	 * dispatch function for the request.
	 */
	drt = ndma_drt_lookup (ndma_dispatch_version_table,
					protocol_version, msg);
	if (drt) {
		goto have_drt;
	}

	/*
	 * Typical case....
	 * Find the protocol_version specific translation
	 * functions for this request/reply. The request
	 * is translated from its native version, NDMPvX,
	 * into NDMPv9. The NDMPv9 form is dispatched.
	 * The resulting reply is translated back to
	 * the native version
	 */

	rrxl = reqrep_xlate_lookup_version (reqrep_xlate_version_table,
				protocol_version);

	/* find the protocol_version translation table */
	if (!rrxl) {
		/* can't do it */
		xa->reply.header.error = NDMP0_NOT_SUPPORTED_ERR;
		return 0;
	}

	/* find the interface's translation table entry */
	rrxl = ndmp_reqrep_by_vx (rrxl, msg);
	if (!rrxl) {
		/* can't do it */
		xa->reply.header.error = NDMP0_NOT_SUPPORTED_ERR;
		return 0;
	}

	/* find the NDMPv9 dispatch table entry */
	drt = ndma_drt_lookup (ndma_dispatch_version_table, NDMP9VER,
				rrxl->v9_message);

	if (!drt) {
		/* can't do it */
		xa->reply.header.error = NDMP0_NOT_SUPPORTED_ERR;
		return 0;
	}

  have_drt:
	/*
	 * Permission checks, always.
	 */

	if (!sess->conn_open
	 && !(drt->flags & NDM_DRT_FLAG_OK_NOT_CONNECTED)) {
		xa->reply.header.error = NDMP0_PERMISSION_ERR;
		return 0;
	}

	if (!sess->conn_authorized
	 && !(drt->flags & NDM_DRT_FLAG_OK_NOT_AUTHORIZED)) {
		xa->reply.header.error = NDMP0_NOT_AUTHORIZED_ERR;
		return 0;
	}

	/*
	 * If there is a translation afoot, translate the request now.
	 */

	if (rrxl) {
		NDMOS_MACRO_ZEROFILL (&xl_xa);
		xa = &xl_xa;

		xa->request.header = arg_xa->request.header;
		xa->request.header.message = rrxl->v9_message;
		xa->request.protocol_version = NDMP9VER;

		xa->reply.header = arg_xa->reply.header;
		xa->reply.flags = arg_xa->reply.flags;
		xa->reply.protocol_version = NDMP9VER;

		rc = (*rrxl->request_xto9)(
				(void*)&arg_xa->request.body,
				(void*)&xa->request.body);

		if (rc < 0) {
			/* unrecoverable translation error */
			xa = arg_xa;
			xa->reply.header.error = NDMP0_UNDEFINED_ERR;
			return 0;
		}
		/* NB: rc>0 means that there were tolerated xlate errors */

		/* allow reply to be freed */
		xa->reply.flags &= ~NDMNMB_FLAG_NO_FREE;
	}

	rc = (*drt->dispatch_request)(sess, xa, ref_conn);

	/* free up any memory allocated as part of the xto9 request */
	if (rrxl)
	    (*rrxl->free_request_xto9)((void*)&xa->request.body);

	if (rc < 0) {
		/* unrecoverable dispatch error */
		if (rrxl) {
			ndmnmb_free (&xa->reply);	/* clean up partials */
			xa = arg_xa;
		}
		xa->reply.header.error = NDMP0_NOT_SUPPORTED_ERR;
		return 0;
	}

	if (rrxl) {
		rc = (*rrxl->reply_9tox)(
				(void*)&xa->reply.body,
				(void*)&arg_xa->reply.body);

		/* free up any memory allocated as part of the 9tox reply */
		if (rrxl)
		    (*rrxl->free_reply_9tox)((void*)&arg_xa->reply.body);

		ndmnmb_free (&xa->reply);	/* clean up */

		xa = arg_xa;

		if (rc < 0) {
			/* unrecoverable translation error */
			xa->reply.header.error = NDMP0_UNDEFINED_ERR;
			return 0;
		}
		/* NB: rc>0 means that there were tolerated xlate errors */
	}
	return 0;
}

int
ndma_dispatch_raise_error (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn,
  ndmp9_error error, char *errstr)
{
	int			protocol_version = ref_conn->protocol_version;
	ndmp0_message		msg = xa->request.header.message;

	if (errstr) {
		ndmalogf (sess, 0, 2, "op=%s err=%s why=%s",
			ndmp_message_to_str (protocol_version, msg),
			ndmp9_error_to_str (error),
			errstr);
	}

	ndmnmb_set_reply_error (&xa->reply, error);

	return 1;
}




/*
 * Access paths to ndma_dispatch_request()
 ****************************************************************
 */

/* incomming requests on a ndmconn connection */
int
ndma_dispatch_conn (struct ndm_session *sess, struct ndmconn *conn)
{
	struct ndmp_xa_buf	xa;
	int			rc;

	NDMOS_MACRO_ZEROFILL (&xa);

	rc = ndmconn_recv_nmb (conn, &xa.request);
	if (rc) {
	    ndmnmb_free (&xa.request);
	    return rc;
	}

	ndma_dispatch_request (sess, &xa, conn);
	ndmnmb_free (&xa.request);

	if (! (xa.reply.flags & NDMNMB_FLAG_NO_SEND)) {
		rc = ndmconn_send_nmb (conn, &xa.reply);
		if (rc) return rc;
	}

	ndmnmb_free (&xa.reply);

	return 0;
}

void
ndma_dispatch_ctrl_unexpected (struct ndmconn *conn, struct ndmp_msg_buf *nmb)
{
	int			protocol_version = conn->protocol_version;
	struct ndm_session *	sess = conn->context;
	struct ndmp_xa_buf	xa;

	if (nmb->header.message_type != NDMP0_MESSAGE_REQUEST) {
		ndmalogf (sess, conn->chan.name, 1,
			"Unexpected message -- probably reply "
			"w/ wrong reply_sequence");
#if 0
		/* causes crash, needs investigation */
		ndmnmb_snoop (&sess->param.log, "WTF", 5,
					nmb, conn->chan.name);
#endif
		ndmnmb_free (nmb);
		return;
	}

	NDMOS_MACRO_ZEROFILL (&xa);
	xa.request = *nmb;

	ndmalogf (sess, conn->chan.name, 4, "Async request %s",
			ndmp_message_to_str (protocol_version,
				xa.request.header.message));

	ndma_dispatch_request (sess, &xa, conn);

	if (! (xa.reply.flags & NDMNMB_FLAG_NO_SEND)) {
		ndmconn_send_nmb (conn, &xa.reply);
	}

	ndmnmb_free (&xa.reply);
}



int
ndma_call_no_tattle (struct ndmconn *conn, struct ndmp_xa_buf *arg_xa)
{
	struct ndmp_xa_buf *	xa = arg_xa;
	struct ndmp_xa_buf	xl_xa;
	struct reqrep_xlate *	rrxl = 0;
	unsigned		protocol_version = conn->protocol_version;
	unsigned		msg = xa->request.header.message;
	int			rc;

	if (xa->request.protocol_version == NDMP9VER) {
		/*
		 * Typical case....
		 * Find the protocol_version specific translation
		 * functions for this request/reply. The request
		 * is translated to its native version, NDMPvX,
		 * from NDMPv9. The NDMPvX form is transmitted.
		 * The resulting reply is translated back to NDMPv9.
		 * NDMPvX is determined by the connection.
		 */

		rrxl = reqrep_xlate_lookup_version (reqrep_xlate_version_table,
				protocol_version);

		/* find the protocol_version translation table */
		if (!rrxl) {
			/* can't do it */
			xa->reply.header.error = NDMP0_NOT_SUPPORTED_ERR;
			rc = NDMCONN_CALL_STATUS_HDR_ERROR;
			conn->last_header_error = xa->reply.header.error;
			return rc;
		}

		/* find the interface's translation table entry */
		rrxl = ndmp_reqrep_by_v9 (rrxl, msg);
		if (!rrxl) {
			/* can't do it */
			xa->reply.header.error = NDMP0_NOT_SUPPORTED_ERR;
			rc = NDMCONN_CALL_STATUS_HDR_ERROR;
			conn->last_header_error = xa->reply.header.error;
			return rc;
		}

		NDMOS_MACRO_ZEROFILL (&xl_xa);
		xa = &xl_xa;

		xa->request.header = arg_xa->request.header;
		xa->request.header.message = rrxl->vx_message;
		xa->request.protocol_version = protocol_version;

		rc = (*rrxl->request_9tox)(
				(void*)&arg_xa->request.body,
				(void*)&xa->request.body);

		if (rc < 0) {
			/* unrecoverable translation error */
			ndmnmb_free (&xa->request);	/* clean up partials */
			xa = arg_xa;
			xa->reply.header.error = NDMP0_NOT_SUPPORTED_ERR;
			rc = NDMCONN_CALL_STATUS_HDR_ERROR;
			conn->last_header_error = xa->reply.header.error;
			return rc;
		}
		/* NB: rc>0 means that there were tolerated xlate errors */
	}

	if (conn->conn_type == NDMCONN_TYPE_RESIDENT) {
		struct ndm_session *sess = conn->context;

		conn->last_message = xa->request.header.message;
		conn->last_call_status = NDMCONN_CALL_STATUS_BOTCH;
		conn->last_header_error = -1;	/* invalid */
		conn->last_reply_error = -1;	/* invalid */

		xa->request.header.sequence = conn->next_sequence++;

		ndmconn_snoop_nmb (conn, &xa->request, "Send");

		rc = ndma_dispatch_request (sess, xa, conn);

		xa->reply.header.sequence = conn->next_sequence++;

		if (! (xa->reply.flags & NDMNMB_FLAG_NO_SEND))
			ndmconn_snoop_nmb (conn, &xa->reply, "Recv");
		if (rc) {
			/* keep it */
		} else if (xa->reply.header.error != NDMP0_NO_ERR) {
			rc = NDMCONN_CALL_STATUS_HDR_ERROR;
			conn->last_header_error = xa->reply.header.error;
		} else {
			conn->last_header_error = NDMP9_NO_ERR;
			conn->last_reply_error =
					ndmnmb_get_reply_error (&xa->reply);

			if (conn->last_reply_error == NDMP9_NO_ERR) {
				rc = NDMCONN_CALL_STATUS_OK;
			} else {
				rc = NDMCONN_CALL_STATUS_REPLY_ERROR;
			}
		}
	} else {
		rc = ndmconn_call (conn, xa);
		if (rc == 0) {
		    if ((conn->time_limit > 0) &&
			(conn->received_time > conn->sent_time)) {
			int delta;

			delta = conn->received_time - conn->sent_time;
			if (delta > conn->time_limit)
			    rc = NDMCONN_CALL_STATUS_REPLY_LATE;
		    }
		}
	}


	if (rrxl) {
		int		xrc;

		xrc = (*rrxl->reply_xto9)(
				(void*)&xa->reply.body,
				(void*)&arg_xa->reply.body);

		ndmnmb_free (&xa->request);	/* clean up */
		ndmnmb_free (&xa->reply);	/* clean up */

		arg_xa->reply.header = xa->reply.header;
		arg_xa->reply.flags = xa->reply.flags;
		arg_xa->reply.protocol_version = NDMP9VER;

		xa = arg_xa;

		if (xrc < 0) {
			/* unrecoverable translation error */
			xa->reply.header.error = NDMP0_UNDEFINED_ERR;
			rc = NDMCONN_CALL_STATUS_HDR_ERROR;
			conn->last_header_error = xa->reply.header.error;
			return rc;
		}
		/* NB: rc>0 means that there were tolerated xlate errors */
	}

	return rc;
}

int
ndma_call (struct ndmconn *conn, struct ndmp_xa_buf *xa)
{
	int		rc;

	rc = ndma_call_no_tattle (conn, xa);

	if (rc) {
		ndma_tattle (conn, xa, rc);
	}
	return rc;
}

int
ndma_send_to_control (struct ndm_session *sess, struct ndmp_xa_buf *xa,
  struct ndmconn *from_conn)
{
	struct ndmconn *	conn = sess->plumb.control;
	int			rc;

	if (conn->conn_type == NDMCONN_TYPE_RESIDENT && from_conn) {
		/*
		 * Control and sending agent are
		 * resident. Substitute the sending
		 * agents "connection" so that logs
		 * look right and right protocol_version
		 * is used.
		 */
		conn = from_conn;
	}

	rc = ndma_call_no_tattle (conn, xa);

	if (rc) {
		ndma_tattle (conn, xa, rc);
	}
	return rc;
}

int
ndma_tattle (struct ndmconn *conn, struct ndmp_xa_buf *xa, int rc)
{
	struct ndm_session *sess = conn->context;
	int		protocol_version = conn->protocol_version;
	unsigned	msg = xa->request.header.message;
	char *		tag = conn->chan.name;
	char *		msgname = ndmp_message_to_str (protocol_version, msg);
	unsigned	err;

	switch (rc) {
	case 0:
		ndmalogf (sess, tag, 2, " ?OK %s", msgname);
		break;

	case 1:	/* no error in header, error in reply */
		err = ndmnmb_get_reply_error_raw (&xa->reply);
		ndmalogf (sess, tag, 2, " ERR %s  %s",
			msgname,
			ndmp_error_to_str (protocol_version, err));
		break;

	case 2:	/* no error in header or in reply, response late */
		ndmalogf (sess, tag, 2, " REPLY LATE %s, took %d seconds",
			  msgname,
			  (conn->received_time - conn->sent_time));
		break;

	case -2: /* error in header, no reply body */
		err = xa->reply.header.error;
		ndmalogf (sess, tag, 2, " ERR-AGENT %s  %s",
			msgname,
			ndmp_error_to_str (protocol_version, err));
		break;

	default:
		ndmalogf (sess, tag, 2, " ERR-CONN %s  %s",
			msgname,
			ndmconn_get_err_msg (conn));
		break;
	}

	return 0;
}




/*
 * NDMPx_CONNECT Interfaces
 ****************************************************************
 */




/*
 * NDMP[0234]_CONNECT_OPEN
 */
int
ndmp_sxa_connect_open (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
    NDMS_WITH(ndmp0_connect_open)
	if (sess->conn_open) {
	    if (request->protocol_version != ref_conn->protocol_version) {
		NDMADR_RAISE_ILLEGAL_ARGS("too late to change version");
	    }
	} else {
	    switch (request->protocol_version) {
#ifndef NDMOS_OPTION_NO_NDMP2
	    case NDMP2VER:
#endif /* !NDMOS_OPTION_NO_NDMP2 */
#ifndef NDMOS_OPTION_NO_NDMP3
	    case NDMP3VER:
#endif /* !NDMOS_OPTION_NO_NDMP3 */
#ifndef NDMOS_OPTION_NO_NDMP4
	    case NDMP4VER:
#endif /* !NDMOS_OPTION_NO_NDMP4 */
		connect_open_common (sess, xa, ref_conn,
					request->protocol_version);
		break;

	    default:
		NDMADR_RAISE_ILLEGAL_ARGS("unsupport protocol version");
		break;
	    }
	}
    NDMS_ENDWITH

    return 0;
}

static int
connect_open_common (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn,
  int protocol_version)
{
#ifndef NDMOS_OPTION_NO_DATA_AGENT
	sess->data_acb.protocol_version = protocol_version;
#endif /* !NDMOS_OPTION_NO_DATA_AGENT */
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	sess->tape_acb.protocol_version = protocol_version;
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */
#ifndef NDMOS_OPTION_NO_ROBOT_AGENT
	sess->robot_acb.protocol_version = protocol_version;
#endif /* !NDMOS_OPTION_NO_ROBOT_AGENT */

	ref_conn->protocol_version = protocol_version;
	sess->conn_open = 1;

	return 0;
}




/*
 * NDMP[234]_CONNECT_CLIENT_AUTH
 */
int
ndmp_sxa_connect_client_auth (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_auth_type	auth_type;
	char *		name = 0;
	char *		proof = 0;

      NDMS_WITH(ndmp9_connect_client_auth)

	auth_type = request->auth_data.auth_type;
	switch (auth_type) {
	default:
		NDMADR_RAISE_ILLEGAL_ARGS ("auth_type");

	case NDMP9_AUTH_TEXT:
	    {
		ndmp9_auth_text *	p;

		p = &request->auth_data.ndmp9_auth_data_u.auth_text;
		name = p->auth_id;
		proof = p->auth_password;
		if (!ndmos_ok_name_password (sess, name, proof)) {
			NDMADR_RAISE(NDMP9_NOT_AUTHORIZED_ERR,
						"password not OK");
		}
	    }
	    break;

	case NDMP9_AUTH_MD5:
	    {
		ndmp9_auth_md5 *	p;

		p = &request->auth_data.ndmp9_auth_data_u.auth_md5;
		name = p->auth_id;
		proof = p->auth_digest;

		if (!sess->md5_challenge_valid) {
			NDMADR_RAISE(NDMP9_NOT_AUTHORIZED_ERR,
					"no challenge");
		}

		if (!ndmos_ok_name_md5_digest (sess, name, proof)) {
			NDMADR_RAISE(NDMP9_NOT_AUTHORIZED_ERR,
					"digest not OK");
		}
	    }
	    break;
	}
	sess->conn_authorized = 1;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[023]_CONNECT_CLOSE
 */
int
ndmp_sxa_connect_close (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;	/* ??? */

	/* TODO: shutdown everything */
	sess->connect_status = 0;
	ndmchan_set_eof (&ref_conn->chan);

	return 0;
}




/*
 * NDMP[23]_CONNECT_SERVER_AUTH
 */
int
ndmp_sxa_connect_server_auth (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	return NDMADR_UNIMPLEMENTED_MESSAGE;
}




/*
 * NDMPx_CONFIG Interfaces
 ****************************************************************
 */




/*
 * NDMP[234]_CONFIG_GET_HOST_INFO
 * NDMP[34]_CONFIG_GET_SERVER_INFO
 * NDMP2_CONFIG_GET_MOVER_TYPE
 * NDMP[34]_CONFIG_GET_CONNECTION_TYPE
 * NDMP[34]_CONFIG_GET_BUTYPE_INFO
 * NDMP[34]_CONFIG_GET_FS_INFO
 * NDMP[34]_CONFIG_GET_TAPE_INFO
 * NDMP[34]_CONFIG_GET_SCSI_INFO
 */
int
ndmp_sxa_config_get_info (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
      NDMS_WITH_VOID_REQUEST(ndmp9_config_get_info)
	ndmos_sync_config_info (sess);

	if (sess->config_info.conntypes == 0) {
		/* OS left it for us to do */
#ifndef NDMOS_OPTION_NO_DATA_AGENT
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
		sess->config_info.conntypes |= NDMP9_CONFIG_CONNTYPE_LOCAL;
		sess->config_info.conntypes |= NDMP9_CONFIG_CONNTYPE_TCP;
#else /* !NDMOS_OPTION_NO_TAPE_AGENT */
		sess->config_info.conntypes |= NDMP9_CONFIG_CONNTYPE_TCP;
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */
#else /* !NDMOS_OPTION_NO_DATA_AGENT */
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
		sess->config_info.conntypes |= NDMP9_CONFIG_CONNTYPE_TCP;
#else /* !NDMOS_OPTION_NO_TAPE_AGENT */
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */
#endif /* !NDMOS_OPTION_NO_DATA_AGENT */
	}

	if (sess->config_info.authtypes == 0) {
		/* OS left it for us to do */
		sess->config_info.authtypes |= NDMP9_CONFIG_AUTHTYPE_TEXT;
		sess->config_info.authtypes |= NDMP9_CONFIG_AUTHTYPE_MD5;
	}

	reply->config_info = sess->config_info;

	return 0;
      NDMS_ENDWITH
}




#ifndef NDMOS_OPTION_NO_NDMP2
/*
 * NDMP2_CONFIG_GET_BUTYPE_ATTR
 */
int
ndmp2_sxa_config_get_butype_attr (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_config_info *	ci = &sess->config_info;
	ndmp9_butype_info *	bu = 0;
	unsigned int		i;

	assert (xa->request.protocol_version == NDMP2VER);

      NDMS_WITH(ndmp2_config_get_butype_attr)
	ndmos_sync_config_info (sess);

	for (i = 0; i < ci->butype_info.butype_info_len; i++) {
		bu = &ci->butype_info.butype_info_val[i];

		if (strcmp (request->name, bu->butype_name) == 0) {
			break;
		}
	}

	if (i >= ci->butype_info.butype_info_len) {
		NDMADR_RAISE_ILLEGAL_ARGS("butype");
	}

	reply->attrs = bu->v2attr.value;

	return 0;
      NDMS_ENDWITH
}
#endif /* !NDMOS_OPTION_NO_NDMP2 */




/*
 * NDMP[234]_CONFIG_GET_AUTH_ATTR
 *
 * Credits to Rajiv of NetApp for helping with MD5 stuff.
 */
int
ndmp_sxa_config_get_auth_attr (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
      NDMS_WITH(ndmp9_config_get_auth_attr)
	switch (request->auth_type) {
	default:
		NDMADR_RAISE_ILLEGAL_ARGS ("auth_type");

	case NDMP9_AUTH_NONE:
		break;

	case NDMP9_AUTH_TEXT:
		break;

	case NDMP9_AUTH_MD5:
		ndmos_get_md5_challenge (sess);
		NDMOS_API_BCOPY (sess->md5_challenge,
			reply->server_attr.ndmp9_auth_attr_u.challenge, 64);
		break;
	}
	reply->server_attr.auth_type = request->auth_type;

	return 0;
      NDMS_ENDWITH
}




#ifndef NDMOS_OPTION_NO_ROBOT_AGENT	/* Surrounds SCSI intfs */
/*
 * NDMPx_SCSI Interfaces
 ****************************************************************
 *
 * If these are implemented, they should already have been
 * intercepted by ndmos_dispatch_request(). There is absolutely
 * no way to implement this generically, nor is there merit to
 * a generic "layer".
 *
 * Still, just in case, they are implemented here.
 */

static ndmp9_error	scsi_open_ok (struct ndm_session *sess);
static ndmp9_error	scsi_op_ok (struct ndm_session *sess);




/*
 * NDMP[234]_SCSI_OPEN
 */
int
ndmp_sxa_scsi_open (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;

      NDMS_WITH(ndmp9_scsi_open)
	error = scsi_open_ok (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!scsi_open_ok");
	}

	error = ndmos_scsi_open (sess, request->device);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "scsi_open");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_SCSI_CLOSE
 */
int
ndmp_sxa_scsi_close (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;

      NDMS_WITH_VOID_REQUEST(ndmp9_scsi_close)
	error = scsi_op_ok (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!scsi_op_ok");
	}

	error = ndmos_scsi_close (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "scsi_close");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_SCSI_GET_STATE
 */
int
ndmp_sxa_scsi_get_state (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_robot_agent *ra = &sess->robot_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_scsi_get_state)
	ndmos_scsi_sync_state (sess);

	*reply = ra->scsi_state;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[23]_SCSI_SET_TARGET
 */
int
ndmp_sxa_scsi_set_target (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;

      NDMS_WITH(ndmp9_scsi_set_target)
	error = scsi_op_ok (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!scsi_op_ok");
	}

	error = ndmos_scsi_set_target (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "scsi_set_target");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_SCSI_RESET_DEVICE
 */
int
ndmp_sxa_scsi_reset_device (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;

      NDMS_WITH_VOID_REQUEST(ndmp9_scsi_reset_device)
	error = scsi_op_ok (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!scsi_op_ok");
	}

	error = ndmos_scsi_reset_device (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "scsi_reset_device");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[23]_SCSI_RESET_BUS
 */
int
ndmp_sxa_scsi_reset_bus (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;

      NDMS_WITH_VOID_REQUEST(ndmp9_scsi_reset_bus)
	error = scsi_op_ok (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!scsi_op_ok");
	}

	error = ndmos_scsi_reset_bus (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "scsi_reset_bus");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_SCSI_EXECUTE_CDB
 */
int
ndmp_sxa_scsi_execute_cdb (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;

      NDMS_WITH(ndmp9_scsi_execute_cdb)
	error = scsi_op_ok (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!scsi_op_ok");
	}

	error = ndmos_scsi_execute_cdb (sess, request, reply);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "scsi_execute_cdb");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMPx_SCSI helper routines
 */

static ndmp9_error
scsi_open_ok (struct ndm_session *sess)
{
	struct ndm_robot_agent *	ra = &sess->robot_acb;

	ndmos_scsi_sync_state(sess);
	if (ra->scsi_state.error != NDMP9_DEV_NOT_OPEN_ERR)
		return NDMP9_DEVICE_OPENED_ERR;

#ifndef NDMOS_OPTION_ALLOW_SCSI_AND_TAPE_BOTH_OPEN
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	ndmos_tape_sync_state(sess);
	if (sess->tape_acb.tape_state.error != NDMP9_DEV_NOT_OPEN_ERR)
		return NDMP9_DEVICE_OPENED_ERR;
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */
#endif /* NDMOS_OPTION_ALLOW_SCSI_AND_TAPE_BOTH_OPEN */

	return NDMP9_NO_ERR;
}

static ndmp9_error
scsi_op_ok (struct ndm_session *sess)
{
	struct ndm_robot_agent *	ra = &sess->robot_acb;

	ndmos_scsi_sync_state(sess);
	if (ra->scsi_state.error != NDMP9_NO_ERR)
		return NDMP9_DEV_NOT_OPEN_ERR;

	return NDMP9_NO_ERR;
}
#endif /* !NDMOS_OPTION_NO_ROBOT_AGENT */	/* Surrounds SCSI intfs */




#ifndef NDMOS_OPTION_NO_TAPE_AGENT		/* Surrounds TAPE intfs */
/*
 * NDMPx_TAPE Interfaces
 ****************************************************************
 */
static ndmp9_error	tape_open_ok (struct ndm_session *sess,
				int will_write);
static ndmp9_error	tape_op_ok (struct ndm_session *sess,
				int will_write);




/*
 * NDMP[234]_TAPE_OPEN
 */
int
ndmp_sxa_tape_open (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;
	int			will_write;

      NDMS_WITH(ndmp9_tape_open)
	switch (request->mode) {
	default:
		NDMADR_RAISE_ILLEGAL_ARGS("tape_mode");

	case NDMP9_TAPE_READ_MODE:
		will_write = 0;
		break;

	case NDMP9_TAPE_RDWR_MODE:
	case NDMP9_TAPE_RAW_MODE:
		will_write = 1;
		break;
	}

	error = tape_open_ok (sess, will_write);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!tape_open_ok");
	}

	error = ndmos_tape_open (sess, request->device, will_write);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "tape_open");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_TAPE_CLOSE
 */
int
ndmp_sxa_tape_close (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;

      NDMS_WITH_VOID_REQUEST(ndmp9_tape_close)
	error = tape_op_ok (sess, 0);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!tape_op_ok");
	}

	error = ndmos_tape_close (sess);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "tape_close");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_TAPE_GET_STATE
 */
int
ndmp_sxa_tape_get_state (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_tape_get_state)

	ndmos_tape_sync_state(sess);

	*reply = ta->tape_state;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_TAPE_MTIO
 */
int
ndmp_sxa_tape_mtio (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;
	ndmp9_tape_mtio_op	tape_op;
	int			will_write = 0;
	unsigned long		resid = 0;

      NDMS_WITH(ndmp9_tape_mtio)

	switch (request->tape_op) {
	default:
		NDMADR_RAISE_ILLEGAL_ARGS("tape_op");

	case NDMP9_MTIO_EOF:
		will_write = 1;
		tape_op = NDMP9_MTIO_EOF;
		break;

	case NDMP9_MTIO_FSF:
	case NDMP9_MTIO_BSF:
	case NDMP9_MTIO_FSR:
	case NDMP9_MTIO_BSR:
	case NDMP9_MTIO_REW:
	case NDMP9_MTIO_OFF:
		tape_op = request->tape_op;
		break;
	}

	error = tape_op_ok (sess, will_write);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!tape_op_ok");
	}

	error = ndmos_tape_mtio (sess, tape_op, request->count, &resid);

	reply->error = error;
	reply->resid_count = resid;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_TAPE_WRITE
 */
int
ndmp_sxa_tape_write (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;
	unsigned long		done_count = 0;

      NDMS_WITH(ndmp9_tape_write)
	if (request->data_out.data_out_len == 0) {
		/*
		 * NDMPv4 clarification -- a tape read or write with
		 * a count==0 is a no-op. This is undoubtedly influenced
		 * by the SCSI Sequential Access specification which
		 * says much the same thing.
		 *
		 * NDMPv[23] MAY return NDMP_NO_ERR or
		 * NDMP_ILLEGAL_ARGS_ERR.
		 */
		reply->error = NDMP9_NO_ERR;
		reply->count = 0;

		return 0;
	}

	if (!NDMOS_MACRO_OK_TAPE_REC_LEN(request->data_out.data_out_len)) {
		NDMADR_RAISE_ILLEGAL_ARGS("!ok_tape_rec_len");
	}

	error = tape_op_ok (sess, 1);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!tape_op_ok");
	}

	error = ndmos_tape_write (sess, request->data_out.data_out_val,
				request->data_out.data_out_len,
				&done_count);
	reply->error = error;
	reply->count = done_count;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_TAPE_READ
 */
int
ndmp_sxa_tape_read (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;
	ndmp9_error		error;
	unsigned long		done_count = 0;

      NDMS_WITH(ndmp9_tape_read)
	if (request->count == 0) {
		/*
		 * NDMPv4 clarification -- a tape read or write with
		 * a count==0 is a no-op. This is undoubtedly influenced
		 * by the SCSI Sequential Access specification which
		 * says much the same thing.
		 *
		 * NDMPv[23] MAY return NDMP_NO_ERR or
		 * NDMP_ILLEGAL_ARGS_ERR.
		 */
		reply->error = NDMP9_NO_ERR;
		reply->data_in.data_in_val = ta->tape_buffer;
		reply->data_in.data_in_len = 0;

		return 0;
	}

	if (!NDMOS_MACRO_OK_TAPE_REC_LEN(request->count)) {
		NDMADR_RAISE_ILLEGAL_ARGS("!ok_tape_rec_len");
	}

	error = tape_op_ok (sess, 0);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!tape_op_ok");
	}

	error = ndmos_tape_read (sess, ta->tape_buffer,
				request->count,
				&done_count);
	reply->error = error;
	reply->data_in.data_in_val = ta->tape_buffer;
	reply->data_in.data_in_len = done_count;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_TAPE_EXECUTE_CDB
 *
 * If this is implemented, it should already have been
 * intercepted by ndmos_dispatch_request().
 * There is absolutely no way to implement this generically,
 * nor is there merit to a generic "layer".
 * Still, just in case, it is implemented here.
 */
int
ndmp_sxa_tape_execute_cdb (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
      NDMS_WITH(ndmp9_tape_execute_cdb)
	return NDMADR_UNIMPLEMENTED_MESSAGE;
      NDMS_ENDWITH
}




/*
 * NDMPx_TAPE helper routines
 */

static ndmp9_error
tape_open_ok (struct ndm_session *sess, int will_write)
{
	struct ndm_tape_agent *		ta = &sess->tape_acb;

	ndmos_tape_sync_state(sess);
	if (ta->tape_state.state != NDMP9_TAPE_STATE_IDLE)
		return NDMP9_DEVICE_OPENED_ERR;

#ifndef NDMOS_OPTION_ALLOW_SCSI_AND_TAPE_BOTH_OPEN
#ifndef NDMOS_OPTION_NO_ROBOT_AGENT
	ndmos_scsi_sync_state(sess);
	if (sess->robot_acb.scsi_state.error != NDMP9_DEV_NOT_OPEN_ERR)
		return NDMP9_DEVICE_OPENED_ERR;
#endif /* !NDMOS_OPTION_NO_ROBOT_AGENT */
#endif /* NDMOS_OPTION_ALLOW_SCSI_AND_TAPE_BOTH_OPEN */

	return NDMP9_NO_ERR;
}

/*
 * Tape operation is only OK if it is open and the MOVER
 * hasn't got a hold of it. We can't allow tape operations
 * to interfere with the MOVER.
 */

static ndmp9_error
tape_op_ok (struct ndm_session *sess, int will_write)
{
	struct ndm_tape_agent *		ta = &sess->tape_acb;

	ndmos_tape_sync_state(sess);
	switch (ta->tape_state.state) {
	case NDMP9_TAPE_STATE_IDLE:
		return NDMP9_DEV_NOT_OPEN_ERR;

	case NDMP9_TAPE_STATE_OPEN:
		if (will_write && !NDMTA_TAPE_IS_WRITABLE(ta))
			return NDMP9_PERMISSION_ERR;
		break;

	case NDMP9_TAPE_STATE_MOVER:
		return NDMP9_ILLEGAL_STATE_ERR;
	}

	return NDMP9_NO_ERR;
}
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */	/* Surrounds TAPE intfs */




#ifndef NDMOS_OPTION_NO_DATA_AGENT		/* Surrounds DATA intfs */
/*
 * NDMPx_DATA Interfaces
 ****************************************************************
 */

static int		data_ok_bu_type (struct ndm_session *sess,
				struct ndmp_xa_buf *xa,
				struct ndmconn *ref_conn,
				char *bu_type);

static int		data_can_connect_and_start (struct ndm_session *sess,
				struct ndmp_xa_buf *xa,
				struct ndmconn *ref_conn,
				ndmp9_addr *data_addr,
				ndmp9_mover_mode mover_mode);

static int		data_can_connect (struct ndm_session *sess,
				struct ndmp_xa_buf *xa,
				struct ndmconn *ref_conn,
				ndmp9_addr *data_addr);

static int		data_can_start (struct ndm_session *sess,
				struct ndmp_xa_buf *xa,
				struct ndmconn *ref_conn,
				ndmp9_mover_mode mover_mode);

static int		data_connect (struct ndm_session *sess,
				struct ndmp_xa_buf *xa,
				struct ndmconn *ref_conn,
				ndmp9_addr *data_addr);

static ndmp9_error	data_copy_environment (struct ndm_session *sess,
				ndmp9_pval *env, unsigned n_env);

static ndmp9_error	data_copy_nlist (struct ndm_session *sess,
				ndmp9_name *nlist, unsigned n_nlist);





/*
 * NDMP[234]_DATA_GET_STATE
 */
int
ndmp_sxa_data_get_state (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_data_agent *	da = &sess->data_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_data_get_state)
	*reply = da->data_state;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_DATA_START_BACKUP
 */
int
ndmp_sxa_data_start_backup (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	int			rc;
	ndmp9_error		error;

      NDMS_WITH(ndmp9_data_start_backup)
	rc = data_ok_bu_type (sess, xa, ref_conn, request->bu_type);
	if (rc) {
		return rc;
	}

	if (request->addr.addr_type != NDMP9_ADDR_AS_CONNECTED) {
		rc = data_can_connect_and_start (sess, xa, ref_conn,
				&request->addr, NDMP9_MOVER_MODE_READ);
	} else {
		rc = data_can_start (sess, xa, ref_conn,
				NDMP9_MOVER_MODE_READ);
	}
	if (rc) {
		return rc;	/* already tattled */
	}

	strcpy (sess->data_acb.bu_type, request->bu_type);

	error = data_copy_environment (sess,
			request->env.env_val, request->env.env_len);
	if (error != NDMP9_NO_ERR) {
		ndmda_belay (sess);
		NDMADR_RAISE(error, "copy-env");
	}

	if (request->addr.addr_type != NDMP9_ADDR_AS_CONNECTED) {
		rc = data_connect (sess, xa, ref_conn, &request->addr);
		if (rc) {
			ndmda_belay (sess);
			return rc;	/* already tattled */
		}
	}

	error = ndmda_data_start_backup (sess);
	if (error != NDMP9_NO_ERR) {
		/* TODO: undo everything */
		ndmda_belay (sess);
		NDMADR_RAISE(error, "start_backup");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_DATA_START_RECOVER
 */
int
ndmp_sxa_data_start_recover (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;
	int			rc;

      NDMS_WITH(ndmp9_data_start_recover)
	rc = data_ok_bu_type (sess, xa, ref_conn, request->bu_type);
	if (rc) {
		return rc;
	}

	if (request->addr.addr_type != NDMP9_ADDR_AS_CONNECTED) {
		rc = data_can_connect_and_start (sess, xa, ref_conn,
				&request->addr, NDMP9_MOVER_MODE_WRITE);
	} else {
		rc = data_can_start (sess, xa, ref_conn,
				NDMP9_MOVER_MODE_WRITE);
	}
	if (rc) {
		return rc;	/* already tattled */
	}

	strcpy (sess->data_acb.bu_type, request->bu_type);

	error = data_copy_environment (sess,
			request->env.env_val, request->env.env_len);
	if (error != NDMP9_NO_ERR) {
		ndmda_belay (sess);
		NDMADR_RAISE(error, "copy-env");
	}

	error = data_copy_nlist (sess,
		request->nlist.nlist_val, request->nlist.nlist_len);

	if (error != NDMP9_NO_ERR) {
		ndmda_belay (sess);
		NDMADR_RAISE(error, "copy-nlist");
	}

	if (request->addr.addr_type != NDMP9_ADDR_AS_CONNECTED) {
		rc = data_connect (sess, xa, ref_conn, &request->addr);
		if (rc) {
			ndmda_belay (sess);
			return rc;	/* already tattled */
		}
	}

	error = ndmda_data_start_recover (sess);
	if (error != NDMP9_NO_ERR) {
		/* TODO: undo everything */
		ndmda_belay (sess);
		NDMADR_RAISE(error, "start_recover");
	}

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_DATA_ABORT
 */
int
ndmp_sxa_data_abort (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_data_agent *	da = &sess->data_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_data_abort)
	if (da->data_state.state != NDMP9_DATA_STATE_ACTIVE)
		NDMADR_RAISE_ILLEGAL_STATE("data_state !ACTIVE");

	ndmda_data_abort (sess);

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_DATA_GET_ENV
 */
int
ndmp_sxa_data_get_env (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_data_agent *	da = &sess->data_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_data_get_env)
	if (da->data_state.state == NDMP9_DATA_STATE_IDLE) {
		NDMADR_RAISE_ILLEGAL_STATE("data_state IDLE");
	}
	if (da->data_state.operation != NDMP9_DATA_OP_BACKUP) {
		NDMADR_RAISE_ILLEGAL_STATE("data_op !BACKUP");
	}

	ndmda_sync_environment (sess);

	ndmalogf (sess, ref_conn->chan.name, 6, "n_env=%d", da->env_tab.n_env);

	reply->env.env_len = da->env_tab.n_env;
	reply->env.env_val = da->env_tab.env;

#if 0
	xa->reply.flags &= ~NDMNMB_FLAG_NO_FREE;  /* free env after xmit */
#endif

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_DATA_STOP
 */
int
ndmp_sxa_data_stop (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_data_agent *	da = &sess->data_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_data_stop)
	if (da->data_state.state != NDMP9_DATA_STATE_HALTED) {
		NDMADR_RAISE_ILLEGAL_STATE("data_state !HALTED");
	}

	ndmda_data_stop (sess);

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_DATA_START_RECOVER_FILEHIST
 * This is a Traakan extension to NDMPv2 and NDMPv3
 * Adopted for NDMPv4
 */
int
ndmp_sxa_data_start_recover_filehist (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	ndmp9_error		error;
	int			rc;

      NDMS_WITH(ndmp9_data_start_recover)
	rc = data_ok_bu_type (sess, xa, ref_conn, request->bu_type);
	if (rc) {
		return rc;
	}

	if (request->addr.addr_type != NDMP9_ADDR_AS_CONNECTED) {
		rc = data_can_connect_and_start (sess, xa, ref_conn,
				&request->addr, NDMP9_MOVER_MODE_WRITE);
	} else {
		rc = data_can_start (sess, xa, ref_conn,
				NDMP9_MOVER_MODE_WRITE);
	}
	if (rc) {
		return rc;	/* already tattled */
	}

	strcpy (sess->data_acb.bu_type, request->bu_type);

	error = data_copy_environment (sess,
			request->env.env_val, request->env.env_len);
	if (error != NDMP9_NO_ERR) {
		ndmda_belay (sess);
		NDMADR_RAISE(error, "copy-env");
	}

	error = data_copy_nlist (sess,
		request->nlist.nlist_val, request->nlist.nlist_len);

	if (error != NDMP9_NO_ERR) {
		ndmda_belay (sess);
		NDMADR_RAISE(error, "copy-nlist");
	}

	if (request->addr.addr_type != NDMP9_ADDR_AS_CONNECTED) {
		rc = data_connect (sess, xa, ref_conn, &request->addr);
		if (rc) {
			ndmda_belay (sess);
			return rc;	/* already tattled */
		}
	}

	error = ndmda_data_start_recover_fh (sess);
	if (error != NDMP9_NO_ERR) {
		/* TODO: undo everything */
		ndmda_belay (sess);
		NDMADR_RAISE(error, "start_recover_filehist");
	}

	return 0;
      NDMS_ENDWITH
}


#ifndef NDMOS_EFFECT_NO_NDMP3_NOR_NDMP4	/* Surrounds NDMPv[34] DATA intfs */
/*
 * NDMP[34]_DATA_CONNECT
 */
int
ndmp_sxa_data_connect (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{

      NDMS_WITH(ndmp9_data_connect)
	return data_connect (sess, xa, ref_conn, &request->addr);
      NDMS_ENDWITH
}




#ifdef notyet
static int		data_listen_common34 (struct ndm_session *sess,
				struct ndmp_xa_buf *xa,
				struct ndmconn *ref_conn,
				ndmp9_addr_type addr_type);

/*
 * NDMP[34]_DATA_LISTEN
 */
int
ndmadr_data_listen (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
    struct ndm_data_agent *	da = &sess->data_acb;
    int				rc;

    switch (xa->request.protocol_version) {
    default: return NDMADR_UNIMPLEMENTED_VERSION; /* should never happen */

#ifndef NDMOS_OPTION_NO_NDMP2
    case NDMP2VER:
	/* not part of NDMPv2 */
	return NDMADR_UNSPECIFIED_MESSAGE;
#endif /* !NDMOS_OPTION_NO_NDMP2 */

#ifndef NDMOS_OPTION_NO_NDMP3
    case NDMP3VER:
      NDMS_WITH(ndmp3_data_listen)
	ndmp9_addr_type		addr_type;

	/* Check args, map along the way */
	switch (request->addr_type) {
	default:		addr_type = -1; break;
	case NDMP3_ADDR_LOCAL:	addr_type = NDMP9_ADDR_LOCAL;	break;
	case NDMP3_ADDR_TCP:	addr_type = NDMP9_ADDR_TCP;	break;
	}

	rc = data_listen_common34 (sess, xa, ref_conn, addr_type);
	if (rc)
		return rc;	/* something went wrong */

	ndmp_9to3_addr (&da->data_state.data_connection_addr,
				&reply->data_connection_addr);
	/* reply->error already set to NDMPx_NO_ERROR */
      NDMS_ENDWITH
      break;
#endif /* !NDMOS_OPTION_NO_NDMP3 */

#ifndef NDMOS_OPTION_NO_NDMP4
    case NDMP4VER:
      NDMS_WITH(ndmp4_data_listen)
	ndmp9_addr_type		addr_type;

	/* Check args, map along the way */
	switch (request->addr_type) {
	default:		addr_type = -1; break;
	case NDMP4_ADDR_LOCAL:	addr_type = NDMP9_ADDR_LOCAL;	break;
	case NDMP4_ADDR_TCP:	addr_type = NDMP9_ADDR_TCP;	break;
	}

	rc = data_listen_common34 (sess, xa, ref_conn, addr_type);
	if (rc)
		return rc;	/* something went wrong */

	ndmp_9to4_addr (&da->data_state.data_connection_addr,
				&reply->data_connection_addr);
	/* reply->error already set to NDMPx_NO_ERROR */
      NDMS_ENDWITH
      break;
#endif /* !NDMOS_OPTION_NO_NDMP4 */
    }
    return 0;
}

/* this same intf is expected in v4, so _common() now */
static int
data_listen_common34 (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn,
  ndmp9_addr_type addr_type)
{
	struct ndm_data_agent *	da = &sess->data_acb;
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	struct ndm_tape_agent *	ta = &sess->tape_acb;
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */
	ndmp9_error		error;
	char			reason[100];

	/* Check args */

	switch (addr_type) {
	default:		NDMADR_RAISE_ILLEGAL_ARGS("mover_addr_type");
	case NDMP9_ADDR_LOCAL:
#ifdef NDMOS_OPTION_NO_TAPE_AGENT
		NDMADR_RAISE_ILLEGAL_ARGS("data LOCAL w/o local TAPE agent");
#endif /* NDMOS_OPTION_NO_TAPE_AGENT */
		break;

	case NDMP9_ADDR_TCP:
		break;
	}

	/* Check states -- this should cover everything */
	if (da->data_state.state != NDMP9_DATA_STATE_IDLE)
		NDMADR_RAISE_ILLEGAL_STATE("data_state !IDLE");
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	if (ta->mover_state.state != NDMP9_MOVER_STATE_IDLE)
		NDMADR_RAISE_ILLEGAL_STATE("mover_state !IDLE");
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */

	/*
	 * Check image stream state -- should already be reflected
	 * in the mover and data states. This extra check gives
	 * us an extra measure of robustness and sanity
	 * check on the implementation.
	 */
	error = ndmis_audit_data_listen (sess, addr_type, reason);
	if (error != NDMP9_NO_ERR) NDMADR_RAISE(error, reason);

	error = ndmis_data_listen (sess, addr_type,
			&da->data_state.data_connection_addr,
			reason);
	if (error != NDMP9_NO_ERR) NDMADR_RAISE(error, reason);

	error = ndmda_data_listen(sess);
	if (error != NDMP9_NO_ERR) {
		/* TODO: belay ndmis_data_listen() */
		NDMADR_RAISE(error, "!data_listen");
	}

	return 0;
}
#endif /* notyet */

#endif /* !NDMOS_EFFECT_NO_NDMP3_NOR_NDMP4  Surrounds NDMPv[34] DATA intfs */




/*
 * NDMPx_DATA helper routines
 */


static int
data_ok_bu_type (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn,
  char *bu_type)
{
	ndmp9_config_info *	ci = &sess->config_info;
	ndmp9_butype_info *	bu;
	unsigned int		i;

	ndmos_sync_config_info (sess);

	for (i = 0; i < ci->butype_info.butype_info_len; i++) {
		bu = &ci->butype_info.butype_info_val[i];

		if (strcmp (bu_type, bu->butype_name) == 0) {
			return 0;
		}
	}

	NDMADR_RAISE_ILLEGAL_ARGS ("bu_type");
}


/*
 * Data can only start if the mover is ready.
 * Just mode and state checks.
 */

static int
data_can_connect_and_start (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn,
  ndmp9_addr *data_addr,
  ndmp9_mover_mode mover_mode)
{
	int			rc;

	/* Check args */
	switch (mover_mode) {
	default:		NDMADR_RAISE_ILLEGAL_ARGS("mover_mode");
	case NDMP9_MOVER_MODE_READ:	/* aka BACKUP */
	case NDMP9_MOVER_MODE_WRITE:	/* aka RECOVER */
		break;
	}

	rc = data_can_connect (sess, xa, ref_conn, data_addr);
	if (rc) return rc;

#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	if (data_addr->addr_type == NDMP9_ADDR_LOCAL) {
		struct ndm_tape_agent *		ta = &sess->tape_acb;
		ndmp9_mover_get_state_reply *	ms = &ta->mover_state;

		if (ms->mode != mover_mode)
			NDMADR_RAISE_ILLEGAL_STATE("mover_mode mismatch");
	}
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */

	return 0;
}

static int
data_can_connect (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn,
  ndmp9_addr *data_addr)
{
	struct ndm_data_agent *	da = &sess->data_acb;
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	struct ndm_tape_agent *	ta = &sess->tape_acb;
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */
	ndmp9_error		error;
	char			reason[100];

	/* Check args */
	switch (data_addr->addr_type) {
	default:		NDMADR_RAISE_ILLEGAL_ARGS("addr_type");
	case NDMP9_ADDR_LOCAL:
#ifdef NDMOS_OPTION_NO_TAPE_AGENT
		NDMADR_RAISE_ILLEGAL_ARGS("mover LOCAL w/o local DATA agent");
#endif /* NDMOS_OPTION_NO_TAPE_AGENT */
		break;

	case NDMP9_ADDR_TCP:
		break;
	}

	/* Check states -- this should cover everything */
	if (da->data_state.state != NDMP9_DATA_STATE_IDLE)
		NDMADR_RAISE_ILLEGAL_STATE("data_state !IDLE");

#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	if (data_addr->addr_type == NDMP9_ADDR_LOCAL) {
		ndmp9_mover_get_state_reply *ms = &ta->mover_state;

		if (ms->state != NDMP9_MOVER_STATE_LISTEN)
			NDMADR_RAISE_ILLEGAL_STATE("mover_state !LISTEN");

		if (ms->data_connection_addr.addr_type != NDMP9_ADDR_LOCAL)
			NDMADR_RAISE_ILLEGAL_STATE("mover_addr !LOCAL");

	} else {
		if (ta->mover_state.state != NDMP9_MOVER_STATE_IDLE)
			NDMADR_RAISE_ILLEGAL_STATE("mover_state !IDLE");
	}
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */

	/*
	 * Check image stream state -- should already be reflected
	 * in the mover and data states. This extra check gives
	 * us an extra measure of robustness and sanity
	 * check on the implementation.
	 */
	error = ndmis_audit_data_connect (sess, data_addr->addr_type, reason);
	if (error != NDMP9_NO_ERR) NDMADR_RAISE(error, reason);

	return 0;
}

static int
data_can_start (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn,
  ndmp9_mover_mode mover_mode)
{
	struct ndm_data_agent *	da = &sess->data_acb;
	ndmp9_data_get_state_reply *ds = &da->data_state;
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	struct ndm_tape_agent *	ta = &sess->tape_acb;
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */

	/* Check args */
	switch (mover_mode) {
	default:		NDMADR_RAISE_ILLEGAL_ARGS("mover_mode");
	case NDMP9_MOVER_MODE_READ:	/* aka BACKUP */
	case NDMP9_MOVER_MODE_WRITE:	/* aka RECOVER */
		break;
	}

	/* Check states -- this should cover everything */
	if (da->data_state.state != NDMP9_DATA_STATE_CONNECTED)
		NDMADR_RAISE_ILLEGAL_STATE("data_state !CONNECTED");

#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	if (ds->data_connection_addr.addr_type == NDMP9_ADDR_LOCAL) {
		ndmp9_mover_get_state_reply *ms = &ta->mover_state;

		if (ms->state != NDMP9_MOVER_STATE_ACTIVE)
			NDMADR_RAISE_ILLEGAL_STATE("mover_state !ACTIVE");

		if (ms->data_connection_addr.addr_type != NDMP9_ADDR_LOCAL)
			NDMADR_RAISE_ILLEGAL_STATE("mover_addr !LOCAL");

		if (ms->mode != mover_mode)
			NDMADR_RAISE_ILLEGAL_STATE("mover_mode mismatch");
	} else {
		if (ta->mover_state.state != NDMP9_MOVER_STATE_IDLE)
			NDMADR_RAISE_ILLEGAL_STATE("mover_state !IDLE");
	}
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */

	return 0;
}

/*
 * For NDMPv2, called from ndmadr_data_start_{backup,recover,recover_filhist}()
 * For NDMPv[34], called from ndmp_sxa_data_connect()
 */
static int
data_connect (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn,
  ndmp9_addr *data_addr)
{
	struct ndm_data_agent *	da = &sess->data_acb;
	int			rc;
	ndmp9_error		error;
	char			reason[100];

	rc = data_can_connect (sess, xa, ref_conn, data_addr);
	if (rc)
		return rc;

	/*
	 * Audits done, connect already
	 */
	error = ndmis_data_connect (sess, data_addr, reason);
	if (error != NDMP9_NO_ERR) NDMADR_RAISE(error, reason);

	da->data_state.data_connection_addr = *data_addr;
	/* alt: da->....data_connection_addr = sess->...peer_addr */

	error = ndmda_data_connect (sess);
	if (error != NDMP9_NO_ERR) {
		/* TODO: belay ndmis_data_connect() */
		NDMADR_RAISE(error, "!data_connect");
	}

	da->data_state.data_connection_addr = *data_addr;

	return 0;
}

static ndmp9_error
data_copy_environment (struct ndm_session *sess,
  ndmp9_pval *env, unsigned n_env)
{
	int			rc;

	if (n_env > NDM_MAX_ENV)
		return NDMP9_ILLEGAL_ARGS_ERR;

	rc = ndmda_copy_environment (sess, env, n_env);
	if (rc != 0)
		return NDMP9_NO_MEM_ERR;

	return NDMP9_NO_ERR;
}

static ndmp9_error
data_copy_nlist (struct ndm_session *sess,
  ndmp9_name *nlist, unsigned n_nlist)
{
	int			rc;

	if (n_nlist >= NDM_MAX_NLIST)
		return NDMP9_ILLEGAL_ARGS_ERR;

	rc = ndmda_copy_nlist (sess, nlist, n_nlist);
	if (rc != 0)
		return NDMP9_NO_MEM_ERR;

	return NDMP9_NO_ERR;
}


#endif /* !NDMOS_OPTION_NO_DATA_AGENT */	/* Surrounds DATA intfs */




#ifndef NDMOS_OPTION_NO_TAPE_AGENT		/* Surrounds MOVER intfs */
/*
 * NDMPx_MOVER Interfaces
 ****************************************************************
 */

static ndmp9_error	mover_can_proceed (struct ndm_session *sess,
				int will_write);







/*
 * NDMP[234]_MOVER_GET_STATE
 */
int
ndmp_sxa_mover_get_state (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_mover_get_state)
	ndmta_mover_sync_state(sess);
	*reply = ta->mover_state;
	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_MOVER_LISTEN
 */
int
ndmp_sxa_mover_listen (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
#ifndef NDMOS_OPTION_NO_DATA_AGENT
	struct ndm_data_agent *	da = &sess->data_acb;
#endif /* !NDMOS_OPTION_NO_DATA_AGENT */
	struct ndm_tape_agent *	ta = &sess->tape_acb;
	ndmp9_error		error;
	int			will_write;
	char			reason[100];

      NDMS_WITH(ndmp9_mover_listen)
	ndmalogf (sess, 0, 6, "mover_listen_common() addr_type=%s mode=%s",
			ndmp9_addr_type_to_str (request->addr_type),
			ndmp9_mover_mode_to_str (request->mode));

	/* Check args */
	switch (request->mode) {
	default:
		NDMADR_RAISE_ILLEGAL_ARGS("mover_mode");

	case NDMP9_MOVER_MODE_READ:
		will_write = 1;
		break;

	case NDMP9_MOVER_MODE_WRITE:
		will_write = 0;
		break;
	}

	switch (request->addr_type) {
	default:
		NDMADR_RAISE_ILLEGAL_ARGS("mover_addr_type");

	case NDMP9_ADDR_LOCAL:
#ifdef NDMOS_OPTION_NO_DATA_AGENT
		NDMADR_RAISE_ILLEGAL_ARGS("mover LOCAL w/o local DATA agent");
#endif /* NDMOS_OPTION_NO_DATA_AGENT */
		break;

	case NDMP9_ADDR_TCP:
		break;
	}

	/* Check states -- this should cover everything */
	if (ta->mover_state.state != NDMP9_MOVER_STATE_IDLE) {
		NDMADR_RAISE_ILLEGAL_STATE("mover_state !IDLE");
	}
#ifndef NDMOS_OPTION_NO_DATA_AGENT
	if (da->data_state.state != NDMP9_DATA_STATE_IDLE) {
		NDMADR_RAISE_ILLEGAL_STATE("data_state !IDLE");
	}
#endif /* !NDMOS_OPTION_NO_DATA_AGENT */

	/* Check that the tape is ready to go */
	error = mover_can_proceed (sess, will_write);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!mover_can_proceed");
	}

	/*
	 * Check image stream state -- should already be reflected
	 * in the mover and data states. This extra check gives
	 * us an extra measure of robustness and sanity
	 * check on the implementation.
	 */
	error = ndmis_audit_tape_listen (sess, request->addr_type, reason);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, reason);
	}

	error = ndmis_tape_listen (sess, request->addr_type,
			&ta->mover_state.data_connection_addr,
			reason);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, reason);
	}

	error = ndmta_mover_listen(sess, request->mode);
	if (error != NDMP9_NO_ERR) {
		/* TODO: belay ndmis_tape_listen() */
		NDMADR_RAISE(error, "!mover_listen");
	}

	reply->data_connection_addr = ta->mover_state.data_connection_addr;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_MOVER_CONTINUE
 */
int
ndmp_sxa_mover_continue (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;
	ndmp9_error		error;
	int			will_write;

      NDMS_WITH_VOID_REQUEST(ndmp9_mover_continue)
	if (ta->mover_state.state != NDMP9_MOVER_STATE_PAUSED) {
		NDMADR_RAISE_ILLEGAL_STATE("mover_state !PAUSED");
	}

	will_write = ta->mover_state.mode == NDMP9_MOVER_MODE_READ;

	error = mover_can_proceed (sess, will_write);
	if (error != NDMP9_NO_ERR) {
		NDMADR_RAISE(error, "!mover_can_proceed");
	}

	ndmta_mover_continue (sess);

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_MOVER_ABORT
 */
int
ndmp_sxa_mover_abort (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_mover_abort)
	if (ta->mover_state.state != NDMP9_MOVER_STATE_LISTEN
	 && ta->mover_state.state != NDMP9_MOVER_STATE_ACTIVE
	 && ta->mover_state.state != NDMP9_MOVER_STATE_PAUSED) {
		NDMADR_RAISE_ILLEGAL_STATE("mover_state");
	}

	ndmta_mover_abort (sess);

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_MOVER_STOP
 */
int
ndmp_sxa_mover_stop (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_mover_stop)
	if (ta->mover_state.state != NDMP9_MOVER_STATE_HALTED) {
		NDMADR_RAISE_ILLEGAL_STATE("mover_state !HALTED");
	}

	ndmta_mover_stop (sess);

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_MOVER_SET_WINDOW
 */
int
ndmp_sxa_mover_set_window (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;
	struct ndmp9_mover_get_state_reply *ms = &ta->mover_state;
	unsigned long long	max_len;
	unsigned long long	end_win;

      NDMS_WITH(ndmp9_mover_set_window)
	ndmta_mover_sync_state (sess);

	if (ref_conn->protocol_version < NDMP4VER) {
		/*
		 * NDMP[23] require the Mover be in LISTEN state.
		 * Unclear sequence for MOVER_CONNECT.
		 */
		if (ms->state != NDMP9_MOVER_STATE_LISTEN
		 && ms->state != NDMP9_MOVER_STATE_PAUSED) {
			NDMADR_RAISE_ILLEGAL_STATE("mover_state !LISTEN/PAUSED");
		}
	} else {
		/*
		 * NDMP4 require the Mover be in IDLE state.
		 * This always preceeds both MOVER_LISTEN or
		 * MOVER_CONNECT.
		 */
		if (ms->state != NDMP9_MOVER_STATE_IDLE
		 && ms->state != NDMP9_MOVER_STATE_PAUSED) {
			NDMADR_RAISE_ILLEGAL_STATE("mover_state !IDLE/PAUSED");
		}
	}

	if (request->offset % ms->record_size != 0) {
		NDMADR_RAISE_ILLEGAL_ARGS("off !record_size");
	}

	/* TODO: NDMPv4 subtle semantic changes here */

	/* If a maximum length window is required following a mover transition
	 * to the PAUSED state, a window length of all ones (binary) minus the
	 * current window offset MUST be specified." (NDMPv4 RFC, Section
	 * 3.6.2.2) -- we allow length = NDMP_LENGTH_INFINITY too */

	if (request->length != NDMP_LENGTH_INFINITY
		&& request->length + request->offset != NDMP_LENGTH_INFINITY) {
		if (request->length % ms->record_size != 0) {
			NDMADR_RAISE_ILLEGAL_ARGS("len !record_size");
		}
#if 0
		/* Too pedantic. Sometimes needed (like for testing) */
		if (request->length == 0) {
			NDMADR_RAISE_ILLEGAL_ARGS("length 0");
		}
#endif

		max_len = NDMP_LENGTH_INFINITY - request->offset;
		max_len -= max_len % ms->record_size;
		if (request->length > max_len) {  /* learn math fella */
			NDMADR_RAISE_ILLEGAL_ARGS("length too long");
		}
		end_win = request->offset + request->length;
	} else {
		end_win = NDMP_LENGTH_INFINITY;
	}
	ms->window_offset = request->offset;
	/* record_num should probably be one less than this value, but the spec
	 * says to divide, so we divide */
	ms->record_num = request->offset / ms->record_size;
	ms->window_length = request->length;
	ta->mover_window_end = end_win;
	ta->mover_window_first_blockno = ta->tape_state.blockno.value;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_MOVER_READ
 */
int
ndmp_sxa_mover_read (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;
	struct ndmp9_mover_get_state_reply *ms = &ta->mover_state;

      NDMS_WITH(ndmp9_mover_read)
	ndmta_mover_sync_state (sess);

	if (ms->state != NDMP9_MOVER_STATE_ACTIVE) {
		NDMADR_RAISE_ILLEGAL_STATE("mover_state !ACTIVE");
	}

	if (ms->bytes_left_to_read > 0) {
		NDMADR_RAISE_ILLEGAL_STATE("byte_left_to_read");
	}

	if (ms->data_connection_addr.addr_type != NDMP9_ADDR_TCP) {
		NDMADR_RAISE_ILLEGAL_STATE("mover_addr !TCP");
	}

	if (ms->mode != NDMP9_MOVER_MODE_WRITE) {
		NDMADR_RAISE_ILLEGAL_STATE("mover_mode !WRITE");
	}

	ndmta_mover_read (sess, request->offset, request->length);

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_MOVER_CLOSE
 */
int
ndmp_sxa_mover_close (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;

      NDMS_WITH_VOID_REQUEST(ndmp9_mover_close) {
	if (ta->mover_state.state == NDMP9_MOVER_STATE_IDLE)
		NDMADR_RAISE_ILLEGAL_STATE("mover_state !IDLE");
	}

	ndmta_mover_close (sess);

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_MOVER_SET_RECORD_SIZE
 */
int
ndmp_sxa_mover_set_record_size (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;
	struct ndmp9_mover_get_state_reply *ms = &ta->mover_state;

      NDMS_WITH(ndmp9_mover_set_record_size)
	ndmta_mover_sync_state (sess);

	if (ms->state != NDMP9_MOVER_STATE_IDLE
	 && ms->state != NDMP9_MOVER_STATE_PAUSED)
		NDMADR_RAISE_ILLEGAL_STATE("mover_state !IDLE/PAUSED");

	if (!NDMOS_MACRO_OK_TAPE_REC_LEN(request->record_size))
		NDMADR_RAISE_ILLEGAL_ARGS("!ok_tape_rec_len");

	ta->mover_state.record_size = request->record_size;

	return 0;
      NDMS_ENDWITH
}



#ifndef NDMOS_EFFECT_NO_NDMP3_NOR_NDMP4	/* Surrounds NDMPv[34] MOVER intfs */

static int		mover_connect_common34 (struct ndm_session *sess,
				struct ndmp_xa_buf *xa,
				struct ndmconn *ref_conn,
				ndmp9_addr *addr,
				ndmp9_mover_mode mover_mode);

/*
 * NDMP[34]_MOVER_CONNECT
 */
int
ndmp_sxa_mover_connect (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
#ifndef NDMOS_OPTION_NO_DATA_AGENT
	struct ndm_data_agent *	da = &sess->data_acb;
#endif /* !NDMOS_OPTION_NO_DATA_AGENT */
	struct ndm_tape_agent *	ta = &sess->tape_acb;
	ndmp9_error		error;
	int			will_write;
	char			reason[100];

      NDMS_WITH(ndmp9_mover_connect)

	/* Check args */
	switch (request->mode) {
	default:		NDMADR_RAISE_ILLEGAL_ARGS("mover_mode");
	case NDMP9_MOVER_MODE_READ:
		will_write = 1;
		break;

	case NDMP9_MOVER_MODE_WRITE:
		will_write = 0;
		break;
	}

	switch (request->addr.addr_type) {
	default:		NDMADR_RAISE_ILLEGAL_ARGS("mover_addr_type");
	case NDMP9_ADDR_LOCAL:
#ifdef NDMOS_OPTION_NO_DATA_AGENT
		NDMADR_RAISE_ILLEGAL_ARGS("mover LOCAL w/o local DATA agent");
#endif /* NDMOS_OPTION_NO_DATA_AGENT */
		break;

	case NDMP9_ADDR_TCP:
		break;
	}

	/* Check states -- this should cover everything */
	if (ta->mover_state.state != NDMP9_MOVER_STATE_IDLE)
		NDMADR_RAISE_ILLEGAL_STATE("mover_state !IDLE");
#ifndef NDMOS_OPTION_NO_DATA_AGENT
	if (request->addr.addr_type == NDMP9_ADDR_LOCAL) {
		ndmp9_data_get_state_reply *ds = &da->data_state;

		if (ds->state != NDMP9_DATA_STATE_LISTEN)
			NDMADR_RAISE_ILLEGAL_STATE("data_state !LISTEN");

		if (ds->data_connection_addr.addr_type != NDMP9_ADDR_LOCAL)
			NDMADR_RAISE_ILLEGAL_STATE("data_addr !LOCAL");
	} else {
		if (da->data_state.state != NDMP9_DATA_STATE_IDLE)
			NDMADR_RAISE_ILLEGAL_STATE("data_state !IDLE");
	}
#endif /* !NDMOS_OPTION_NO_DATA_AGENT */

	/* Check that the tape is ready to go */
	error = mover_can_proceed (sess, will_write);
	if (error != NDMP9_NO_ERR) NDMADR_RAISE(error, "!mover_can_proceed");

	/*
	 * Check image stream state -- should already be reflected
	 * in the mover and data states. This extra check gives
	 * us an extra measure of robustness and sanity
	 * check on the implementation.
	 */
	error = ndmis_audit_tape_connect (sess, request->addr.addr_type, reason);
	if (error != NDMP9_NO_ERR) NDMADR_RAISE(error, reason);

	error = ndmis_tape_connect (sess, &request->addr, reason);
	if (error != NDMP9_NO_ERR) NDMADR_RAISE(error, reason);

	ta->mover_state.data_connection_addr = request->addr;
	/* alt: ta->....data_connection_addr = sess->...peer_addr */

	error = ndmta_mover_connect (sess, request->mode);
	if (error != NDMP9_NO_ERR) {
		/* TODO: belay ndmis_tape_connect() */
		NDMADR_RAISE(error, "!mover_connect");
	}

	return 0;
      NDMS_ENDWITH
}
#endif /* !NDMOS_EFFECT_NO_NDMP3_NOR_NDMP4  Surrounds NDMPv[34] MOVER intfs */



/*
 * NDMPx_MOVER helper routines
 */

/*
 * MOVER can only proceed from IDLE->LISTEN or PAUSED->ACTIVE
 * if the tape drive is ready.
 */

static ndmp9_error
mover_can_proceed (struct ndm_session *sess, int will_write)
{
	struct ndm_tape_agent *		ta = &sess->tape_acb;

	ndmos_tape_sync_state(sess);
	if (ta->tape_state.state != NDMP9_TAPE_STATE_OPEN)
		return NDMP9_DEV_NOT_OPEN_ERR;

	if (will_write && !NDMTA_TAPE_IS_WRITABLE(ta))
		return NDMP9_PERMISSION_ERR;

	return NDMP9_NO_ERR;
}

#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */	/* Surrounds MOVER intfs */




#ifndef NDMOS_OPTION_NO_CONTROL_AGENT		/* Surrounds NOTIFY intfs */
/*
 * NDMPx_NOTIFY Interfaces
 ****************************************************************
 */




/*
 * NDMP[234]_NOTIFY_DATA_HALTED
 */
int
ndmp_sxa_notify_data_halted (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_control_agent *ca = &sess->control_acb;

      NDMS_WITH_NO_REPLY(ndmp9_notify_data_halted)
	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	ca->pending_notify_data_halted++;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_NOTIFY_CONNECTED
 */
int
ndmp_sxa_notify_connected (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
      NDMS_WITH_NO_REPLY(ndmp9_notify_connected)
	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;
	/* Just ignore? */
	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_NOTIFY_MOVER_HALTED
 */
int
ndmp_sxa_notify_mover_halted (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_control_agent *	ca = &sess->control_acb;

      NDMS_WITH_NO_REPLY(ndmp9_notify_mover_halted)
	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	ca->pending_notify_mover_halted++;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_NOTIFY_MOVER_PAUSED
 */
int
ndmp_sxa_notify_mover_paused (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_control_agent *	ca = &sess->control_acb;

      NDMS_WITH_NO_REPLY(ndmp9_notify_mover_paused)
	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	ca->pending_notify_mover_paused++;
	ca->last_notify_mover_paused.reason = request->reason;
	ca->last_notify_mover_paused.seek_position = request->seek_position;

	return 0;
      NDMS_ENDWITH
}




/*
 * NDMP[234]_NOTIFY_DATA_READ
 */
int
ndmp_sxa_notify_data_read (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_control_agent *	ca = &sess->control_acb;

      NDMS_WITH_NO_REPLY(ndmp9_notify_data_read)
	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	ca->pending_notify_data_read++;
	ca->last_notify_data_read.offset = request->offset;
	ca->last_notify_data_read.length = request->length;

	return 0;
      NDMS_ENDWITH
}
#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */	/* Surrounds NOTIFY intfs */




#ifndef NDMOS_OPTION_NO_CONTROL_AGENT		/* Surrounds LOG intfs */
/*
 * NDMPx_LOG Interfaces
 ****************************************************************
 */



/*
 * NDMP[234]_LOG_FILE
 */
int
ndmp_sxa_log_file (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	char			prefix[32];
	char *			tag;
	int			lev = 0;

	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	NDMS_WITH_NO_REPLY(ndmp9_log_file)
		switch (request->recovery_status) {
		case NDMP9_RECOVERY_SUCCESSFUL:
			tag =  "OK";
			lev = 1;
			break;

		case NDMP9_RECOVERY_FAILED_PERMISSION:
			tag =  "Bad Permission";
			break;

		case NDMP9_RECOVERY_FAILED_NOT_FOUND:
			tag =  "Not found";
			break;

		case NDMP9_RECOVERY_FAILED_NO_DIRECTORY:
			tag =  "No directory";
			break;

		case NDMP9_RECOVERY_FAILED_OUT_OF_MEMORY:
			tag =  "Out of mem";
			break;

		case NDMP9_RECOVERY_FAILED_IO_ERROR:
			tag =  "I/O error";
			break;

		case NDMP9_RECOVERY_FAILED_UNDEFINED_ERROR:
			tag =  "General error";
			break;

		default:
			tag =  "n";
			break;
		}

		/* count the notification and whether it is good news or not */
		ca->recover_log_file_count++;
		if (lev == 1) {
		    ca->recover_log_file_ok++;
		} else {
		    ca->recover_log_file_error++;
		}

		sprintf (prefix, "%cLF", ref_conn->chan.name[1]);

		ndmalogf (sess, prefix, lev, "%s: %s", tag, request->name);
	NDMS_ENDWITH

	return 0;
}




#ifndef NDMOS_OPTION_NO_NDMP2
/*
 * NDMP2_LOG_LOG
 */
int
ndmp2_sxa_log_log (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	char			prefix[32];
	char *			tag;
	int			lev;

	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	NDMS_WITH_NO_REPLY(ndmp2_log_log)
		tag =  "n";
		lev = 1;

		sprintf (prefix, "%cLM%s", ref_conn->chan.name[1], tag);

		ndmalogf (sess, prefix, lev, "LOG_LOG: '%s'", request->entry);
	NDMS_ENDWITH

	return 0;
}

/*
 * NDMP2_LOG_DEBUG
 */
int
ndmp2_sxa_log_debug (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	char			prefix[32];
	char *			tag;
	int			lev;

	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	NDMS_WITH_NO_REPLY(ndmp2_log_debug)
		tag =  "d";
		lev = 2;

		sprintf (prefix, "%cLM%s", ref_conn->chan.name[1], tag);

		ndmalogf (sess, prefix, lev, "LOG_DEBUG: '%s'", request->message);
	NDMS_ENDWITH

	return 0;
}

#endif /* !NDMOS_OPTION_NO_NDMP2 */




/*
 * NDMP[34]_LOG_MESSAGE
 */
int
ndmp_sxa_log_message (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	char			prefix[32];
	char *			tag;
	int			lev;

	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	NDMS_WITH_NO_REPLY(ndmp9_log_message)
		switch (request->log_type) {
		case NDMP9_LOG_NORMAL:
			tag =  "n";
			lev = 1;
			break;

		case NDMP9_LOG_DEBUG:
			tag =  "d";
			lev = 2;
			break;

		case NDMP9_LOG_ERROR:
			tag = "e";
			lev = 0;
			break;

		case NDMP9_LOG_WARNING:
			tag = "w";
			lev = 0;
			break;

		default:
			tag = "?";
			lev = 0;
			break;
		}

		sprintf (prefix, "%cLM%s", ref_conn->chan.name[1], tag);

		//ndmalogf (sess, prefix, lev, "LOG_MESSAGE: '%s'", request->entry);
		ndmalogf (sess, prefix, 0, "%s", request->entry);
	NDMS_ENDWITH

	return 0;
}

#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */	/* Surrounds LOG intfs */




#ifndef NDMOS_OPTION_NO_CONTROL_AGENT		/* Surrounds FH intfs */
/*
 * NDMPx_FH Interfaces
 ****************************************************************
 */




/*
 * NDMP2_FH_ADD_UNIX_PATH
 * NDMP[34]_FH_ADD_FILE
 */
int
ndmp_sxa_fh_add_file (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	struct ndmlog *		ixlog = &ca->job.index_log;
	int			tagc = ref_conn->chan.name[1];
	unsigned int		i;
	ndmp9_file *		file;

	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	NDMS_WITH_NO_REPLY(ndmp9_fh_add_file)
		for (i = 0; i < request->files.files_len; i++) {
			file = &request->files.files_val[i];

			ndmfhdb_add_file (ixlog, tagc,
				file->unix_path, &file->fstat);
		}
	NDMS_ENDWITH

	return 0;
}




/*
 * NDMP2_FH_ADD_UNIX_DIR
 * NDMP[34]_FH_ADD_DIR
 */
int
ndmp_sxa_fh_add_dir (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	struct ndmlog *		ixlog = &ca->job.index_log;
	int			tagc = ref_conn->chan.name[1];
	char *			raw_name;
	unsigned int		i;
	ndmp9_dir *		dir;

	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	NDMS_WITH_NO_REPLY(ndmp9_fh_add_dir)
		for (i = 0; i < request->dirs.dirs_len; i++) {
			dir = &request->dirs.dirs_val[i];

			raw_name = dir->unix_name;

			switch (ca->job.n_dir_entry) {
			case 0:
				if (strcmp (raw_name, ".") == 0) {
					/* goodness */
					ndmfhdb_add_dirnode_root (ixlog,
						tagc, dir->node);
					ca->job.root_node = dir->node;
				} else {
					/* ungoodness */
					ndmalogf (sess, 0, 0,
						"WARNING: First add_dir "
						"entry is non-conforming");
				}
				break;

			case 1:
				if (strcmp (raw_name, "..") == 0
				 && dir->parent == dir->node
				 && dir->node == ca->job.root_node) {
					/* goodness */
				} else {
					/* ungoodness */
					/* NetApp is non-conforming */
					/* ndmalogf (sess, 0, 0,
						"WARNING: Second add_dir "
						"entry is non-conforming"); */
				}
				break;

			default:
				break;
			}

			ndmfhdb_add_dir (ixlog, tagc,
				dir->unix_name, dir->parent, dir->node);

			ca->job.n_dir_entry++;
		}
	NDMS_ENDWITH

	return 0;
}




/*
 * NDMP2_FH_ADD_UNIX_NODE
 * NDMP[34]_FH_ADD_NODE
 */
int
ndmp_sxa_fh_add_node (struct ndm_session *sess,
  struct ndmp_xa_buf *xa, struct ndmconn *ref_conn)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	struct ndmlog *		ixlog = &ca->job.index_log;
	int			tagc = ref_conn->chan.name[1];
	unsigned int		i;
	ndmp9_node *		node;

	xa->reply.flags |= NDMNMB_FLAG_NO_SEND;

	NDMS_WITH_NO_REPLY(ndmp9_fh_add_node)
		for (i = 0; i < request->nodes.nodes_len; i++) {
			node = &request->nodes.nodes_val[i];

			ndmfhdb_add_node (ixlog, tagc,
				node->fstat.node.value, &node->fstat);
		}
	NDMS_ENDWITH

	return 0;
}
#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */	/* Surrounds FH intfs */



/*
 * Common helper interfaces
 ****************************************************************
 * These do complicated state checks which are called from
 * several of the interfaces above.
 */









#ifndef NDMOS_OPTION_NO_TAPE_AGENT
/*
 * Shortcut for DATA->MOVER READ requests when NDMP9_ADDR_LOCAL
 * (local MOVER). This is implemented here because of the
 * state (sanity) checks. This should track the
 * NDMP9_MOVER_READ stanza in ndma_dispatch_request().
 */

int
ndmta_local_mover_read (struct ndm_session *sess,
  unsigned long long offset, unsigned long long length)
{
	struct ndm_tape_agent *	ta = &sess->tape_acb;
	struct ndmp9_mover_get_state_reply *ms = &ta->mover_state;
	char *			errstr = 0;

	if (ms->state != NDMP9_MOVER_STATE_ACTIVE
	 && ms->state != NDMP9_MOVER_STATE_LISTEN) {
		errstr = "mover_state !ACTIVE";
		goto senderr;
	}
	if (ms->bytes_left_to_read > 0) {
		errstr = "byte_left_to_read";
		goto senderr;
	}
	if (ms->data_connection_addr.addr_type != NDMP9_ADDR_LOCAL) {
		errstr = "mover_addr !LOCAL";
		goto senderr;
	}
	if (ms->mode != NDMP9_MOVER_MODE_WRITE) {
		errstr = "mover_mode !WRITE";
		goto senderr;
	}

	ms->seek_position = offset;
	ms->bytes_left_to_read = length;
	ta->mover_want_pos = offset;

	return 0;

  senderr:
	if (errstr) {
		ndmalogf (sess, 0, 2, "local_read error why=%s", errstr);
	}

	return -1;
}
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */







/*
 * Dispatch Version Table and Dispatch Request Tables (DVT/DRT)
 ****************************************************************
 */

struct ndm_dispatch_request_table *
ndma_drt_lookup (struct ndm_dispatch_version_table *dvt,
  unsigned protocol_version, unsigned message)
{
	struct ndm_dispatch_request_table *	drt;

	for (; dvt->protocol_version >= 0; dvt++) {
		if (dvt->protocol_version == (int)protocol_version)
			break;
	}

	if (dvt->protocol_version < 0)
		return 0;

	for (drt = dvt->dispatch_request_table; drt->message; drt++) {
		if (drt->message == message)
			return drt;
	}

	return 0;
}

struct ndm_dispatch_request_table ndma_dispatch_request_table_v0[] = {
   { NDMP0_CONNECT_OPEN,
     NDM_DRT_FLAG_OK_NOT_CONNECTED+NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_connect_open,
   },
   { NDMP0_CONNECT_CLOSE,
     NDM_DRT_FLAG_OK_NOT_CONNECTED+NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_connect_close,
   },
#ifndef NDMOS_OPTION_NO_CONTROL_AGENT		/* Surrounds NOTIFY intfs */
   { NDMP0_NOTIFY_CONNECTED,
     0,
     ndmp_sxa_notify_connected,
   },
#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */	/* Surrounds NOTIFY intfs */
   {0}
};



#ifndef NDMOS_OPTION_NO_NDMP2
struct ndm_dispatch_request_table ndma_dispatch_request_table_v2[] = {
   { NDMP2_CONFIG_GET_BUTYPE_ATTR,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp2_sxa_config_get_butype_attr
   },
   { NDMP2_LOG_LOG,	0,	ndmp2_sxa_log_log },
   { NDMP2_LOG_DEBUG,	0,	ndmp2_sxa_log_debug },
   {0}
};
#endif /* !NDMOS_OPTION_NO_NDMP2 */

#ifndef NDMOS_OPTION_NO_NDMP3
struct ndm_dispatch_request_table ndma_dispatch_request_table_v3[] = {
   {0}
};
#endif /* !NDMOS_OPTION_NO_NDMP3 */

#ifndef NDMOS_OPTION_NO_NDMP4
struct ndm_dispatch_request_table ndma_dispatch_request_table_v4[] = {
   {0}
};
#endif /* !NDMOS_OPTION_NO_NDMP4 */


struct ndm_dispatch_request_table ndma_dispatch_request_table_v9[] = {
   { NDMP9_CONNECT_OPEN,
     NDM_DRT_FLAG_OK_NOT_CONNECTED+NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_connect_open
   },
   { NDMP9_CONNECT_CLIENT_AUTH,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_connect_client_auth
   },
   { NDMP9_CONNECT_CLOSE,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_connect_close
   },
   { NDMP9_CONNECT_SERVER_AUTH,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_connect_server_auth
   },
   { NDMP9_CONFIG_GET_HOST_INFO,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_config_get_info
   },
   { NDMP9_CONFIG_GET_CONNECTION_TYPE,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_config_get_info
   },
   { NDMP9_CONFIG_GET_AUTH_ATTR,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_config_get_auth_attr
   },
   { NDMP9_CONFIG_GET_BUTYPE_INFO,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_config_get_info
   },
   { NDMP9_CONFIG_GET_FS_INFO,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_config_get_info
   },
   { NDMP9_CONFIG_GET_TAPE_INFO,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_config_get_info
   },
   { NDMP9_CONFIG_GET_SCSI_INFO,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_config_get_info
   },
   { NDMP9_CONFIG_GET_SERVER_INFO,
     NDM_DRT_FLAG_OK_NOT_AUTHORIZED,
     ndmp_sxa_config_get_info
   },
#ifndef NDMOS_OPTION_NO_ROBOT_AGENT		/* Surrounds SCSI intfs */
   { NDMP9_SCSI_OPEN,			0, ndmp_sxa_scsi_open },
   { NDMP9_SCSI_CLOSE,			0, ndmp_sxa_scsi_close },
   { NDMP9_SCSI_GET_STATE,		0, ndmp_sxa_scsi_get_state },
   { NDMP9_SCSI_SET_TARGET,		0, ndmp_sxa_scsi_set_target },
   { NDMP9_SCSI_RESET_DEVICE,		0, ndmp_sxa_scsi_reset_device },
   { NDMP9_SCSI_RESET_BUS,		0, ndmp_sxa_scsi_reset_bus },
   { NDMP9_SCSI_EXECUTE_CDB,		0, ndmp_sxa_scsi_execute_cdb },
#endif /* !NDMOS_OPTION_NO_ROBOT_AGENT */	/* Surrounds SCSI intfs */
#ifndef NDMOS_OPTION_NO_TAPE_AGENT		/* Surrounds TAPE intfs */
   { NDMP9_TAPE_OPEN,			0, ndmp_sxa_tape_open },
   { NDMP9_TAPE_CLOSE,			0, ndmp_sxa_tape_close },
   { NDMP9_TAPE_GET_STATE,		0, ndmp_sxa_tape_get_state },
   { NDMP9_TAPE_MTIO,			0, ndmp_sxa_tape_mtio },
   { NDMP9_TAPE_WRITE,			0, ndmp_sxa_tape_write },
   { NDMP9_TAPE_READ,			0, ndmp_sxa_tape_read },
   { NDMP9_TAPE_EXECUTE_CDB,		0, ndmp_sxa_tape_execute_cdb },
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */	/* Surrounds TAPE intfs */
#ifndef NDMOS_OPTION_NO_DATA_AGENT		/* Surrounds DATA intfs */
   { NDMP9_DATA_GET_STATE,		0, ndmp_sxa_data_get_state },
   { NDMP9_DATA_START_BACKUP,		0, ndmp_sxa_data_start_backup },
   { NDMP9_DATA_START_RECOVER,		0, ndmp_sxa_data_start_recover },
   { NDMP9_DATA_ABORT,			0, ndmp_sxa_data_abort },
   { NDMP9_DATA_GET_ENV,		0, ndmp_sxa_data_get_env },
   { NDMP9_DATA_STOP,			0, ndmp_sxa_data_stop },
#ifndef NDMOS_EFFECT_NO_NDMP3_NOR_NDMP4	/* Surrounds NDMPv[34] DATA intfs */
#ifdef notyet
   { NDMP9_DATA_LISTEN,			0, ndmp_sxa_data_listen },
#endif /* notyet */
   { NDMP9_DATA_CONNECT,		0, ndmp_sxa_data_connect },
#endif /* NDMOS_EFFECT_NO_NDMP3_NOR_NDMP4  Surrounds NDMPv[34] DATA intfs */
   { NDMP9_DATA_START_RECOVER_FILEHIST,	0,
				ndmp_sxa_data_start_recover_filehist },
#endif /* !NDMOS_OPTION_NO_DATA_AGENT */	/* Surrounds DATA intfs */
#ifndef NDMOS_OPTION_NO_CONTROL_AGENT		/* Surrounds NOTIFY intfs */
   { NDMP9_NOTIFY_DATA_HALTED,		0, ndmp_sxa_notify_data_halted },
   { NDMP9_NOTIFY_CONNECTED,		0, ndmp_sxa_notify_connected },
   { NDMP9_NOTIFY_MOVER_HALTED,		0, ndmp_sxa_notify_mover_halted },
   { NDMP9_NOTIFY_MOVER_PAUSED,		0, ndmp_sxa_notify_mover_paused },
   { NDMP9_NOTIFY_DATA_READ,		0, ndmp_sxa_notify_data_read },
#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */	/* Surrounds NOTIFY intfs */
#ifndef NDMOS_OPTION_NO_CONTROL_AGENT		/* Surrounds LOG intfs */
   { NDMP9_LOG_FILE,			0, ndmp_sxa_log_file },
   { NDMP9_LOG_MESSAGE,			0, ndmp_sxa_log_message },
#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */	/* Surrounds LOG intfs */
#ifndef NDMOS_OPTION_NO_CONTROL_AGENT		/* Surrounds FH intfs */
   { NDMP9_FH_ADD_FILE,			0, ndmp_sxa_fh_add_file },
   { NDMP9_FH_ADD_DIR,			0, ndmp_sxa_fh_add_dir },
   { NDMP9_FH_ADD_NODE,			0, ndmp_sxa_fh_add_node },
#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */	/* Surrounds FH intfs */
#ifndef NDMOS_OPTION_NO_TAPE_AGENT		/* Surrounds MOVER intfs */
   { NDMP9_MOVER_GET_STATE,		0, ndmp_sxa_mover_get_state },
   { NDMP9_MOVER_LISTEN,		0, ndmp_sxa_mover_listen },
   { NDMP9_MOVER_CONTINUE,		0, ndmp_sxa_mover_continue },
   { NDMP9_MOVER_ABORT,			0, ndmp_sxa_mover_abort },
   { NDMP9_MOVER_STOP,			0, ndmp_sxa_mover_stop },
   { NDMP9_MOVER_SET_WINDOW,		0, ndmp_sxa_mover_set_window },
   { NDMP9_MOVER_READ,			0, ndmp_sxa_mover_read },
   { NDMP9_MOVER_CLOSE,			0, ndmp_sxa_mover_close },
   { NDMP9_MOVER_SET_RECORD_SIZE,	0, ndmp_sxa_mover_set_record_size },
   { NDMP9_MOVER_CONNECT,		0, ndmp_sxa_mover_connect },
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */	/* Surrounds MOVER intfs */
   {0}
};


struct ndm_dispatch_version_table ndma_dispatch_version_table[] = {
	{ 0,		ndma_dispatch_request_table_v0 },
#ifndef NDMOS_OPTION_NO_NDMP2
	{ NDMP2VER,	ndma_dispatch_request_table_v2 },
#endif /* !NDMOS_OPTION_NO_NDMP2 */
#ifndef NDMOS_OPTION_NO_NDMP3
	{ NDMP3VER,	ndma_dispatch_request_table_v3 },
#endif /* !NDMOS_OPTION_NO_NDMP3 */
#ifndef NDMOS_OPTION_NO_NDMP4
	{ NDMP4VER,	ndma_dispatch_request_table_v4 },
#endif /* !NDMOS_OPTION_NO_NDMP4 */
	{ NDMP9VER,	ndma_dispatch_request_table_v9 },

	{ -1 }
};
