Blob Blame History Raw
/*
 * 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:
 *
 */


#include "ndmagents.h"
#include "amutil.h"


#ifndef NDMOS_OPTION_NO_CONTROL_AGENT

int ndmca_monitor_backup_tape_tcp (struct ndm_session *sess);
int ndmca_monitor_recover_tape_tcp (struct ndm_session *sess);
int ndmca_monitor_shutdown_tape_tcp (struct ndm_session *sess);

int
ndmca_op_create_backup (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			rc;

	ca->tape_mode = NDMP9_TAPE_RDWR_MODE;
	ca->mover_mode = NDMP9_MOVER_MODE_READ;
	ca->is_label_op = 0;

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

	rc = ndmca_data_start_backup (sess);
	if (rc == 0) {
	    rc = ndmca_monitor_startup (sess);
	    if (rc == 0) {
		rc = ndmca_monitor_backup (sess);
	    }
	}

	if (rc == 0)
	    rc = ndmca_monitor_shutdown (sess);
	else
	    ndmca_monitor_shutdown (sess);

	ndmca_media_tattle (sess);

	return rc;
}

int
ndmca_op_recover_files (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			rc;

	ca->tape_mode = NDMP9_TAPE_READ_MODE;
	ca->mover_mode = NDMP9_MOVER_MODE_WRITE;
	ca->is_label_op = 0;

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

	rc = ndmca_data_start_recover (sess);
	if (rc == 0) {
		rc = ndmca_monitor_startup (sess);
		if (rc == 0) {
			rc = ndmca_monitor_recover (sess);
		}
	}

	if (rc == 0)
	    rc = ndmca_monitor_shutdown (sess);
	else
	    ndmca_monitor_shutdown (sess);

	if (rc == 0) {
	    if (ca->recover_log_file_count > 0) {
		struct ndm_control_agent *ca = &sess->control_acb;
		int		    n_nlist = ca->job.nlist_tab.n_nlist;

		ndmalogf (sess, 0, 0,
			  "LOG_FILE messages: %d OK, %d ERROR, total %d of %d",
			  ca->recover_log_file_ok,
			  ca->recover_log_file_error,
			  ca->recover_log_file_count,
			  n_nlist);
		if (ca->recover_log_file_ok < n_nlist) {
		    rc = 1;
		}
	    } else {
		ndmalogf (sess, 0, 1,
			  "DATA did not report any LOG_FILE messages");
	    }
	}

	if(!ca->job.tape_tcp)
		ndmca_media_tattle (sess);

	return rc;
}

int
ndmca_op_recover_fh (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			rc;

	ca->tape_mode = NDMP9_TAPE_READ_MODE;
	ca->mover_mode = NDMP9_MOVER_MODE_WRITE;
	ca->is_label_op = 0;

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

	rc = ndmca_data_start_recover_filehist (sess);
	if (rc == 0) {
		rc = ndmca_monitor_startup (sess);
		if (rc == 0) {
			rc = ndmca_monitor_recover (sess);
		}
	}

	if (rc == 0)
	    rc = ndmca_monitor_shutdown (sess);
	else
	    ndmca_monitor_shutdown (sess);

	ndmca_media_tattle (sess);

	return rc;
}

char *ndmca_data_est(struct ndm_control_agent *ca)
{
    char *estb;
    static char estb_buf[64];

    estb = 0;
    if (ca->data_state.est_bytes_remain.valid &&
	(ca->data_state.est_bytes_remain.value >= 1024)) {
	snprintf(estb_buf,
		 sizeof (estb_buf),
		 " left %lldKB",
		 ca->data_state.est_bytes_remain.value/1024LL);
	estb = estb_buf;
    }

    return estb;
}

