/*
 * 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 "wraplib.h"

#ifndef NDMOS_OPTION_NO_DATA_AGENT


/*
 * Initialization and Cleanup
 ****************************************************************
 */

/* Initialize -- Set data structure to know value, ignore current value */
int ndmda_initialize(struct ndm_session* sess)
{
  sess->data_acb = NDMOS_API_MALLOC(sizeof(struct ndm_data_agent));
  if (!sess->data_acb) return -1;
  NDMOS_MACRO_ZEROFILL(sess->data_acb);

  sess->data_acb->data_state.state = NDMP9_DATA_STATE_IDLE;
  ndmchan_initialize(&sess->data_acb->formatter_error, "dfp-error");
  ndmchan_initialize(&sess->data_acb->formatter_wrap, "dfp-wrap");
  ndmchan_initialize(&sess->data_acb->formatter_image, "dfp-image");
  ndmda_fh_initialize(sess);

  return 0;
}

/* Commission -- Get agent ready. Entire session has been initialize()d */
int ndmda_commission(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;

  da->data_state.state = NDMP9_DATA_STATE_IDLE;
  ndmda_fh_commission(sess);

  return 0;
}

/* Decommission -- Discard agent */
int ndmda_decommission(struct ndm_session* sess)
{
  ndmis_data_close(sess);
  ndmda_purge_environment(sess);
  ndmda_purge_nlist(sess);
  ndmda_fh_decommission(sess);
  NDMOS_API_BZERO(sess->data_acb->bu_type, sizeof sess->data_acb->bu_type);

  ndmda_commission(sess);

  return 0;
}

/* Destroy -- destroy agent */
int ndmda_destroy(struct ndm_session* sess)
{
  if (!sess->data_acb) { return 0; }

  if (sess->data_acb->fmt_image_buf) {
    NDMOS_API_FREE(sess->data_acb->fmt_image_buf);
  }
  if (sess->data_acb->fmt_error_buf) {
    NDMOS_API_FREE(sess->data_acb->fmt_error_buf);
  }
  if (sess->data_acb->fmt_wrap_buf) {
    NDMOS_API_FREE(sess->data_acb->fmt_wrap_buf);
  }

  ndmda_fh_destroy(sess);

  NDMOS_API_FREE(sess->data_acb);
  sess->data_acb = NULL;

  return 0;
}

/* Belay -- Cancel partially issued activation/start */
int ndmda_belay(struct ndm_session* sess)
{
  ndmda_fh_belay(sess);
  return ndmda_decommission(sess);
}


/*
 * Semantic actions -- called from ndma_dispatch()
 ****************************************************************
 */

static int add_env(struct ndm_env_table* envtab, char* cmd)
{
  char buf[1024];
  struct ndm_env_entry* entry;

  for (entry = envtab->head; entry; entry = entry->next) {
    snprintf(buf, sizeof(buf) - 1, "%s=%s", entry->pval.name,
             entry->pval.value);
    buf[sizeof(buf) - 1] = '\0';
    ndmda_add_to_cmd(cmd, "-E");
    ndmda_add_to_cmd(cmd, buf);
  }

  return 0;
}

static int add_nlist(struct ndm_nlist_table* nlisttab, char* cmd)
{
  char buf[32];
  struct ndm_nlist_entry* entry;

  for (entry = nlisttab->head; entry; entry = entry->next) {
    ndmda_add_to_cmd(cmd, entry->name.original_path);
    if (entry->name.fh_info.valid == NDMP9_VALIDITY_VALID) {
      snprintf(buf, sizeof(buf), "@%llu", entry->name.fh_info.value);
      ndmda_add_to_cmd(cmd, buf);
    } else {
      ndmda_add_to_cmd(cmd, "@-");
    }
    ndmda_add_to_cmd(cmd, entry->name.destination_path);
  }

  return 0;
}


