Imported Upstream version 3.1.0
[debian/amanda] / ndmp-src / ndma_cops_backreco.c
diff --git a/ndmp-src/ndma_cops_backreco.c b/ndmp-src/ndma_cops_backreco.c
new file mode 100644 (file)
index 0000000..095c378
--- /dev/null
@@ -0,0 +1,1103 @@
+/*
+ * 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 "util.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 */