int
ndmca_monitor_backup (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			count;
	ndmp9_data_state	ds;
	ndmp9_mover_state	ms;
	char *estb;

	if (ca->job.tape_tcp) {
		return ndmca_monitor_backup_tape_tcp(sess);
	}

	ndmalogf (sess, 0, 3, "Monitoring backup");

	for (count = 0; count < 10; count++) {
		ndmca_mon_wait_for_something (sess, count <= 1 ? 30 : 10);
		if (ndmca_monitor_get_states(sess) < 0)
		    break;

#if 0
		if (count > 2)
			ndmca_mon_show_states(sess);
#endif

		ds = ca->data_state.state;
		ms = ca->mover_state.state;

		estb = ndmca_data_est(ca);

		ndmalogf (sess, 0, 1,
			  "DATA: bytes %lldKB%s  MOVER: written %lldKB record %d",
			  ca->data_state.bytes_processed/1024LL,
			  estb ? estb : "",
			  ca->mover_state.bytes_moved/1024LL,
			  ca->mover_state.record_num);

		if (ds == NDMP9_DATA_STATE_ACTIVE
		 && ms == NDMP9_MOVER_STATE_ACTIVE) {
			count = 0;
			continue;
		}

		/*
		 * Check MOVER for needed tape change during DATA_FLOW_TO_TAPE.
		 * Have to do this before checking DATA. Even if DATA halted,
		 * MOVER may be holding unwritten data. Have to perform
		 * the tape change.
		 */
		if (ms == NDMP9_MOVER_STATE_PAUSED) {
			ndmp9_mover_pause_reason	pr;

			pr = ca->mover_state.pause_reason;

			if (!ca->pending_notify_mover_paused) {
				/* count=count */
				continue;		/* wait for notice */
			}

			ca->pending_notify_mover_paused = 0;

			ndmalogf (sess, 0, 3, "Mover paused, reason=%s",
					ndmp9_mover_pause_reason_to_str (pr));

			/* backups are different then recoverys... When
                         * we reach the end of a window, we signal EOW
                         * except in V2 where we signal EOF. EOM occurs
			 * at EOT (or EOF does).
			 * This is based on reading comments in the email
			 * archives...
			 */
			if ((pr == NDMP9_MOVER_PAUSE_EOM) ||
			    (pr == NDMP9_MOVER_PAUSE_EOW)) {
				if (ndmca_monitor_load_next(sess) == 0) {
					/* count=count */
					continue;	/* Happy */
				}
				/* Something went wrong with tape change. */
			} else if ((sess->plumb.tape->protocol_version <= 2) &&
				   pr == NDMP9_MOVER_PAUSE_EOF) {
				if (ndmca_monitor_load_next(sess) == 0) {
					/* count=count */
					continue;	/* Happy */
				}
				/* Something went wrong with tape change. */
			} else {
				/* All other pause reasons
				 * are critically bogus. */

			}
			ndmalogf (sess, 0, 0,
				"Operation paused w/o remedy, cancelling");
			ndmca_mover_abort (sess);
			return -1;
		}

		/*
		 * If DATA has halted, the show is over.
		 */
		if (ds == NDMP9_DATA_STATE_HALTED) {
			if (ms != NDMP9_MOVER_STATE_HALTED) {
				ndmalogf (sess, 0, 3,
					"DATA halted, MOVER active");
				/*
				 * MOVER still occupied. It might be a
				 * heartbeat away from asking for another
				 * tape. Give it a chance.
				 */
				continue;
			}

			ndmalogf (sess, 0, 2, "Operation done, cleaning up");

			ndmca_monitor_get_post_backup_env (sess);

			return 0;
		}
#if 1
		if (ms == NDMP9_MOVER_STATE_HALTED) {
			if (ds == NDMP9_DATA_STATE_ACTIVE) {
				ndmalogf (sess, 0, 3,
					"MOVER halted, DATA active");
				/*
				 * DATA still occupied.
				 */
				continue;
			}
		}
#endif

		if (ms != NDMP9_MOVER_STATE_ACTIVE && count == 0) {
			/* Not active. Not paused. Something wrong */
			ndmalogf (sess, 0, 0,
				"Operation in unreasonable state, cancelling");
			return -1;
		}
	}

	ndmalogf (sess, 0, 0, "Operation monitoring mishandled, cancelling");
	return -1;
}