ndmp9_error ndmda_data_start_backup(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;
  ndmp9_error error = NDMP9_NO_ERR;
  char cmd[NDMDA_MAX_CMD];

  strcpy(cmd, "wrap_");
  strcat(cmd, da->bu_type);

  if (sess->param->log_level > 0) {
    char tmpbuf[40];
    snprintf(tmpbuf, sizeof(tmpbuf), "-d%d", sess->param->log_level);
    ndmda_add_to_cmd(cmd, tmpbuf);
  }

  ndmda_add_to_cmd(cmd, "-c");
  ndmda_add_to_cmd(cmd, "-I#3");
  add_env(&da->env_tab, cmd);

  ndma_send_logmsg(sess, NDMP9_LOG_DEBUG, sess->plumb.data, "CMD: %s", cmd);

  if (ndmda_pipe_fork_exec(sess, cmd, 1) < 0) { return NDMP9_UNDEFINED_ERR; }

  ndmis_data_start(sess, NDMCHAN_MODE_WRITE);

  da->data_state.state = NDMP9_DATA_STATE_ACTIVE;
  da->data_state.operation = NDMP9_DATA_OP_BACKUP;

  return error;
}

ndmp9_error ndmda_data_start_recover(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;
  ndmp9_error error = NDMP9_NO_ERR;
  char cmd[NDMDA_MAX_CMD];

  strcpy(cmd, "wrap_");
  strcat(cmd, da->bu_type);

  if (sess->param->log_level > 0) {
    char tmpbuf[40];
    snprintf(tmpbuf, sizeof(tmpbuf), "-d%d", sess->param->log_level);
    ndmda_add_to_cmd(cmd, tmpbuf);
  }

  ndmda_add_to_cmd(cmd, "-x");
  ndmda_add_to_cmd(cmd, "-I#3");
  add_env(&da->env_tab, cmd);
  add_nlist(&da->nlist_tab, cmd);

  ndma_send_logmsg(sess, NDMP9_LOG_DEBUG, sess->plumb.data, "CMD: %s", cmd);

  if (ndmda_pipe_fork_exec(sess, cmd, 0) < 0) { return NDMP9_UNDEFINED_ERR; }

  ndmis_data_start(sess, NDMCHAN_MODE_READ);

  da->data_state.state = NDMP9_DATA_STATE_ACTIVE;
  da->data_state.operation = NDMP9_DATA_OP_RECOVER;

  return error;
}

ndmp9_error ndmda_data_start_recover_fh(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;
  ndmp9_error error = NDMP9_NO_ERR;
  char cmd[NDMDA_MAX_CMD];

  strcpy(cmd, "wrap_");
  strcat(cmd, da->bu_type);
  ndmda_add_to_cmd(cmd, "-t");
  ndmda_add_to_cmd(cmd, "-I#3");
  add_env(&da->env_tab, cmd);
  add_nlist(&da->nlist_tab, cmd);

  ndma_send_logmsg(sess, NDMP9_LOG_DEBUG, sess->plumb.data, "CMD: %s", cmd);

  if (ndmda_pipe_fork_exec(sess, cmd, 0) < 0) { return NDMP9_UNDEFINED_ERR; }

  ndmis_data_start(sess, NDMCHAN_MODE_READ);

  da->data_state.state = NDMP9_DATA_STATE_ACTIVE;
  da->data_state.operation = NDMP9_DATA_OP_RECOVER_FILEHIST;

  return error;
}

void ndmda_sync_state(struct ndm_session* sess)
{ /* no-op, always accurate */
}

void ndmda_data_abort(struct ndm_session* sess)
{
  ndmda_data_halt(sess, NDMP9_DATA_HALT_ABORTED);
}

void ndmda_sync_environment(struct ndm_session* sess)
{
  /* no-op, always accurate */
}

ndmp9_error ndmda_data_listen(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;

  da->data_state.state = NDMP9_DATA_STATE_LISTEN;
  da->data_state.halt_reason = NDMP9_DATA_HALT_NA;

  return NDMP9_NO_ERR;
}

ndmp9_error ndmda_data_connect(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;

  da->data_state.state = NDMP9_DATA_STATE_CONNECTED;
  da->data_state.halt_reason = NDMP9_DATA_HALT_NA;

  return NDMP9_NO_ERR;
}

void ndmda_data_halt(struct ndm_session* sess, ndmp9_data_halt_reason reason)
{
  struct ndm_data_agent* da = sess->data_acb;

  da->data_state.state = NDMP9_DATA_STATE_HALTED;
  da->data_state.halt_reason = reason;
  da->data_notify_pending = 1;

  ndmda_fh_flush(sess);

  ndmis_data_close(sess);

  ndmchan_cleanup(&da->formatter_image);
  ndmchan_cleanup(&da->formatter_error);
  ndmchan_cleanup(&da->formatter_wrap);

  /* this needs to be better */
  if (da->formatter_pid) {
    sleep(1); /* give gtar a chance to stop by itself */
    kill(da->formatter_pid, SIGTERM);
  }
}