int
ndmca_monitor_backup_tape_tcp (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			count;
	ndmp9_data_state	ds;
	char *estb;
	struct ndmlog *		ixlog = &ca->job.index_log;
	char *			pname = get_pname();

	ndmalogf (sess, 0, 3, "Monitoring backup");

	for (count = 0; count < 10; count++) {
		ndmca_mon_wait_for_something (sess, count <= 1 ? 30 : 10);
		if (ndmca_monitor_get_states(sess) < 0)
		    break;

#if 0
		if (count > 2)
			ndmca_mon_show_states(sess);
#endif

		ds = ca->data_state.state;

		estb = ndmca_data_est(ca);

		ndmalogf (sess, 0, 1,
			  "DATA: bytes %lldKB%s",
			  ca->data_state.bytes_processed/1024LL,
			  estb ? estb : "");

		if (strcmp(pname, "amndmjob") == 0) {
			ndmlogf (ixlog, "DATA SIZE", 0, "%lldKB",
				 ca->data_state.bytes_processed/1024LL);
		}

		if (ds == NDMP9_DATA_STATE_ACTIVE) {
			count = 0;
			continue;
		}

		/*
		 * If DATA has halted, the show is over.
		 */
		if (ds == NDMP9_DATA_STATE_HALTED) {
			ndmalogf (sess, 0, 2, "Operation done, cleaning up");

			ndmca_monitor_get_post_backup_env (sess);

			return 0;
		}
	}

	ndmalogf (sess, 0, 0, "Operation monitoring mishandled, cancelling");
	return -1;
}

int
ndmca_monitor_get_post_backup_env (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	struct ndmlog *		ixlog = &ca->job.index_log;
	int			rc, i;
	ndmp9_pval *		pv;

	rc = ndmca_data_get_env (sess);
	if (rc && ca->data_state.error == NDMP9_ILLEGAL_STATE_ERR) {
		ndmalogf (sess, 0, 2, "fetch post backup env failed");
		return 0;
	}
	if (rc) {
		ndmalogf (sess, 0, 0, "fetch post backup env failed");
		return -1;
	}

	for (i = 0; i < ca->job.result_env_tab.n_env; i++) {
		pv = &ca->job.result_env_tab.env[i];

		ndmlogf (ixlog, "DE", 0, "%s=%s", pv->name, pv->value);
	}

	return 0;
}

int
ndmca_monitor_recover (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			count, rc;
	ndmp9_data_state	ds;
	ndmp9_mover_state	ms;
	char *estb;
	int last_state_print = 0;

	if (ca->job.tape_tcp) {
		return (ndmca_monitor_recover_tape_tcp(sess));
	}

	ndmalogf (sess, 0, 3, "Monitoring recover");

	for (count = 0; count < 10; count++) {
		if (ca->pending_notify_data_read) {
			ca->pending_notify_data_read = 0;

			rc = ndmca_mover_read (sess,
				ca->last_notify_data_read.offset,
				ca->last_notify_data_read.length);
			if (rc) {
				ndmalogf (sess, 0, 0, "data-read failed");
				return -1;
			}
			if (count < 5)
				continue;
		}

		ndmca_mon_wait_for_something (sess, count <= 1 ? 30 : 10);

		if (ndmca_monitor_get_states(sess) < 0)
		    break;

#if 0
		if (count > 2)
			ndmca_mon_show_states(sess);
#endif

		ds = ca->data_state.state;
		ms = ca->mover_state.state;

		estb = ndmca_data_est(ca);

		if ((ds != NDMP9_DATA_STATE_ACTIVE) ||
		    (ms != NDMP9_MOVER_STATE_ACTIVE) ||
		    ((time(0) - last_state_print) >= 5)) {

		    ndmalogf (sess, 0, 1,
			      "DATA: bytes %lldKB%s  MOVER: read %lldKB record %d",
			      ca->data_state.bytes_processed/1024LL,
			      estb ? estb : "",
			      ca->mover_state.bytes_moved/1024LL,
			      ca->mover_state.record_num);
		    last_state_print = time(0);
		}

		if (ds == NDMP9_DATA_STATE_ACTIVE
		 && ms == NDMP9_MOVER_STATE_ACTIVE) {
			count = 0;
			continue;
		}

		/*
		 * Check MOVER for needed tape change during DATA_FLOW_TO_TAPE.
		 * Have to do this before checking DATA. Even if DATA halted,
		 * MOVER may be holding unwritten data. Have to perform
		 * the tape change.
		 */
		if (ms == NDMP9_MOVER_STATE_PAUSED) {
			ndmp9_mover_pause_reason	pr;

			pr = ca->mover_state.pause_reason;

			if (!ca->pending_notify_mover_paused) {
				/* count=count */
				continue;		/* wait for notice */
			}

			ca->pending_notify_mover_paused = 0;

			ndmalogf (sess, 0, 3, "Mover paused, reason=%s",
					ndmp9_mover_pause_reason_to_str (pr));

			if (((pr == NDMP9_MOVER_PAUSE_EOF) ||
			     (pr == NDMP9_MOVER_PAUSE_SEEK))
			    && (ca->cur_media_ix+1 == ca->job.media_tab.n_media)) {
				/*
				 * Last tape consumed by tape agent.
				 * The DATA agent may be just shy
				 * of done, but there is no way for
				 * us to tell. So, close the
				 * image stream from the TAPE
				 * agent side, thus indicating
				 * EOF to the DATA agent.
				 */
				ndmalogf (sess, 0, 2, "End of tapes");
				ndmca_mover_close (sess);
				/* count=count */
				continue;
			}

			if (pr == NDMP9_MOVER_PAUSE_EOM
			 || pr == NDMP9_MOVER_PAUSE_EOF) {
				if (ndmca_monitor_load_next(sess) == 0) {
					/* count=count */
					continue;	/* Happy */
				}
				/* Something went wrong with tape change. */
			} else if (pr == NDMP9_MOVER_PAUSE_SEEK) {
				if (ndmca_monitor_seek_tape(sess) == 0) {
					/* count=count */
					continue;	/* Happy */
				}
				/* Something went wrong with tape change. */
			} else {
				/* All other pause reasons
				 * are critically bogus. */
			}
			ndmalogf (sess, 0, 0,
				"Operation paused w/o remedy, cancelling");
			ndmca_mover_abort (sess);
			return -1;
		}

		/*
		 * If DATA has halted, the show is over.
		 */
		if (ds == NDMP9_DATA_STATE_HALTED) {
			if (ms != NDMP9_MOVER_STATE_HALTED) {
				ndmalogf (sess, 0, 3,
					"DATA halted, MOVER active");
				/*
				 * MOVER still occupied. It might
				 * figure it out. Then again, it might
				 * be awaiting a MOVER_READ. The NDMP
				 * design does not provide a state
				 * for awaiting MOVER_READ, so we have
				 * to guess.
				 */
				if (count > 0) {
					ndmca_mover_close(sess);
				}
				continue;
			}

			ndmalogf (sess, 0, 2, "Operation done, cleaning up");

			ndmca_monitor_get_post_backup_env (sess);

			return 0;
		}

		if (ms != NDMP9_MOVER_STATE_ACTIVE && count == 0) {
			/* Not active. Not paused. Something wrong */
			ndmalogf (sess, 0, 0,
				"Operation in unreasonable state, cancelling");
			return -1;
		}
	}

	ndmalogf (sess, 0, 0, "Operation monitoring mishandled, cancelling");
	return -1;
}