void ndmda_data_stop(struct ndm_session* sess) { ndmda_decommission(sess); }


/*
 * Quantum -- get a bit of work done
 ****************************************************************
 */

int ndmda_quantum(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;
  int did_something = 0; /* did nothing */


  switch (da->data_state.state) {
    default:
      ndmalogf(sess, 0, 0, "BOTCH data state");
      return -1;

    case NDMP9_DATA_STATE_IDLE:
    case NDMP9_DATA_STATE_HALTED:
    case NDMP9_DATA_STATE_CONNECTED:
      break;

    case NDMP9_DATA_STATE_LISTEN:
      switch (sess->plumb.image_stream->data_ep.connect_status) {
        case NDMIS_CONN_LISTEN: /* no connection yet */
          break;

        case NDMIS_CONN_ACCEPTED: /* we're in business */
          /* drum roll please... */
          da->data_state.state = NDMP9_DATA_STATE_CONNECTED;
          /* tah-dah */
          did_something++; /* did something */
          break;

        case NDMIS_CONN_BOTCHED: /* accept() went south */
        default:                 /* ain't suppose to happen */
          ndmda_data_halt(sess, NDMP9_DATA_HALT_CONNECT_ERROR);
          did_something++; /* did something */
          break;
      }
      break;

    case NDMP9_DATA_STATE_ACTIVE:
      did_something |= ndmda_quantum_stderr(sess);
      did_something |= ndmda_quantum_wrap(sess);
      did_something |= ndmda_quantum_image(sess);
      break;
  }

  ndmda_send_notice(sess);

  return did_something;
}

int ndmda_quantum_stderr(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;
  struct ndmchan* ch = &da->formatter_error;
  int did_something = 0;
  char* p;
  char* data;
  char* pend;
  unsigned n_ready;

again:
  n_ready = ndmchan_n_ready(ch);
  if (n_ready == 0) return did_something;

  data = p = &ch->data[ch->beg_ix];
  pend = p + n_ready;

  while (p < pend && *p != '\n') p++;


  if (p < pend && *p == '\n') {
    *p++ = 0;
    ndma_send_logmsg(sess, NDMP9_LOG_NORMAL, sess->plumb.data, "%s", data);
    ch->beg_ix += p - data;
    did_something++;
    goto again;
  }

  if (!ch->eof) return did_something;

  /* content w/o newline, and EOF */
  /* p == pend */
  if (ch->end_ix >= ch->data_size) {
    if (data != ch->data) {
      ndmchan_compress(ch);
      goto again;
    }
    /* that's one huge message */
    p--; /* lose last byte */
  }

  ch->data[ch->end_ix++] = '\n';
  did_something++;
  goto again;
}

int ndmda_quantum_wrap(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;
  struct ndmchan* ch = &da->formatter_wrap;
  int did_something = 0;
  char* p;
  char* data;
  char* pend;
  unsigned n_ready;
  int is_recover = 0;

  switch (da->data_state.operation) {
    default:
      assert(0);
      break;

    case NDMP9_DATA_OP_BACKUP:
      break;

    case NDMP9_DATA_OP_RECOVER:
    case NDMP9_DATA_OP_RECOVER_FILEHIST:
      is_recover = 1;
      break;
  }

again:
  n_ready = ndmchan_n_ready(ch);
  if (n_ready == 0) {
    if (ch->eof && is_recover) {
      ndmda_data_halt(sess, NDMP9_DATA_HALT_SUCCESSFUL);
    }
    return did_something;
  }
  data = p = &ch->data[ch->beg_ix];
  pend = p + n_ready;

  while (p < pend && *p != '\n') p++;


  if (p < pend && *p == '\n') {
    *p++ = 0;
    ndmda_wrap_in(sess, data);
    ch->beg_ix += p - data;
    did_something++;
    goto again;
  }

  if (!ch->eof) return did_something;

  /* content w/o newline, and EOF */
  /* p == pend */
  if (ch->end_ix >= ch->data_size) {
    if (data != ch->data) {
      ndmchan_compress(ch);
      goto again;
    }
    /* that's one huge message */
    p--; /* lose last byte */
  }

  ch->data[ch->end_ix++] = '\n';
  did_something++;
  goto again;
}