int
ndmca_monitor_recover_tape_tcp (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			count;
	ndmp9_data_state	ds;
	char *estb;
	int last_state_print = 0;

	ndmalogf (sess, 0, 3, "Monitoring recover");

	for (count = 0; count < 10; count++) {

		ndmca_mon_wait_for_something (sess, count <= 1 ? 30 : 10);

		if (ndmca_monitor_get_states(sess) < 0)
		    break;

#if 0
		if (count > 2)
			ndmca_mon_show_states(sess);
#endif

		ds = ca->data_state.state;

		estb = ndmca_data_est(ca);

		if ((ds != NDMP9_DATA_STATE_ACTIVE) ||
		    ((time(0) - last_state_print) >= 5)) {

		    ndmalogf (sess, 0, 1,
			      "DATA: bytes %lldKB%s  MOVER: read %lldKB record %d",
			      ca->data_state.bytes_processed/1024LL,
			      estb ? estb : "",
			      ca->mover_state.bytes_moved/1024LL,
			      ca->mover_state.record_num);
		    last_state_print = time(0);
		}

		if (ds == NDMP9_DATA_STATE_ACTIVE) {
			count = 0;
			continue;
		}

		/*
		 * If DATA has halted, the show is over.
		 */
		if (ds == NDMP9_DATA_STATE_HALTED) {
			ndmalogf (sess, 0, 2, "Operation done, cleaning up");

			ndmca_monitor_get_post_backup_env (sess);

			return 0;
		}

	}

	ndmalogf (sess, 0, 0, "Operation monitoring mishandled, cancelling");
	return -1;
}


int
ndmca_backreco_startup (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			rc = 0;

	if (!ca->job.tape_tcp)
		rc = ndmca_op_robot_startup (sess, 1);
	if (rc) return rc;

	rc = ndmca_connect_data_agent(sess);
	if (rc) {
		ndmconn_destruct (sess->plumb.data);
		return rc;
	}

	if (ca->job.tape_tcp) {
		return 0;
	}

	rc = ndmca_connect_tape_agent(sess);
	if (rc) {
		ndmconn_destruct (sess->plumb.tape);
		return rc;
	}

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

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

	ndmca_media_calculate_offsets (sess);

	if (sess->control_acb.swap_connect &&
	    (sess->plumb.tape->protocol_version >= 3)) {
	    if (sess->plumb.tape->protocol_version < 4) {
		rc = ndmca_data_listen (sess);
		if (rc) return rc;

		rc = ndmca_media_set_window_current (sess);
		if (rc) return rc;
	    } else {
		rc = ndmca_media_set_window_current (sess);
		if (rc) return rc;

		rc = ndmca_data_listen (sess);
		if (rc) return rc;
	    }
	} else {
	    if (sess->plumb.tape->protocol_version < 4) {
		rc = ndmca_mover_listen (sess);
		if (rc) return rc;

		rc = ndmca_media_set_window_current (sess);
		if (rc) return rc;
	    } else {
		rc = ndmca_media_set_window_current (sess);
		if (rc) return rc;

		rc = ndmca_mover_listen (sess);
		if (rc) return rc;
	    }
	}

	return 0;
}

int
ndmca_monitor_startup (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	ndmp9_data_state	ds;
	ndmp9_mover_state	ms;
	int			count;

	ndmalogf (sess, 0, 3, "Waiting for operation to start");

	if (ca->job.tape_tcp)
		return 0;

	for (count = 0; count < 10; count++) {
		if (ndmca_monitor_get_states (sess) < 0)
		    break;

		ds = ca->data_state.state;
		if (!ca->job.tape_tcp)
			ms = ca->mover_state.state;
		else
			ms = NDMP9_MOVER_STATE_ACTIVE;

		if (ds == NDMP9_DATA_STATE_ACTIVE
		 && ms == NDMP9_MOVER_STATE_ACTIVE) {
			ndmalogf (sess, 0, 1, "Operation started");
			return 0;
		}

		if (ds == NDMP9_DATA_STATE_HALTED
		    && ms == NDMP9_MOVER_STATE_HALTED) {
			/* operation finished immediately */
			return 0;
		}

		if (ds != NDMP9_DATA_STATE_IDLE
		 && ms != NDMP9_MOVER_STATE_IDLE
		 && ms != NDMP9_MOVER_STATE_LISTEN) {
			ndmalogf (sess, 0, 1,
				  "Operation started in unusual fashion");
			return 0;
		}

		ndmca_mon_wait_for_something (sess, 2);
	}

	ndmalogf (sess, 0, 0, "Operation failed to start");
	return -1;
}

/*
 * Just make sure things get finished
 */
int
ndmca_monitor_shutdown (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	ndmp9_data_state	ds;
	ndmp9_data_halt_reason	dhr;
	ndmp9_mover_state	ms;
	ndmp9_mover_halt_reason	mhr;
	int			count;
	int			finish;

	if (ca->job.tape_tcp) {
		return ndmca_monitor_shutdown_tape_tcp(sess);
	}
	ndmalogf (sess, 0, 3, "Waiting for operation to halt");

	for (count = 0; count < 10; count++) {
		ndmca_mon_wait_for_something (sess, 2);

		if (ndmca_monitor_get_states (sess) < 0)
		    break;

#if 0
		if (count > 2)
			ndmca_mon_show_states(sess);
#endif

		ds = ca->data_state.state;
		ms = ca->mover_state.state;

		if (ds == NDMP9_DATA_STATE_HALTED
		 && ms == NDMP9_MOVER_STATE_HALTED) {
		        dhr = ca->data_state.halt_reason;
			mhr = ca->mover_state.halt_reason;
			break;
		}

		if (count > 2) {
			if (ds != NDMP9_DATA_STATE_HALTED)
				ndmca_data_abort(sess);
			if (ms != NDMP9_MOVER_STATE_HALTED)
				ndmca_mover_abort(sess);
		}
	}

	if (ca->tape_state.error == NDMP9_NO_ERR) {
		ndmca_monitor_unload_last_tape (sess);
	}

	if (count >= 10) {
		ndmalogf (sess, 0, 0,
			"Operation did not halt, something wrong");
	}

	ndmalogf (sess, 0, 2, "Operation halted, stopping");

	ds = ca->data_state.state;
	ms = ca->mover_state.state;
	dhr = ca->data_state.halt_reason;
	mhr = ca->mover_state.halt_reason;

	if ((ds == NDMP9_DATA_STATE_HALTED)
	    && (ms == NDMP9_MOVER_STATE_HALTED)) {
	    if ((dhr == NDMP9_DATA_HALT_SUCCESSFUL) &&
		(mhr == NDMP9_MOVER_HALT_CONNECT_CLOSED)) {
		/* Successful operation */
		ndmalogf (sess, 0, 0, "Operation ended OKAY");
		finish = 0;
	    } else {
		/* Questionable success */
		ndmalogf (sess, 0, 0, "Operation ended questionably");
		finish = 1;
	    }
	} else {
	    ndmalogf (sess, 0, 0, "Operation ended in failure");
	    finish = -1;
	}

	ndmca_data_stop (sess);
	ndmca_mover_stop (sess);

	for (count = 0; count < 10; count++) {
		if (ndmca_monitor_get_states(sess) < 0)
		    break;

#if 0
		if (count > 2)
			ndmca_mon_show_states(sess);
#endif

		ds = ca->data_state.state;
		ms = ca->mover_state.state;

		if (ds == NDMP9_DATA_STATE_IDLE
		 && ms == NDMP9_MOVER_STATE_IDLE) {
			break;
		}
	}

	if (count >= 10) {
		ndmalogf (sess, 0, 0,
			"Operation did not stop, something wrong");
		return -1;
	}

	return finish;
}

int
ndmca_monitor_shutdown_tape_tcp (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	ndmp9_data_state	ds;
	ndmp9_data_halt_reason	dhr;
	int			count;
	int			finish;

	ndmalogf (sess, 0, 3, "Waiting for operation to halt");

	for (count = 0; count < 10; count++) {
		ndmca_mon_wait_for_something (sess, 2);

		if (ndmca_monitor_get_states (sess) < 0)
		    break;

#if 0
		if (count > 2)
			ndmca_mon_show_states(sess);
#endif

		ds = ca->data_state.state;

		if (ds == NDMP9_DATA_STATE_HALTED) {
		        dhr = ca->data_state.halt_reason;
			break;
		}

		if (count > 2) {
			if (ds != NDMP9_DATA_STATE_HALTED)
				ndmca_data_abort(sess);
		}
	}

	if (count >= 10) {
		ndmalogf (sess, 0, 0,
			"Operation did not halt, something wrong");
	}

	ndmalogf (sess, 0, 2, "Operation halted, stopping");

	ds = ca->data_state.state;
	dhr = ca->data_state.halt_reason;

	if (ds == NDMP9_DATA_STATE_HALTED) {
	    if (dhr == NDMP9_DATA_HALT_SUCCESSFUL) {
		/* Successful operation */
		ndmalogf (sess, 0, 0, "Operation ended OKAY");
		finish = 0;
	    } else {
		/* Questionable success */
		ndmalogf (sess, 0, 0, "Operation ended questionably");
		finish = 1;
	    }
	} else {
	    ndmalogf (sess, 0, 0, "Operation ended in failure");
	    finish = -1;
	}

	ndmca_data_stop (sess);

	for (count = 0; count < 10; count++) {
		if (ndmca_monitor_get_states(sess) < 0)
		    break;

#if 0
		if (count > 2)
			ndmca_mon_show_states(sess);
#endif

		ds = ca->data_state.state;

		if (ds == NDMP9_DATA_STATE_IDLE) {
			break;
		}
	}

	if (count >= 10) {
		ndmalogf (sess, 0, 0,
			"Operation did not stop, something wrong");
		return -1;
	}

	return finish;
}