int ndmda_quantum_image(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;
  struct ndmchan* from_chan;
  struct ndmchan* to_chan;
  unsigned n_ready, n_avail, n_copy;
  int is_backup = 0;

  switch (da->data_state.operation) {
    default:
      assert(0);
      from_chan = 0;
      to_chan = 0;
      break;

    case NDMP9_DATA_OP_BACKUP:
      from_chan = &da->formatter_image;
      to_chan = &sess->plumb.image_stream->chan;
      is_backup = 1;
      break;

    case NDMP9_DATA_OP_RECOVER:
    case NDMP9_DATA_OP_RECOVER_FILEHIST:
      from_chan = &sess->plumb.image_stream->chan;
      to_chan = &da->formatter_image;
      break;
  }

again:
  n_copy = n_ready = ndmchan_n_ready(from_chan);
  if (n_ready == 0) {
    if (from_chan->eof) {
      to_chan->eof = 1;
      if (ndmchan_n_ready(to_chan) == 0) {
        if (is_backup) { ndmda_data_halt(sess, NDMP9_DATA_HALT_SUCCESSFUL); }
      }
    }
    return 0; /* data blocked */
  }

  n_avail = ndmchan_n_avail(to_chan);
  if (n_copy > n_avail) n_copy = n_avail;

  if (da->enable_hist) {
    if (n_copy > da->pass_resid) n_copy = da->pass_resid;
  }

  if (n_copy > 0) {
    bcopy(&from_chan->data[from_chan->beg_ix], &to_chan->data[to_chan->end_ix],
          n_copy);
    from_chan->beg_ix += n_copy;
    to_chan->end_ix += n_copy;
    da->data_state.bytes_processed += n_copy;
    da->pass_resid -= n_copy;
    goto again; /* do as much as possible */
  }

  return 0;
}


/*
 * Process WRAP messages from the formatter. Called from
 * ndmda_quantum_wrap(). The formatter sends one line text
 * messages via the WRAP pipe (fd=3 on formatter).
 * The WRAP message contain log messages, file history,
 * status updates, etc, etc, etc.
 *
 * Here the messages are parsed and directed to the
 * right NDMP interface toward the Control Agent.
 */

void ndmp9_fstat_from_wrap_fstat(ndmp9_file_stat* fstat9,
                                 struct wrap_fstat* fstatw);

int ndmda_wrap_in(struct ndm_session* sess, char* wrap_line)
{
  struct wrap_msg_buf _wmsg, *wmsg = &_wmsg;
  int rc;
  ndmp9_file_stat fstat9;

  NDMOS_MACRO_ZEROFILL(wmsg);

  rc = wrap_parse_msg(wrap_line, wmsg);
  if (rc != 0) {
    ndmalogf(sess, 0, 2, "Malformed wrap: %s", wrap_line);
    return -1;
  }

  switch (wmsg->msg_type) {
    case WRAP_MSGTYPE_LOG_MESSAGE:
      ndmalogf(sess, "WRAP", 2, "%s", wmsg->body.log_message.message);
      ndma_send_logmsg(sess, NDMP9_LOG_NORMAL, sess->plumb.data, "WRAP: %s",
                       wmsg->body.log_message.message);
      break;

    case WRAP_MSGTYPE_ADD_FILE:
      ndmp9_fstat_from_wrap_fstat(&fstat9, &wmsg->body.add_file.fstat);
      fstat9.fh_info.valid = NDMP9_VALIDITY_VALID;
      fstat9.fh_info.value = wmsg->body.add_file.fhinfo;
      ndmda_fh_add_file(sess, &fstat9, wmsg->body.add_file.path);
      break;

    case WRAP_MSGTYPE_ADD_DIRENT:
      ndmda_fh_add_dir(sess, wmsg->body.add_dirent.dir_fileno,
                       wmsg->body.add_dirent.name,
                       wmsg->body.add_dirent.fileno);
      break;

    case WRAP_MSGTYPE_ADD_NODE:
      ndmp9_fstat_from_wrap_fstat(&fstat9, &wmsg->body.add_node.fstat);
      fstat9.fh_info.valid = NDMP9_VALIDITY_VALID;
      fstat9.fh_info.value = wmsg->body.add_node.fhinfo;
      ndmda_fh_add_node(sess, &fstat9);
      break;

    case WRAP_MSGTYPE_DATA_READ:
      ndmda_send_data_read(sess, wmsg->body.data_read.offset,
                           wmsg->body.data_read.length);
      break;

    case WRAP_MSGTYPE_ADD_ENV:
    case WRAP_MSGTYPE_DATA_STATS:
    case WRAP_MSGTYPE_RECOVERY_RESULT:
      ndmalogf(sess, 0, 2, "Unimplemented wrap: %s", wrap_line);
      break;
  }

  return 0;
}

void ndmp9_fstat_from_wrap_fstat(ndmp9_file_stat* fstat9,
                                 struct wrap_fstat* fstatw)
{
  NDMOS_MACRO_ZEROFILL(fstat9);

  switch (fstatw->ftype) {
    default:
    case WRAP_FTYPE_INVALID:
      fstat9->ftype = NDMP9_FILE_OTHER;
      break;
    case WRAP_FTYPE_DIR:
      fstat9->ftype = NDMP9_FILE_DIR;
      break;
    case WRAP_FTYPE_FIFO:
      fstat9->ftype = NDMP9_FILE_FIFO;
      break;
    case WRAP_FTYPE_CSPEC:
      fstat9->ftype = NDMP9_FILE_CSPEC;
      break;
    case WRAP_FTYPE_BSPEC:
      fstat9->ftype = NDMP9_FILE_BSPEC;
      break;
    case WRAP_FTYPE_REG:
      fstat9->ftype = NDMP9_FILE_REG;
      break;
    case WRAP_FTYPE_SLINK:
      fstat9->ftype = NDMP9_FILE_SLINK;
      break;
    case WRAP_FTYPE_SOCK:
      fstat9->ftype = NDMP9_FILE_SOCK;
      break;
    case WRAP_FTYPE_REGISTRY:
      fstat9->ftype = NDMP9_FILE_REGISTRY;
      break;
    case WRAP_FTYPE_OTHER:
      fstat9->ftype = NDMP9_FILE_OTHER;
      break;
  }

  if (fstatw->valid & WRAP_FSTAT_VALID_FTYPE) {}

  if (fstatw->valid & WRAP_FSTAT_VALID_MODE) {
    fstat9->mode.valid = NDMP9_VALIDITY_VALID;
    fstat9->mode.value = fstatw->mode;
  }

  if (fstatw->valid & WRAP_FSTAT_VALID_SIZE) {
    fstat9->size.valid = NDMP9_VALIDITY_VALID;
    fstat9->size.value = fstatw->size;
  }

  if (fstatw->valid & WRAP_FSTAT_VALID_LINKS) {
    fstat9->links.valid = NDMP9_VALIDITY_VALID;
    fstat9->links.value = fstatw->size;
  }

  if (fstatw->valid & WRAP_FSTAT_VALID_UID) {
    fstat9->uid.valid = NDMP9_VALIDITY_VALID;
    fstat9->uid.value = fstatw->uid;
  }

  if (fstatw->valid & WRAP_FSTAT_VALID_GID) {
    fstat9->gid.valid = NDMP9_VALIDITY_VALID;
    fstat9->gid.value = fstatw->gid;
  }

  if (fstatw->valid & WRAP_FSTAT_VALID_ATIME) {
    fstat9->atime.valid = NDMP9_VALIDITY_VALID;
    fstat9->atime.value = fstatw->atime;
  }

  if (fstatw->valid & WRAP_FSTAT_VALID_MTIME) {
    fstat9->mtime.valid = NDMP9_VALIDITY_VALID;
    fstat9->mtime.value = fstatw->mtime;
  }

  if (fstatw->valid & WRAP_FSTAT_VALID_CTIME) {
    fstat9->ctime.valid = NDMP9_VALIDITY_VALID;
    fstat9->ctime.value = fstatw->ctime;
  }

  if (fstatw->valid & WRAP_FSTAT_VALID_FILENO) {
    fstat9->node.valid = NDMP9_VALIDITY_VALID;
    fstat9->node.value = fstatw->fileno;
  }
}


/*
 * Send LOG and NOTIFY messages
 ****************************************************************
 */

void ndmda_send_notice(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;

  if (!da->data_notify_pending) return;

  da->data_notify_pending = 0;

  switch (da->data_state.state) {
    case NDMP9_DATA_STATE_HALTED:
      ndma_notify_data_halted(sess);
      break;

    default:
      /* Hmm. Why are we here. Race? */
      break;
  }
}

void ndmda_send_data_read(struct ndm_session* sess,
                          uint64_t offset,
                          uint64_t length)
{
  struct ndm_data_agent* da = sess->data_acb;
  ndmp9_addr_type addr_type;

  addr_type = da->data_state.data_connection_addr.addr_type;

  if (NDMP9_ADDR_LOCAL == addr_type) {
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
    if (ndmta_local_mover_read(sess, offset, length) != 0) {
      ndma_send_logmsg(sess, NDMP9_LOG_ERROR, sess->plumb.data,
                       "local_mover_read failed");
      ndmda_data_halt(sess, NDMP9_DATA_HALT_INTERNAL_ERROR);
    }
#else  /* !NDMOS_OPTION_NO_TAPE_AGENT */
    ndma_send_logmsg(sess, NDMP9_LOG_ERROR, sess->plumb.data,
                     "local_mover_read not configured");
    ndmda_data_halt(sess, NDMP9_DATA_HALT_INTERNAL_ERROR);
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */
    return;
  }

  switch (addr_type) {
    case NDMP9_ADDR_TCP:
      ndma_notify_data_read(sess, offset, length);
      break;

    default:
      ndma_send_logmsg(sess, NDMP9_LOG_ERROR, sess->plumb.data,
                       "bogus mover.addr_type");
      ndmda_data_halt(sess, NDMP9_DATA_HALT_INTERNAL_ERROR);
      break;
  }
}


/*
 * Misc -- env[] and nlist[] subroutines, etc
 ****************************************************************
 */

int ndmda_copy_environment(struct ndm_session* sess,
                           ndmp9_pval* env,
                           unsigned n_env)
{
  struct ndm_data_agent* da = sess->data_acb;
  unsigned int i;

  for (i = 0; i < n_env; i++) {
    if (!ndma_store_env_list(&da->env_tab, &env[i])) goto fail;
  }

  return 0;

fail:
  ndma_destroy_env_list(&da->env_tab);

  return -1;
}

struct ndmp9_pval* ndmda_find_env(struct ndm_session* sess, char* name)
{
  struct ndm_data_agent* da = sess->data_acb;
  struct ndm_env_entry* entry;

  for (entry = da->env_tab.head; entry; entry = entry->next) {
    if (strcmp(entry->pval.name, name) == 0) return &entry->pval;
  }

  return 0;
}


int ndmda_interpret_boolean_value(char* value_str, int default_value)
{
  if (strcasecmp(value_str, "y") == 0 || strcasecmp(value_str, "yes") == 0 ||
      strcasecmp(value_str, "t") == 0 || strcasecmp(value_str, "true") == 0 ||
      strcasecmp(value_str, "1") == 0)
    return 1;

  if (strcasecmp(value_str, "n") == 0 || strcasecmp(value_str, "no") == 0 ||
      strcasecmp(value_str, "f") == 0 || strcasecmp(value_str, "false") == 0 ||
      strcasecmp(value_str, "0") == 0)
    return 0;

  return default_value;
}

void ndmda_purge_environment(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;

  ndma_destroy_env_list(&da->env_tab);
}


int ndmda_copy_nlist(struct ndm_session* sess,
                     ndmp9_name* nlist,
                     unsigned n_nlist)
{
  struct ndm_data_agent* da = sess->data_acb;
  unsigned int i;

  for (i = 0; i < n_nlist; i++) {
    if (!ndma_store_nlist(&da->nlist_tab, &nlist[i])) {
      return -1; /* no mem */
    }
  }

  /* TODO: sort */

  return 0;
}

void ndmda_purge_nlist(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;

  ndma_destroy_nlist(&da->nlist_tab);
}

int ndmda_count_invalid_fh_info(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;
  struct ndm_nlist_entry* entry;
  int count;

  count = 0;
  for (entry = da->nlist_tab.head; entry; entry = entry->next) {
    if (entry->name.fh_info.valid != NDMP9_VALIDITY_VALID) { count++; }
  }

  return count;
}

int ndmda_count_invalid_fh_info_pending(struct ndm_session* sess)
{
  struct ndm_data_agent* da = sess->data_acb;
  struct ndm_nlist_entry* entry;
  int count;

  count = 0;
  for (entry = da->nlist_tab.head; entry; entry = entry->next) {
    if (entry->result_err == NDMP9_UNDEFINED_ERR &&
        entry->name.fh_info.valid != NDMP9_VALIDITY_VALID) {
      count++;
    }
  }

  return count;
}

#endif /* !NDMOS_OPTION_NO_DATA_AGENT */