int
ndmca_monitor_get_states (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int rc = 0;

	if (ndmca_data_get_state (sess) < 0)
	    rc = -1;
	if (!ca->job.tape_tcp) {
	    if (ndmca_mover_get_state (sess) < 0)
		rc = -1;
	    ndmca_tape_get_state_no_tattle (sess);
	}

	return rc;
}

int
ndmca_monitor_load_next (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			rc;

	ndmalogf (sess, 0, 1, "Operation requires next tape");

	ndmca_media_capture_mover_window (sess);
	ndmca_media_calculate_offsets (sess);

	if (ca->tape_mode == NDMP9_TAPE_RDWR_MODE) {
	    if (ca->mover_state.pause_reason != NDMP9_MOVER_PAUSE_EOM)
		ndmca_media_write_filemarks (sess);
	    else
		ndmalogf (sess, 0, 1, "At EOM, not writing filemarks");
	}

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

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

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

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

	ndmalogf (sess, 0, 1, "Operation resuming");

	return 0;
}

/* VERY VERY HARD */
int
ndmca_monitor_seek_tape (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			rc;
	unsigned long long	pos;

	pos = ca->last_notify_mover_paused.seek_position;

	ndmalogf (sess, 0, 1, "Operation requires a different tape");

/*	ndmca_media_capture_mover_window (sess);	// !!! */
	ndmca_media_calculate_offsets (sess);

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

	rc = ndmca_media_load_seek (sess, pos);
	if (rc) return rc;

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

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

	ndmalogf (sess, 0, 1, "Operation resuming");

	return 0;
}

int
ndmca_monitor_unload_last_tape (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			rc;

	if (!ca->media_is_loaded)
		return 0;

	ndmca_media_capture_mover_window (sess);
	ndmca_media_calculate_offsets (sess);

	if (ca->tape_mode == NDMP9_TAPE_RDWR_MODE) {
		ndmca_media_write_filemarks (sess);
	}

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

	return 0;
}

int
ndmca_mon_wait_for_something (struct ndm_session *sess, int max_delay_secs)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	int			delta, notices;
	int			time_ref = time(0) + max_delay_secs;

	ndmalogf (sess, 0, 5, "mon_wait_for_something() entered");

	for (;;) {
		delta = time_ref - time(0);
		if (delta <= 0)
			break;

		notices = 0;
		if (ca->pending_notify_data_read) {
			/* leave visible */
			notices++;
		}
		if (ca->pending_notify_data_halted) {
			/* just used to "wake up" */
			ca->pending_notify_data_halted = 0;
			notices++;
		}
		if (ca->pending_notify_mover_paused) {
			/* leave visible */
			notices++;
		}
		if (ca->pending_notify_mover_halted) {
			/* just used to "wake up" */
			ca->pending_notify_mover_halted = 0;
			notices++;
		}

		ndma_session_quantum (sess, notices ? 0 : delta);

		if (notices)
			break;
	}

	ndmalogf (sess, 0, 5, "mon_wait_for_something() happened, resid=%d",
			delta);

	return 0;
}
#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */