/* DBENG (Bbuuzzb) - A database engine. Library functions.
   Rick Smereka, Copyright (C) 1995-2006.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   You can contact the author via email at rsmereka@future-lab.com

   Original DOS version Feb/95, Rick Smereka

   Added call to 'setvbuf' to set I/O buffer size. Dec/96, Rick Smereka

   Updated code to have same functionality as Pascal (Delphi) code.
   Also ported to QNX V4.23a. Jan/97, Rick Smereka

   Complete re-write removing all known errors. Changed to remove 'alias'
   and add 'tid'. Oct/97, Rick Smereka

   Added logic to support 'is_table_locked' flag. If this flag is
   on, no read, write or file pointer movement will be allowed.
   Nov/98, Rick Smereka

   Ported to 32bit Windows under CodeWarrior V4.
   Dec/98, Rick Smereka

   Ported to HP-UX under GNU C V2.8.1.
   Jan/99, Rick Smereka

   Added function 'dbeng_ferror', changed function
   'dbeng_get_recd' to use this new function. Fixed bug in
   function 'dbeng_count_rec' that did not properly trap
   an error from 'dbeng_get_recd'. Feb/99, Rick Smereka

   Added functions 'dbeng_get_tmp_path' and 'dbeng_get_error_log'.
   Moved these functions to 'dbengcfg.c' and renamed them. Modified
   'dbeng_pack' to get the temporary path from function
   'dbeng_config_get_tmp_path'. Modified function 'dbeng_ferror'
   to get the log name (in the case of stand-alone compile)
   from the function 'dbeng_config_get_log'.
   Feb/99, Rick Smereka

   Ported to Red Hat Linux 5.2, Jul/99, Rick Smereka

   Added include of 'flsocket.h'. Mar/2000, Rick Smereka

   Cleaned up debug messages. Apr/2000, Rick Smereka

   Added function 'dbeng_open_table_list'. Jun/2001,
   Rick Smereka

   Added session table support. Oct/2001, Rick Smereka

   Added system catalog support and implemented support for
   new 'dbeng_table' member 'current_position'. Modified
   function 'dbeng_pack_table' to goto the top of the table
   after packing. Added new functions 'dbeng_ll_open' and
   'dbeng_ll_close'. Changed function 'dbeng_set_pos' to
   goto the top of the table before positioning.
   Nov/2001, Rick Smereka

   Added new functions 'dbeng_nsubfields', 'dbeng_subfield_size',
   'dbeng_get_subfield', 'dbeng_put_subfield', 'dbeng_nsubsubfields',
   'dbeng_subsubfield_size', 'dbeng_get_subsubfield' and
   'dbeng_put_subsubfield'. Dec/2001, Rick Smereka

   Added function 'dbeng_record_header'. Changed 'dbeng_get_recd' to
   read and validate the record header through the new function.
   Changed 'dbeng_delete_recd' to make sure that a valid record
   is present before updating it. If the record position has been
   changed (possible other user/tid packed the table changing some of
   the record positions) 'dbeng_delete_recd' will return
   'DBENG_RECORD_POSITION_CHANGED' along with forcing the file position
   back to the top of the table. This situation should never happen
   as 'dbeng_pack_table' and 'dbeng_delete_recd' have now been modified
   to reconcile any other tid pointing to the record being modified.
   Changed 'dbeng_write_string_recd' to maintain the start of record
   file position and current file position instead of going to the
   top of the table after the write of a record. Jan/2002, Rick Smereka

   Added functions 'dbeng_open_systable', 'dbeng_ll_open_table',
   'dbeng_new_systable', 'dbeng_ll_new_table' and
   'dbeng_is_systable'. Placed calls to 'dbengrep_send' from the
   functions 'dbeng_write_new_recd', 'dbeng_write_string_recd',
   'dbeng_delete_recd' and 'dbeng_pack_table'. Feb/2002, Rick Smereka

   Changed function 'dbeng_ll_new_table' to create the new table only
   in single user mode as the catalog module 'dbeng_catalog_new_table'
   will create the table in multiuser mode. Mar/2002, Rick Smereka

   Changed function 'dbeng_record_header' to be private. Apr/2002,
   Rick Smereka
 
   Changed function 'dbeng_get_tid' to obtain the next tid based
   on the number of current tables in use. Added private functions
   'dbeng_put_which_field', 'dbeng_put_which_subfield' and
   'dbeng_put_which_subsubfield'. Modified functions
   'dbeng_put_field', 'dbeng_put_subfield' and
   'dbeng_put_subsubfield' to call their respective
   'dbeng_put_which' function. Added function 'dbeng_exist'.
   May/2002, Rick Smereka

   Added functions 'dbeng_ll_copy_table' and 'dbeng_clear_table'
   and 'dbeng_copy_table'. Jun/2002, Rick Smereka

   Changed function 'dbeng_ferror' to obtain the application name
   (in the case of multi-user [database server]) by calling
   'appinit_get_name'. Jul/2002, Rick Smereka

   Added a parameter to the function 'dbeng_open_table_list'.
   Aug/2002, Rick Smereka

   Added function 'dbeng_tmp_systable' and modified 'dbeng_pack_table'
   to create a new system table using 'dbeng_tmp_systable'. Sep/2002,
   Rick Smereka

   Added include of 'dbengsrt.h'. Added initialization code for new
   'dbeng_table' members 'sort_max_open_bin' and 'sort_max_mem' in
   function 'dbeng_ll_open_table'. Ported to Debian Linux.
   Nov/2002, Rick Smereka
 
   Fixed bug in 'dbeng_tmp_systable' that caused a memory leak.
   Added private function 'dbeng_compare_field' and modified
   functions 'dbeng_find_field' and 'dbeng_find' to use this 
   new function.  Added functions 'dbeng_delete_field',
   'dbeng_delete_subfield' and 'dbeng_delete_subsubfield'.
   Mar/2003, Rick Smereka

   Changed function 'dbeng_get_recd' to goto the top of the
   table when the attempt to read a record header results in
   any error code (anything other than 'DBENG_OK'). Jun/2003,
   Rick Smereka

   Changed counting logic in 'dbeng_count_rec' and added an additional
   parameter. Changed calls to 'dbeng_count_rec'. Changed all references
   to the 'dbeng_table' member 'record_count' to either the active
   count or the deleted record count. Changed function 'dbeng_rec_count'
   by adding an additional parameter. Dec/2003, Rick Smereka

   Added save of 'process_deleted' flag, force of flag to high
   before count in 'dbeng_count_rec' and restore of flag after
   count. Jan/2004, Rick Smereka

   Added function 'dbeng_set_record_count'. Feb/2004,
   Rick Smereka

   Modified function 'dbeng_ll_open_table' to acquire table
   catalog details from function 'dbeng_catalog_table_details'.
   Added functions 'dbeng_autopack' and 'dbeng_set_autopack'.
   Added logic to perform automatic pack in function
   'dbeng_delete_recd' if the autopack threshold is exceeded.
   Changed all logging calls from 'sys_log' to 'logman'.
   Modified function 'dbeng_ferror' to use 'logman' functions.
   Any application linking with this API should register its
   name with the function 'appinit_register_name'. The function
   'dbeng_ferror' obtains the application name by calling the
   function 'appinit_get_name'. Mar/2004, Rick Smereka

   Fixed bug in 'dbeng_ll_new_table' that caused a 'segment fault'
   crash because the output table name was not big enough. Changed
   'dbeng_write_recd' to detect when writing a record already marked
   for deletion. In this case, a new record will be created.
   Jan/2005, Rick Smereka

   Plugged memory leak in 'dbeng_ll_open_table' when the open failed
   due to no catalog entry. Feb/2005, Rick Smereka

   Modified 'dbeng_ll_new_table' to only open the new table when the
   passed tid flag is a positive number. Mar/2006, Rick Smereka */

#include "stdhead.h"
#include "dbmess.h"
#include "dbeng.h"
#include "dbiocode.h"
#include "dbengcfg.h"
#include "dbengsrt.h"
#include "dbengcat.h"
#ifdef MULTIUSER
#include "dbengses.h"
#ifdef IPC_TCP
#include "dbengrep.h"
#endif
#endif

/* global data */

/* pointer to head of open table link list */

struct dbeng_table *dbeng_head;

/* number of tables in use */

int dbeng_table_count;

/* private functions */

static int dbeng_ll_open_table(char *, int *, int);
static int dbeng_ll_new_table(char *, int *, int);
static int dbeng_record_header(struct dbeng_table *, int *, char *);
static int dbeng_put_which_field(struct dbeng_table *, int, int *);
static int dbeng_put_which_subfield(struct dbeng_table *, int, int , int *, 
                                    int *);
static int dbeng_put_which_subsubfield(struct dbeng_table *, int, int, int, 
                                       int *, int *, int *);
static int dbeng_table_in_use(char *);
static int dbeng_ll_copy_table(struct dbeng_table *, struct dbeng_table *);
static int dbeng_compare_field(char *, char *, int, int *);

int dbeng_pack_table(struct dbeng_table *ot)
{
   /* Pack a table by copying only active records. Function
      returns 'DBENG_OK' upon success, an error code otherwise. */

   struct dbeng_table *dest_table;
   char *dest_table_name;
   char mname[] = "dbeng_pack_table";
   int ret, tid, delete_flag, len;

#ifdef MULTIUSER
#ifdef IPC_TCP
   int rep_flag;
#endif
#endif

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   logman("%s:enter,tid=%d", mname, ot->tid);

   /* check for locked table */

   if (ot->is_table_locked)
      {
      dbeng_io_code_log(mname, "locked", DBENG_TABLE_LOCKED);
      return(DBENG_TABLE_LOCKED);
      }

   /* check for changed record */

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   if ((ret = dbeng_tmp_systable(&tid)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_tmp_systable", ret);
      return(ret);
      }

   /* fetch link list pointer from tid */

   if ((dest_table = dbeng_atid_lookup(tid)) == NULL)
      {
      logman("%s:cannot locate tmp table[%d]", mname, tid);
      return(DBENG_CANNOT_CREATE_TABLE);
      }

   logman("%s:output tmp table is %s", mname, dest_table->name);
   len = strlen(dest_table->name);

   if ((dest_table_name = (char *)malloc(len + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[dest_table_name]",
                        DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   strcpy(dest_table_name, dest_table->name);

   /* force source 'process_deleted' flag down (save prev value) */

   delete_flag = ot->process_deleted;
   ot->process_deleted = FALSE;

   /* copy source to destination */

   if ((ret = dbeng_ll_copy_table(ot, dest_table)) != DBENG_OK)
      {
      (void)dbeng_close_table(dest_table);
      unlink(dest_table_name);
      ot->process_deleted = delete_flag;
      dbeng_io_code_log(mname, "bad ret from dbeng_ll_copy_table", ret);
      free(dest_table_name);
      return(ret);
      }
      
   // fix-up record counts

   ot->active_record_count = dest_table->active_record_count;
   ot->deleted_record_count = 0L;

   /* close output (temporary) table */

   if ((ret = dbeng_close_table(dest_table)) != DBENG_OK)
      {
      unlink(dest_table_name);
      ot->process_deleted = delete_flag;
      dbeng_io_code_log(mname, "error closing tmp table", ret);
      free(dest_table_name);
      return(ret);
      }

   /* close source table (but retain table structure) */

   if ((ret = dbeng_ll_close(ot)) != DBENG_OK)
      {
      unlink(dest_table_name);
      ot->process_deleted = delete_flag;
      dbeng_io_code_log(mname, "error closing source table", ret);
      free(dest_table_name);
      return(ret);
      }

   /* delete source table */

   unlink(ot->name);

   /* rename temporary table to source */

   if (!qrename(dest_table_name, ot->name))
      {
      ot->process_deleted = delete_flag;
      logman("%s:error renaming table", mname);
      free(dest_table_name);
      return(DBENG_RENAME_ERROR);
      }

   free(dest_table_name);

   if ((ret = dbeng_ll_open(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error re-opening file", DBENG_UNABLE_TO_OPEN);
      return(DBENG_UNABLE_TO_OPEN);
      }

   /* since we are now at the bottom of the table, goto top */

   (void)dbeng_goto_top(ot);

   // fix-up record count

   if ((ret = dbeng_set_record_count(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error setting record count",
                        DBENG_INTERNAL_ERROR);
      return(DBENG_INTERNAL_ERROR);
      }

   /* replicate command if requested */

#ifdef MULTIUSER
#ifdef IPC_TCP
   (void)dbeng_config_get_replicate_flag(&rep_flag);

   if (rep_flag && ot->rep_list != DBENG_ROT_NULL)
      if ((ret = dbengrep_send(ot, DBENG_SEND_REPLICATE_PACK, (char *)NULL))
          != DBENG_OK)
         logman("%s:replication failed,rc=%d", mname, ret);
#endif
#endif

   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_open_systable(char *fname, int *tid)
{
   /* Open a system table. At the moment, a system table is one of:

         - temporary table
         - session table
         - catalog

      This function behaves just like 'dbeng_open_table' except
      that the physical file is always left open. Function returns
      a 'dbeng' code. */

   char mname[] = "dbeng_open_systable";
   int ret;

   if (fname == (char *)NULL || !strlen(fname))
      {
      dbeng_io_code_log(mname, "fname parameter error",
                        DBENG_INVALID_FILE_NAME);
      return(DBENG_INVALID_FILE_NAME);
      }

   logman("%s:enter,fname=%s", mname, fname);

   if (tid == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[tid]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *tid = 0;

   /* open the table using 'dbeng_ll_open_table' */

   if ((ret = dbeng_ll_open_table(fname, tid, TRUE)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_ll_open_table", ret);
      return(ret);
      }

   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_open_table(char *fname, int *tid)
{
   /* Open a table. Function returns the table 'tid'
      upon success. Function returns a 'dbeng' code. */

   char mname[] = "dbeng_open_table";
   int ret;

   if (fname == (char *)NULL || !strlen(fname))
      {
      dbeng_io_code_log(mname, "fname parameter error",
                        DBENG_INVALID_FILE_NAME);
      return(DBENG_INVALID_FILE_NAME);
      }

   logman("%s:enter,fname=%s", mname, fname);

   if (tid == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[tid]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *tid = 0;

   /* open the table using 'dbeng_ll_open_table' */

   if ((ret = dbeng_ll_open_table(fname, tid, FALSE)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_ll_open_table", ret);
      return(ret);
      }

   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

static int dbeng_ll_open_table(char *fname, int *tid, int systable_flag)
{
   /* Open a table. Table ID is returned in 'tid' upon success.
      If the system catalog flag is on, only logical table names 
      listed in the system catalog will succeed unless the name 
      is prefixed with the 'DBENG_CATALOG_FNAME_IND' character. 
      Function returns 'DBENG_OK' upon success, error code otherwise.
      Note that if the code returned is 'DBENG_FILE_DATA_ERROR',
      the 'tid' is still loaded and the file is left open. */

   struct dbeng_table *ot;
   struct dbeng_table *rov;
   struct dbengcat_details *det;
   char mname[] = "dbeng_ll_open_table";
   int ot_size, session_flag, rep_flag, ret;
   int name_type, done = FALSE;

   logman("%s:enter", mname);

   if (fname == (char *)NULL || !strlen(fname))
      {
      dbeng_io_code_log(mname, "fname parameter error",
                        DBENG_INVALID_FILE_NAME);
      return(DBENG_INVALID_FILE_NAME);
      }

   if (tid == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[tid]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (systable_flag != TRUE && systable_flag != FALSE)
      {
      dbeng_io_code_log(mname, "bad flag[systable_flag]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *tid = 0;
   ot_size = sizeof(struct dbeng_table);

   if ((ot = malloc(ot_size)) == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[ot]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   ot->handle = (FILE *)NULL;

   // prepare to load table catalog details

   if ((ret = dbeng_catalog_init_table_details(fname, systable_flag, &det)) !=
      DBENG_OK)
      {
      dbeng_io_code_log(mname, 
                        "bad rc from dbeng_catalog_init_table_details", ret);
      free(ot);
      return(ret);
      }

   // load table catalog details

   if ((ret = dbeng_catalog_table_details(det)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_catalog_table_details", ret);
      (void)dbeng_catalog_term_table_details(&det);
      free(ot);
      return(ret);
      }

#ifdef MULTIUSER
   (void)dbeng_config_get_session_flag(&session_flag);
   ot->rep_list = DBENG_ROT_NULL;
#endif

   /* file name is stored in original case and is compared
      case sensitive */

   if ((ot->name = malloc(strlen(det->physical_name) + 1)) == (char *)NULL)
      {
      free(ot);
      (void)dbeng_catalog_term_table_details(&det);
      dbeng_io_code_log(mname, "alloc fail[ot->name]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   strcpy(ot->name, det->physical_name);

   if ((ret = dbeng_ll_open(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_ll_open", ret);
      free(ot->name);
      free(ot);
      (void)dbeng_catalog_term_table_details(&det);
      return(ret);
      }

   ot->next = DBENG_OT_NULL;             // end of link list
   ot->rec = (char *)NULL;               // no current record
   ot->change_rec_flag = FALSE;          // no change in record
   ot->enforce_change_rec_flag = FALSE;  // do not enforce 'change_rec_flag'
   ot->record_number = 0L;               // no current record
   ot->orig_position = -1L;              // no start rec file pointer position
   ot->current_position = 0L;            // current pos at top of table
   ot->process_deleted = FALSE;          // do not process deleted records
   ot->is_table_locked = FALSE;          // table is not read/write locked
   ot->is_systable = systable_flag;      // set system table flag

   /* if its a system table, use the default autopack threshold
      otherwise use the threshold given in the catalog (if present) */

   ot->autopack = systable_flag ? DBENG_SYSTABLE_MAX_DELETED : det->apack;
   ot->sort_max_open_bin = DBENGSRT_MAX_OPEN_BIN; // max open bin during sort
   ot->sort_max_mem = DBENGSRT_MAX_SORT_ALLOC;    // max mem alloc during sort

   /* set head of list if first file open */

   if (dbeng_head == DBENG_OT_NULL)
      dbeng_head = ot;
   else
      {
      /* insert new table at end of link list */

      rov = dbeng_head;

      while(!done)
         {
         if (rov->next == DBENG_OT_NULL)
            {
            rov->next = ot;
            done = TRUE;
            }
         else
            rov = rov->next;
         }
      }

   ot->tid = dbeng_get_tid();
   *tid = ot->tid;

   if ((ret = dbeng_count_rec(ot, &ot->active_record_count,
        &ot->deleted_record_count)) != DBENG_OK)
      {
      (void)dbeng_catalog_term_table_details(&det);
      return(ret);
      }

   (void)dbeng_goto_top(ot);
   dbeng_table_count++;

#ifdef MULTIUSER
   /* if session table is active, physically close the file
      unless it is a system table */

   if (session_flag && !systable_flag)
      if ((ret = dbeng_ll_close(ot)) != DBENG_OK)
         dbeng_io_code_log(mname, "error closing table", ret);

#ifdef IPC_TCP
   /* if replication is active and a catalog table name was used
      to open the table, load replication details from
      catalog into replication link list (do not allow
      replication of any system table) */

   (void)dbeng_config_get_replicate_flag(&rep_flag);

   if (rep_flag && fname[0] != DBENG_CATALOG_FNAME_IND && !ot->is_systable)
      if ((ret = dbengrep_rll_load(ot, det->rep_list)) != DBENG_OK)
         {
         if (ret != DBENG_NO_REPLICATION_ENTRY && ret !=
             DBENG_NO_CATALOG_ENTRY)
            {
            logman("%s:exit:bad rc[%d] from dbengrep_rll_load[%d]",
                          mname, ret, rov->tid);
            (void)dbeng_catalog_term_table_details(&det);
            return(ret);
            }
         }
#endif
#endif

   if ((ret = dbeng_catalog_term_table_details(&det)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from "
                        "dbeng_catalog_term_table_details", ret);
      return(ret);
      }

   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_close_table(struct dbeng_table *table)
{
   /* Close a table. Function returns 'DBENG_OK' upon success,
      error code otherwise. */

   struct dbeng_table *rov;
   struct dbeng_table *prev;
   char mname[] = "dbeng_close_table";
   int ret, session_flag, rep_flag, done = FALSE;

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   logman("%s:enter:tid=%d", mname, table->tid);

   if (table->enforce_change_rec_flag && table->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

#ifdef MULTIUSER
   (void)dbeng_config_get_session_flag(&session_flag);

   if (session_flag && !table->is_systable)
      if ((ret = dbeng_session_delete(table->tid)) != DBENG_OK)
         dbeng_io_code_log(mname, "bad rc from dbeng_session_delete", ret);

#ifdef IPC_TCP
   (void)dbeng_config_get_replicate_flag(&rep_flag);

   if (rep_flag && !table->is_systable)
      if ((ret = dbengrep_rll_delete_all(table)) != DBENG_OK)
         dbeng_io_code_log(mname, "bad rc from dbengrep_rll_delete", ret);
#endif
#endif

   rov = table;
   rov->change_rec_flag = -1;       /* mark this table to close */
   rov = dbeng_head;                /* goto top of link list */
   prev = DBENG_OT_NULL;
   done = FALSE;

   /* loop to locate the previous table in the link list, close the
      appropiate table and de-allocate all memory used by the
      table structure */

   while(!done)
      {
      /* this should never happen but just in case */

      if (rov == DBENG_OT_NULL)
         {
         logman("%s:unexpected rov NULL", mname);
         break;
         }

      if (rov->change_rec_flag == -1)
         {
         if (prev == DBENG_OT_NULL)
            if (rov->next == DBENG_OT_NULL)
               dbeng_head = DBENG_OT_NULL;
            else
               dbeng_head = rov->next;
         else
            prev->next = rov->next;

         if ((ret = dbeng_ll_close(rov)) != DBENG_OK)
            dbeng_io_code_log(mname, "warning:problem closing table", ret);

         free(rov->name);

         if (dbeng_is_active_rec(rov) == DBENG_OK)
            free(rov->rec);

         free(rov);
         done = TRUE;
         }
      else
         {
         prev = rov;
         rov = rov->next;
         }
      }

   dbeng_table_count--;

   if (!done)
      {
      dbeng_io_code_log(mname, "normal exit", DBENG_INTERNAL_ERROR);
      return(DBENG_INTERNAL_ERROR);
      }

   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_goto_top(struct dbeng_table *ot)
{
   /* Reposition table at the top, with no current record. Function
      returns 'DBENG_OK' upon success, an error code otherwise. */

   char mname[50];

   strcpy(mname, "dbeng_goto_top");

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* logman("%s:enter:name=%s", mname, ot->name); */

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   rewind(ot->handle);

   if (dbeng_is_active_rec(ot) == DBENG_OK)
      free(ot->rec);

   ot->rec = (char *)NULL;
   ot->record_number = 0L;
   ot->orig_position = -1L;
   ot->current_position = 0L;
   ot->change_rec_flag = FALSE;
   /* logman("%s:normal exit", mname); */
   return(DBENG_OK);
}

int dbeng_rewrite_recd(struct dbeng_table *ot)
{
   /* Rewrite an existing record. Old record will be marked for
      deletion and a new record will be written. Function returns
      'DBENG_OK' upon success, an error code otherwise. */

   struct dbeng_table tmp;
   char mname[50];
   int rec_size;
   int ret, enf_change_rec_flag;

   strcpy(mname, "dbeng_rewrite_recd");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* make sure there is an already existing record */

   if (ot->orig_position == -1L || ot->record_number == 0L)
      {
      dbeng_io_code_log(mname, "no current record", DBENG_NO_EXISTING_RECORD);
      return(DBENG_NO_EXISTING_RECORD);
      }

   // check for locked table

   if (ot->is_table_locked)
      {
      dbeng_io_code_log(mname, "table is locked", DBENG_TABLE_LOCKED);
      return(DBENG_TABLE_LOCKED);
      }

   /* save current record values as they are destroyed by
      the delete record function */

   rec_size = strlen(ot->rec);

   if ((tmp.rec = malloc(rec_size + 1)) == NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[tmp.rec]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   strcpy(tmp.rec, ot->rec);
   tmp.record_number = ot->record_number;
   tmp.orig_position = ot->orig_position;
   tmp.current_position = ot->current_position;

   /* force 'enforce_change_rec_flag' down as the delete
      function checks this */

   enf_change_rec_flag = ot->enforce_change_rec_flag;
   ot->enforce_change_rec_flag = FALSE;
   ret = dbeng_delete_recd(ot);

   if (ret != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret[delete_recd]", ret);
      free(tmp.rec);
      ot->enforce_change_rec_flag = enf_change_rec_flag;
      return(ret);
      }

   /* reload record values */

   ret = dbeng_rec_resize(ot, rec_size);

   if (ret != DBENG_OK)
      {
      dbeng_io_code_log(mname, "resize fail", ret);
      free(tmp.rec);
      ot->enforce_change_rec_flag = enf_change_rec_flag;
      return(ret);
      }

   strcpy(ot->rec, tmp.rec);
   ot->record_number = tmp.record_number;
   ot->orig_position = tmp.orig_position;
   ot->current_position = tmp.current_position;
   free(tmp.rec);
   ret = dbeng_write_new_recd(ot);
   ot->enforce_change_rec_flag = enf_change_rec_flag;
   dbeng_io_code_log(mname, "normal exit", ret);
   return(ret);
}

int dbeng_delete_recd(struct dbeng_table *ot)
{
   /* Mark the current record for deletion. Function returns
      'DBENG_OK' upon success, an error code otherwise. */

   struct dbeng_table *rov;
   char mname[] = "dbeng_delete_recd";
   char rm[2], status;
   int ret, len, rep_flag;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (ot->record_number == 0L || ot->orig_position == -1L)
      {
      dbeng_io_code_log(mname, "no current record", DBENG_NO_EXISTING_RECORD);
      return(DBENG_NO_EXISTING_RECORD);
      }

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   // check for locked table

   if (ot->is_table_locked)
      {
      dbeng_io_code_log(mname, "table is locked", DBENG_TABLE_LOCKED);
      return(DBENG_TABLE_LOCKED);
      }

   /* goto the start of the current record */

   fseek(ot->handle, ot->orig_position, 0);

   /* verify that there is a valid record here */

   if ((ret = dbeng_record_header(ot, &len, &status)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "record header invalid,going to top", ret);

      /* since there no longer is a current reocrd here, goto top */

      (void)dbeng_goto_top(ot);
      return(DBENG_RECORD_POSITION_CHANGED);
      }

   /* go back to the start of the record */

   fseek(ot->handle, ot->orig_position, 0);

   /* rewrite the first byte of the record */

   rm[0] = 'D';
   rm[1] = EOS;

   if (fwrite(rm, 1, 1, ot->handle) != 1)
      {
      dbeng_io_code_log(mname, "write error on delete mark", DBENG_WRITE_FAIL);
      return(DBENG_WRITE_FAIL);
      }

#ifdef MULTIUSER
   /* if another tid is also pointing to the same record,
      tell that tid that this record is now deleted by
      setting the record number to zero which will cause
      'dbeng_write_recd' to write a new record */

   rov = dbeng_head;

   while(rov != DBENG_OT_NULL)
      {
      if (ot->tid != rov->tid && !strcmp(ot->name, rov->name))
         if (ot->orig_position == rov->orig_position)
            {
            logman("%s:another tid using record,setting rn to zero:"
                          "tid=%d,ltid=%d,pos=%ld", mname, ot->tid, rov->tid,
                          rov->orig_position);
            rov->record_number = 0;
            }

      rov = rov->next;
      }

#ifdef IPC_TCP
   /* replicate if requested */

   (void)dbeng_config_get_replicate_flag(&rep_flag);

   if (rep_flag && ot->rep_list != DBENG_ROT_NULL)
      if ((ret = dbengrep_send(ot, DBENG_SEND_REPLICATE_DELETE, (char *)NULL))
          != DBENG_OK)
         logman("%s:replication failed,rc=%d", mname, ret);
#endif
#endif

   ot->active_record_count--;
   ot->deleted_record_count++;
   ot->change_rec_flag = FALSE;

   // pack if autopack threshold is exceeded

   if (ot->autopack && ot->deleted_record_count >= ot->autopack)
      {
      logman("%s:autopack threshold exceeded,autopacking", mname);

      if ((ret = dbeng_pack_table(ot)) != DBENG_OK)
         {
         dbeng_io_code_log(mname, "error autopacking", ret);
         return(ret);
         }
      }
   else
      {
      // fix-up record count

      if ((ret = dbeng_set_record_count(ot)) != DBENG_OK)
         {
         dbeng_io_code_log(mname, "error setting record count",
                           DBENG_INTERNAL_ERROR);
         return(DBENG_INTERNAL_ERROR);
         }
      }
   
   /* since file position is now unknown, goto top of file */

   (void)dbeng_goto_top(ot);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_write_new_recd(struct dbeng_table *ot)
{
   /* Write new record. Function returns 'DBENG_OK' upon success,
      error code otherwise. No regard is given to an already existing
      record. If you need to rewrite, use 'dbeng_rewrite_recd'
      instead. */

   char header[DBENG_REC_HEAD_SIZE + 1];
   char rm[2];
   char mname[50];
   int ret, len_with_head, len_no_head, rep_flag;

   strcpy(mname, "dbeng_write_new_recd");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (dbeng_is_active_rec(ot) == DBENG_NO_RECORD)
      {
      dbeng_io_code_log(mname, "no current record", DBENG_NO_RECORD);
      return(DBENG_NO_RECORD);
      }

   // check for locked table

   if (ot->is_table_locked)
      {
      dbeng_io_code_log(mname, "table is locked", DBENG_TABLE_LOCKED);
      return(DBENG_TABLE_LOCKED);
      }

   len_no_head = strlen(ot->rec);
   fseek(ot->handle, 0L, 2);     /* goto end of table */
   ot->orig_position = ftell(ot->handle);
   len_with_head = len_no_head + 1;
   sprintf(header, "A%010d%c", len_with_head, DBENG_FM);
   sprintf(rm, "%c", DBENG_RM);

   if (fwrite(header, 1, DBENG_REC_HEAD_SIZE, ot->handle) !=
              DBENG_REC_HEAD_SIZE)
      {
      dbeng_io_code_log(mname, "write fail on rec header",
                        DBENG_WRITE_FAIL);
      return(DBENG_WRITE_FAIL);
      }

   if (fwrite(ot->rec, 1, len_no_head, ot->handle) != len_no_head)
      {
      dbeng_io_code_log(mname, "write fail on rec contents",
                        DBENG_WRITE_FAIL);
      return(DBENG_WRITE_FAIL);
      }

   if (fwrite(rm, 1, 1, ot->handle) != 1)
      {
      dbeng_io_code_log(mname, "write fail on rec mark", DBENG_WRITE_FAIL);
      return(DBENG_WRITE_FAIL);
      }

   ++ot->record_number;
   ++ot->active_record_count;
   ot->change_rec_flag = FALSE;

   // fix-up record count

   if ((ret = dbeng_set_record_count(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error setting record count",
                        DBENG_INTERNAL_ERROR);
      return(DBENG_INTERNAL_ERROR);
      }

#ifdef MULTIUSER
#ifdef IPC_TCP
   /* replicate this change if requested */

   (void)dbeng_config_get_replicate_flag(&rep_flag);

   if (rep_flag && ot->rep_list != DBENG_ROT_NULL)
      if ((ret = dbengrep_send(ot, DBENG_SEND_REPLICATE_WRITE, ot->rec))
          != DBENG_OK)
         logman("%s:replication failed,rc=%d", mname, ret);
#endif
#endif

   /* since file position is at end of file, goto top */

   (void)dbeng_goto_top(ot);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_write_string_recd(struct dbeng_table *ot, char *rec)
{
   /* Write new record from the contents of 'rec'. Unlike
      'dbeng_write_new_recd', this function does not
      force the record pointer to the top of the table.
      Function returns
      'DBENG_OK' upon success, an error code otherwise. */

   char header[DBENG_REC_HEAD_SIZE + 1];
   char rm[2];
   char mname[] = "dbeng_write_string_recd";
   int ret, len_with_head, len_no_head, rep_flag;

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (rec == NULL || !strlen(rec))
      {
      dbeng_io_code_log(mname, "null[rec]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* logman("%s:enter", mname); */
   len_no_head = strlen(rec);
   fseek(ot->handle, 0L, 2);     /* goto end of table */
   ot->orig_position = ftell(ot->handle);
   len_with_head = len_no_head + 1;
   sprintf(header, "A%010d%c", len_with_head, DBENG_FM);
   sprintf(rm, "%c", DBENG_RM);

   if (fwrite(header, 1, DBENG_REC_HEAD_SIZE, ot->handle) !=
              DBENG_REC_HEAD_SIZE)
      {
      dbeng_io_code_log(mname, "write fail on rec header",
                        DBENG_WRITE_FAIL);
      return(DBENG_WRITE_FAIL);
      }

   if (fwrite(rec, 1, len_no_head, ot->handle) != len_no_head)
      {
      dbeng_io_code_log(mname, "write fail on rec contents",
                        DBENG_WRITE_FAIL);
      return(DBENG_WRITE_FAIL);
      }

   if (fwrite(rm, 1, 1, ot->handle) != 1)
      {
      dbeng_io_code_log(mname, "write fail on rec mark", DBENG_WRITE_FAIL);
      return(DBENG_WRITE_FAIL);
      }

   ++ot->active_record_count;
   ot->change_rec_flag = FALSE;
   ot->current_position = ftell(ot->handle);

   // fix-up record count

   if ((ret = dbeng_set_record_count(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error setting record count",
                        DBENG_INTERNAL_ERROR);
      return(DBENG_INTERNAL_ERROR);
      }

#ifdef MULTIUSER
#ifdef IPC_TCP
   /* replicate this change if requested */
     
   (void)dbeng_config_get_replicate_flag(&rep_flag);

   if (rep_flag && ot->rep_list != DBENG_ROT_NULL)
      if ((ret = dbengrep_send(ot, DBENG_SEND_REPLICATE_WRITE, rec))
          != DBENG_OK)
         logman("%s:replication failed,rc=%d", mname, ret);
#endif
#endif

   /* logman("%s:normal exit", mname); */
   return(DBENG_OK);
}

int dbeng_write_recd(struct dbeng_table *ot)
{
   /* Write a new reocrd or rewrite an existing record depending on
      the table record counter and the current record status (if any).
      The record contents must already
      be loaded and if a record exists, the file pointer must be
      at the record. Function returns 'DBENG_OK' upon success,
      an error code otherwise. */

   char mname[] = "dbeng_write_recd";
   int ret;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (dbeng_is_active_rec(ot) == DBENG_NO_RECORD)
      {
      dbeng_io_code_log(mname, "no current rec", DBENG_NO_RECORD);
      return(DBENG_NO_RECORD);
      }

   // check for locked table

   if (ot->is_table_locked)
      {
      dbeng_io_code_log(mname, "table is locked", DBENG_TABLE_LOCKED);
      return(DBENG_TABLE_LOCKED);
      }

   // if there is not a current record in memory write a new one

   if (!ot->record_number)
      {
      logman("%s:writing a new record", mname);
      ret = dbeng_write_new_recd(ot);
      }
   else
      {
      // if the current record status is 'deleted', write a new record
  
      if (!ot->record_status)
         {
         logman("%s:record already marked for deletion,writing a new rec",
                mname);
         ret = dbeng_write_new_recd(ot);
         }
      else
         {
         // re-write existing record

         logman("%s:rewriting existing record", mname);
         ret = dbeng_rewrite_recd(ot);
         }
      }

   dbeng_io_code_log(mname, "normal exit", ret);
   return(ret);
}

int dbeng_new(struct dbeng_table *ot)
{
   /* Initialize for a new record. Note that if the current
      record has changed and we are enforcing 'change_rec_flag,
      the function will return that code and the initialization will
      not take place. */

   char mname[50];

   strcpy(mname, "dbeng_new");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   if (dbeng_is_active_rec(ot) == DBENG_OK)
      free(ot->rec);

   ot->rec = (char *)NULL;
   ot->record_number = 0L;
   ot->orig_position = -1L;
   ot->current_position = 0L;
   ot->change_rec_flag = FALSE;
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_get_rec(struct dbeng_table *table, char *rec_out)
{
   /* Copy and return the current record into 'rec_out' which
      must be already allocated large enough to hold the record.
      Function returns 'DBENG_OK' upon success, an error code
      otherwise. */

   char mname[50];
   int ret;

   strcpy(mname, "dbeng_get_rec");
   logman("%s:enter", mname);

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (rec_out == NULL)
      {
      dbeng_io_code_log(mname, "null[rec_out]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   rec_out[0] = EOS;

   if ((ret = dbeng_is_active_rec(table)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   strcpy(rec_out, table->rec);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_get_field(struct dbeng_table *table, char *field_out,
                    int field_num)
{
   /* Obtain the 'field_num' field in the record specified by
      'table'. Function returns 'DBENG_OK' on success,
      error code otherwise. Note that the caller must allocate
      enough memory in 'field_out' to copy the field contents. */

   char mname[50];
   int nwords;

   strcpy(mname, "dbeng_get_field");

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (field_out == NULL)
      {
      dbeng_io_code_log(mname, "null[field_out]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   field_out[0] = EOS;

   if (field_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[field_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* logman("%s:enter", mname); */

   /* if no table or no current record, error */

   if (table == DBENG_OT_NULL || dbeng_is_active_rec(table) == DBENG_NO_RECORD)
      {
      dbeng_io_code_log(mname, "no table or current record", DBENG_NO_RECORD);
      return(DBENG_NO_RECORD);
      }

   nwords = ll_words(table->rec, DBENG_FM);

   /* if field is out of range, error */

   if (field_num > nwords)
      {
      logman("%s:field %d is out of range", mname, field_num);
      return(DBENG_NO_SUCH_FIELD);
      }

   /* get field */

   (void)ll_word(table->rec, field_out, field_num, DBENG_FM);
   /* logman("%s:normal exit", mname); */
   return(DBENG_OK);
}

int dbeng_get_subfield(struct dbeng_table *table, char *subfield_out,
                       int field_num, int subfield_num)
{
   /* Obtain the 'subfield_num' sub-field in the field specified by
      'field_num'. Function returns 'DBENG_OK' on success,
      error code otherwise. Note that the caller must allocate
      enough memory in 'subfield_out' to copy the field contents. */

   char mname[] = "dbeng_get_subfield", *thefield;
   int ret, nwords, fsize, nsubfields;

   logman("%s:enter", mname);

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subfield_out == (char *)NULL)
      {
      dbeng_io_code_log(mname, "null[subfield_out]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   subfield_out[0] = EOS;

   if (field_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[field_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subfield_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[subfield_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_is_active_rec(table)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   if ((ret = dbeng_nsubfields(table, field_num, &nsubfields)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_nsubfields", ret);
      return(ret);
      }

   if (subfield_num > nsubfields)
      {
      dbeng_io_code_log(mname, "out of range[subfield_num]",
                        DBENG_NO_SUCH_SUBFIELD);
      return(DBENG_NO_SUCH_SUBFIELD);
      }

   /* attempt to get the size of the requested field */

   if ((ret = dbeng_field_size(table, field_num, &fsize)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_field_size", ret);
      return(ret);
      }

   if ((thefield = (char *)malloc(fsize + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[thefield]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if ((ret = dbeng_get_field(table, thefield, field_num)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret from dbeng_get_field", ret);
      free(thefield);
      return(ret);
      }

   (void)ll_word(thefield, subfield_out, subfield_num, DBENG_SFM);
   free(thefield);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_get_subsubfield(struct dbeng_table *table, char *subsubfield_out,
                          int field_num, int subfield_num,
                          int subsubfield_num)
{
   /* Obtain the 'subsubfield_num' sub-sub-field in the sub-field specified by
      'subfield_num'. Function returns 'DBENG_OK' on success,
      error code otherwise. Note that the caller must allocate
      enough memory in 'subsubfield_out' to copy the field contents. */

   char mname[] = "dbeng_get_subsubfield", *thesubfield;
   int ret, nwords, sfsize, nsubsubfields;

   logman("%s:enter", mname);

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subsubfield_out == (char *)NULL)
      {
      dbeng_io_code_log(mname, "null[subsubfield_out]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   subsubfield_out[0] = EOS;

   if (field_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[field_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subfield_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[subfield_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subsubfield_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[subsubfield_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_is_active_rec(table)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   if ((ret = dbeng_nsubsubfields(table, field_num, subfield_num,
        &nsubsubfields)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_nsubsubfields", ret);
      return(ret);
      }

   if (subsubfield_num > nsubsubfields)
      {
      dbeng_io_code_log(mname, "out of range[subsubfield_num]",
                        DBENG_NO_SUCH_SUBSUBFIELD);
      return(DBENG_NO_SUCH_SUBSUBFIELD);
      }

   /* attempt to get the size of the requested sub-field */

   if ((ret = dbeng_subfield_size(table, field_num, subfield_num,
        &sfsize)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_subfield_size", ret);
      return(ret);
      }

   if ((thesubfield = (char *)malloc(sfsize + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[thesubfield]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if ((ret = dbeng_get_subfield(table, thesubfield, field_num,
        subfield_num)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret from dbeng_get_subfield", ret);
      free(thesubfield);
      return(ret);
      }

   (void)ll_word(thesubfield, subsubfield_out, subsubfield_num, DBENG_SSFM);
   free(thesubfield);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_put_field(struct dbeng_table *table, char *new_data,
                    int field_num)
{
   /* Store new data in a field and re-size record as
      appropriate. Function returns 'DBENG_OK' upon success,
      error code otherwise. */

   char *old_rec, *new_rec;
   char mname[] = "dbeng_put_field";
   int new_rec_len, new_field_num;
   int nwords;
   int ret;

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_data == NULL)
      {
      dbeng_io_code_log(mname, "null[new_data]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_put_which_field(table, field_num, &new_field_num)) !=
       DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_put_which_field", ret);
      return(ret);
      }

   logman("%s:enter,tid=%d,d=%s,l=%d,f=%d,nf=%d", mname, table->tid, 
                 new_data, strlen(new_data), field_num, new_field_num);

   /* record can be empty */
   /* if null record, create a stub */

   if (table->rec == NULL)
      {
      if ((old_rec = malloc(25)) == NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[old_rec]", DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      old_rec[0] = EOS;
      }
   else
      old_rec = initstring(table->rec); /* otherwise, copy the current rec */

   nwords = ll_words(old_rec, DBENG_FM);
   new_rec_len = strlen(old_rec) + strlen(new_data) + 1;

   if (new_field_num > nwords)
      new_rec_len += new_field_num - nwords;

   /* make sure record is not too big */

   if (DBENG_MAXRECFIELD > 0 && new_rec_len >= DBENG_MAXRECFIELD)
      {
      dbeng_io_code_log(mname, "rec too large", DBENG_RECORD_TOO_LARGE);
      free(old_rec);
      return(DBENG_RECORD_TOO_LARGE);
      }

   if ((new_rec = malloc(new_rec_len)) == NULL)
      {
      free(old_rec);
      dbeng_io_code_log(mname, "alloc fail[new_rec]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if (!ll_wordput(old_rec, new_rec, new_data, new_field_num, DBENG_FM))
      {
      free(old_rec);
      free(new_rec);
      dbeng_io_code_log(mname, "bad ret[ll_wordput]",
                        DBENG_PUT_ERROR);
      return(DBENG_PUT_ERROR);
      }

   ret = dbeng_rec_resize(table, strlen(new_rec));

   if (ret != DBENG_OK)
      {
      free(old_rec);
      free(new_rec);
      dbeng_io_code_log(mname, "bad ret[rec_resize]", ret);
      return(ret);
      }

   strcpy(table->rec, new_rec);
   table->change_rec_flag = TRUE;
   free(old_rec);
   free(new_rec);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_put_subfield(struct dbeng_table *table, char *new_data,
                       int field_num, int subfield_num)
{
   /* Store new data in a sub-field and re-size record as
      appropriate. Function returns 'DBENG_OK' upon success,
      error code otherwise. */

   char *old_field, *new_field;
   char mname[] = "dbeng_put_subfield";
   int field_len, new_field_len, new_fnum, new_sfnum;
   int nwords;
   int ret;

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_data == NULL)
      {
      dbeng_io_code_log(mname, "null[new_data]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_put_which_subfield(table, field_num, subfield_num,
       &new_fnum, &new_sfnum)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_put_which_subfield", ret);
      return(ret);
      }

   logman("%s:enter,tid=%d,d=%s,l=%d,f=%d,sf=%d,nf=%d,nsf=%d", mname, 
                 table->tid, new_data, strlen(new_data), field_num, 
                 subfield_num, new_fnum, new_sfnum);

   /* if the field does not already exist, create it 
      unless there is no current record */

   ret = dbeng_field_size(table, new_fnum, &field_len);

   switch(ret)
      {
      case DBENG_OK:
         break;

      case DBENG_NO_RECORD:
         field_len = 0;
         break;

      case DBENG_NO_SUCH_FIELD:
         logman("%s:field[%d] does not exist,creating it",
                       mname, new_fnum);

         if ((ret = dbeng_put_field(table, "", new_fnum)) != DBENG_OK)
            {
            dbeng_io_code_log(mname, "bad rc from dbeng_put_field", ret);
            return(ret);
            }

         break;

      default:
         dbeng_io_code_log(mname, "bad rc from dbeng_field_size", ret);
         return(ret);
      };

   /* it is possible that the field is empty, in which case, create a stub */

   if (!field_len)
      {
      if ((old_field = (char *)malloc(25)) == (char *)NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[old_field]1", DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      old_field[0] = EOS;
      }
   else
      {
      if ((old_field = (char *)malloc(field_len + 1)) == (char *)NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[old_field]2", DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      if ((ret = dbeng_get_field(table, old_field, new_fnum)) != DBENG_OK)
         {
         free(old_field);
         dbeng_io_code_log(mname, "bad rc from dbeng_get_field", ret);
         return(ret);
         }
      }

   nwords = ll_words(old_field, DBENG_SFM);
   new_field_len = strlen(old_field) + strlen(new_data) + 1;

   if (new_sfnum > nwords)
      new_field_len += new_sfnum - nwords;

   if ((new_field = malloc(new_field_len)) == NULL)
      {
      free(old_field);
      dbeng_io_code_log(mname, "alloc fail[new_field]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if (!ll_wordput(old_field, new_field, new_data, new_sfnum, DBENG_SFM))
      {
      free(old_field);
      free(new_field);
      dbeng_io_code_log(mname, "bad ret[ll_wordput]",
                        DBENG_PUT_ERROR);
      return(DBENG_PUT_ERROR);
      }

   free(old_field);

   /* replace field */

   if ((ret = dbeng_put_field(table, new_field, new_fnum)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret[dbeng_put_field]", ret);
      free(new_field);
      return(ret);
      }

   free(new_field);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_put_subsubfield(struct dbeng_table *table, char *new_data,
                       int field_num, int subfield_num, int subsubfield_num)
{
   /* Store new data in a sub-sub-field and re-size record as
      appropriate. Function returns 'DBENG_OK' upon success,
      error code otherwise. */

   char *old_subfield, *new_subfield;
   char mname[] = "dbeng_put_subsubfield";
   int subfield_len, new_subfield_len, new_fnum, new_sfnum, new_ssfnum;
   int nwords;
   int ret;

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_data == NULL)
      {
      dbeng_io_code_log(mname, "null[new_data]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_put_which_subsubfield(table, field_num, subfield_num,
       subsubfield_num, &new_fnum, &new_sfnum, &new_ssfnum)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_put_which_subsubfield", ret);
      return(ret);
      }

   logman("%s:enter,tid=%d,d=%s,l=%d,f=%d,sf=%d,ssf=%d,nf=%d,nsf=%d,"
                 "nssf=%d", mname, table->tid, new_data, strlen(new_data), 
                 field_num, subfield_num, subsubfield_num, new_fnum,
                 new_sfnum, new_ssfnum);

   /* if the field and/or sub-field does not already exist, create it */

   ret = dbeng_subfield_size(table, new_fnum, new_sfnum, &subfield_len);

   switch(ret)
      {
      case DBENG_OK:
         break;

      case DBENG_NO_RECORD:
         subfield_len = 0;
         break;

      case DBENG_NO_SUCH_FIELD:
      case DBENG_NO_SUCH_SUBFIELD:
         logman("%s:field or subfield does not exist,creating it",
                       mname);

         if ((ret = dbeng_put_subfield(table, "", new_fnum, new_sfnum)) != 
             DBENG_OK)
            {
            dbeng_io_code_log(mname, "bad rc from dbeng_put_field", ret);
            return(ret);
            }

         break;

      default:
         dbeng_io_code_log(mname, "bad rc from dbeng_subfield_size", ret);
         return(ret);
      };

   /* it is possible that the field is empty, in which case, create a stub */

   if (!subfield_len)
      {
      if ((old_subfield = (char *)malloc(25)) == (char *)NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[old_subfield]1",
                           DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      old_subfield[0] = EOS;
      }
   else
      {
      if ((old_subfield = (char *)malloc(subfield_len + 1)) == (char *)NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[old_subfield]2",
                           DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      if ((ret = dbeng_get_subfield(table, old_subfield, new_fnum,
           new_sfnum)) != DBENG_OK)
         {
         free(old_subfield);
         dbeng_io_code_log(mname, "bad rc from dbeng_get_subfield", ret);
         return(ret);
         }
      }

   nwords = ll_words(old_subfield, DBENG_SSFM);
   new_subfield_len = strlen(old_subfield) + strlen(new_data) + 1;

   if (new_ssfnum > nwords)
      new_subfield_len += new_ssfnum - nwords;

   if ((new_subfield = (char *)malloc(new_subfield_len)) == (char *)NULL)
      {
      free(old_subfield);
      dbeng_io_code_log(mname, "alloc fail[new_subfield]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if (!ll_wordput(old_subfield, new_subfield, new_data, new_ssfnum,
       DBENG_SSFM))
      {
      free(old_subfield);
      free(new_subfield);
      dbeng_io_code_log(mname, "bad ret[ll_wordput]",
                        DBENG_PUT_ERROR);
      return(DBENG_PUT_ERROR);
      }

   free(old_subfield);

   /* replace sub-field */

   if ((ret = dbeng_put_subfield(table, new_subfield, new_fnum,
        new_sfnum)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret[dbeng_put_subfield]", ret);
      free(new_subfield);
      return(ret);
      }

   free(new_subfield);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_find(struct dbeng_table *ot, int cs_flag, const char *find_str,
               int *field_num)
{
   /* Locate 'find_str' within record data. All fields within
      each record will be searched. Find will begin at current
      record and continue until EOF or a record is found. Only whole
      fields will match. String comparison is based on 'cs_flag' for
      case sensitivity ('1' is case insensitivity, '0' is case sensitivity).
      Function returns 'DBENG_OK' upon success, an
      error code otherwise. If the find is successful, 'field_num' will
      be loaded with field number that matched. */

   char *field_data;
   char mname[50];
   int nwords, cmp_result;
   int done;
   int ret;
   int i;

   strcpy(mname, "dbeng_find");

   if (ot == NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (cs_flag != TRUE && cs_flag != FALSE)
      {
      dbeng_io_code_log(mname, "must be 0|1[cs_flag]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (find_str == NULL)
      {
      dbeng_io_code_log(mname, "null[find_str]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (field_num == NULL)
      {
      dbeng_io_code_log(mname, "null[field_num]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   logman("%s:enter,tid=%d,cs=%d,fs=%s,l=%d", mname, ot->tid, cs_flag,
           find_str, strlen(find_str));

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   /* get a record if none is present (do not count unsaved rec in memory
      if one is present) */

   if (ot->record_number == 0L)
      {
      logman("%s:no current record,getting first", mname);
      (void)dbeng_goto_top(ot);
      ret = dbeng_get_recd(ot);

      if (ret != DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad ret first[get_recd]", ret);
         return(ret);
         }
      }

   done = FALSE;
   *field_num = 0;
   ot->change_rec_flag = FALSE;

   while(!done)
      {
      if ((field_data = malloc(strlen(ot->rec) + 1)) == NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[field_data]",
                           DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      nwords = ll_words(ot->rec, DBENG_FM);

      for(i = 1; i <= nwords; i++)
         {
         ret = dbeng_get_field(ot, field_data, i);

         if (ret != DBENG_OK)
            {
            free(field_data);
            dbeng_io_code_log(mname, "bad ret[get_field]", ret);
            return(ret);
            }

         if ((ret = dbeng_compare_field(field_data, (char *)find_str, cs_flag,
                                        &cmp_result)) != DBENG_OK)
            {
            free(field_data);
            dbeng_io_code_log(mname, "bad ret[dbeng_compare_field]", ret);
            return(ret);
            }

         if (!cmp_result)
            {
            *field_num = i;
            free(field_data);
            dbeng_io_code_log(mname, "match found,normal exit", DBENG_OK);
            return(DBENG_OK);
            }
         }

      free(field_data);
      ret = dbeng_get_recd(ot);

      if (ret != DBENG_OK)
         done = TRUE;
      }

   dbeng_io_code_log(mname, "no match,normal exit", ret);
   return(ret);
}

int dbeng_find_field(struct dbeng_table *ot, int cs_flag, const char *find_str,
                     int field_num)
{
   /* Locate 'find_str' within the 'field_num' field of a record.
      Each record will be searched. Find will begin at current
      record and continue until EOF or a record is found. Only
      whole fields will match. String comparison is obtained from
      'cs_flag', for case sensitivity. Function returns 'DBENG_OK'
      upon success, an error code otherwise. */

   char *field_data;
   char mname[50];
   int nwords, cmp_result;
   int done;
   int ret;

   strcpy(mname, "dbeng_find_field");

   if (ot == NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (cs_flag != TRUE && cs_flag != FALSE)
      {
      dbeng_io_code_log(mname, "must be 0|1[cs_flag]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (find_str == NULL)
      {
      dbeng_io_code_log(mname, "null[find_str]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (field_num <= 0)
      {
      dbeng_io_code_log(mname, "illegal[field_num]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   logman("%s:enter,tid=%d,cs=%d,fs=%s,l=%d,fn=%d", mname,
                 ot->tid, cs_flag, find_str, strlen(find_str), field_num);

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   /* get a record if none is present (do not count unsaved rec in memory
      if one is present) */

   if (ot->record_number == 0L)
      {
      logman("%s:no current record,getting first", mname);
      (void)dbeng_goto_top(ot);
      ret = dbeng_get_recd(ot);

      if (ret != DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad ret first[get_recd]", ret);
         return(ret);
         }
      }

   done = FALSE;
   ot->change_rec_flag = FALSE;

   while(!done)
      {
      if ((field_data = malloc(strlen(ot->rec) + 1)) == NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[field_data]",
                           DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      nwords = ll_words(ot->rec, DBENG_FM);

      if (nwords >= field_num)
         {
         ret = dbeng_get_field(ot, field_data, field_num);

         if (ret != DBENG_OK)
            {
            free(field_data);
            dbeng_io_code_log(mname, "bad ret[get_field]", ret);
            return(ret);
            }

         if ((ret = dbeng_compare_field(field_data, (char *)find_str, cs_flag,
                                        &cmp_result)) != DBENG_OK)
            {
            free(field_data);
            dbeng_io_code_log(mname, "bad ret[dbeng_compare_field", ret);
            return(ret);
            }

         if (!cmp_result)
            {
            free(field_data);
            dbeng_io_code_log(mname, "match found,normal exit", DBENG_OK);
            return(DBENG_OK);
            }
         }

      free(field_data);
      ret = dbeng_get_recd(ot);

      if (ret != DBENG_OK)
         done = TRUE;
      }

   dbeng_io_code_log(mname, "no match,normal exit", ret);
   return(ret);
}

int dbeng_find_part(struct dbeng_table *ot, int cs_flag, const char *find_str,
                    int *field_num)
{
   /* Locate 'find_str' within record data. All fields within
      each record will be searched. Find will begin at current
      record and continue until EOF or a record is found. Any part
      of a field will match. String comparison is based on 'cs_flag'
      for case sensitivity. Function returns 'DBENG_OK' upon success,
      an error code otherwise. If the find is successful, 'field_num' will
      be loaded with field number that matched. */

   char *field_data;
   char mname[50];
   int nwords;
   int done;
   int ret;
   int process;
   int field_len;
   int len_find_str;
   int i, j;

   strcpy(mname, "dbeng_find_part");

   if (ot == NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (cs_flag != TRUE && cs_flag != FALSE)
      {
      dbeng_io_code_log(mname, "must be 0|1[cs_flag]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (find_str == NULL)
      {
      dbeng_io_code_log(mname, "null[find_str]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (field_num == NULL)
      {
      dbeng_io_code_log(mname, "null[field_num]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   logman("%s:enter,tid=%d,cs=%d,fs=%s,l=%d", mname, ot->tid, cs_flag,
           find_str, strlen(find_str));

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   /* get a record if none is present (do not count unsaved rec in memory
      if one is present) */

   if (ot->record_number == 0L)
      {
      (void)dbeng_goto_top(ot);
      ret = dbeng_get_recd(ot);

      if (ret != DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad ret first[get_recd]", ret);
         return(ret);
         }
      }

   done = FALSE;
   len_find_str = strlen(find_str);
   *field_num = 0;
   ot->change_rec_flag = FALSE;

   while(!done)
      {
      if ((field_data = malloc(strlen(ot->rec) + 1)) == NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[field_data]",
                           DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      nwords = ll_words(ot->rec, DBENG_FM);

      for(i = 1; i <= nwords; i++)
         {
         ret = dbeng_get_field(ot, field_data, i);

         if (ret != DBENG_OK)
            {
            free(field_data);
            dbeng_io_code_log(mname, "bad ret[get_field]", ret);
            return(ret);
            }

         field_len = strlen(field_data);
         process = TRUE;

         /* do not compare if length of field is less than
            length of search string */

         if (field_len < len_find_str)
            process = FALSE;

         if (process)
            {
            for(j = 0; j < field_len; j++)
               {
               if (strlen(&field_data[j]) < len_find_str)
                  break;

               if (cs_flag)
                  ret = strnicmp(&field_data[j], (char *)find_str, len_find_str);
               else
                  ret = strncmp(&field_data[j], find_str, len_find_str);

               if (!ret)
                  {
                  *field_num = i;
                  free(field_data);
                  dbeng_io_code_log(mname, "match found,normal exit",
                                    DBENG_OK);
                  return(DBENG_OK);
                  }
               }
            }
         }

      free(field_data);
      ret = dbeng_get_recd(ot);

      if (ret != DBENG_OK)
         done = TRUE;
      }

   dbeng_io_code_log(mname, "no match,normal exit", ret);
   return(ret);
}

int dbeng_find_field_part(struct dbeng_table *ot, int cs_flag,
                          const char *find_str, int field_num)
{
   /* Locate 'find_str' within the 'field_num' field of a record.
      Each record will be searched. Find will begin at current
      record and continue until EOF or a record is found. Any part
      of the field will match. String comparison is obtained from
      'cs_flag', for case sensitivity. Function returns 'DBENG_OK'
      upon success, an error code otherwise. */

   char *field_data;
   char mname[50];
   int field_len;
   int len_find_str;
   int process;
   int nwords;
   int done;
   int ret;
   int i;

   strcpy(mname, "dbeng_find_field_part");

   if (ot == NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (cs_flag != TRUE && cs_flag != FALSE)
      {
      dbeng_io_code_log(mname, "must be 0|1[cs_flag]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (find_str == NULL)
      {
      dbeng_io_code_log(mname, "null[find_str]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (field_num <= 0)
      {
      dbeng_io_code_log(mname, "illegal[field_num]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   logman("%s:enter,tid=%d,cs=%d,fs=%s,l=%d,fn=%d", mname,
                 ot->tid, cs_flag, find_str, strlen(find_str), field_num);

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   /* get a record if none is present (do not count unsaved rec in memory
      if one is present) */

   if (ot->record_number == 0L)
      {
      (void)dbeng_goto_top(ot);
      ret = dbeng_get_recd(ot);

      if (ret != DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad ret first[get_recd]", ret);
         return(ret);
         }
      }

   done = FALSE;
   len_find_str = strlen(find_str);
   ot->change_rec_flag = FALSE;

   while(!done)
      {
      if ((field_data = malloc(strlen(ot->rec) + 1)) == NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[field_data]",
                           DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      process = TRUE;
      nwords = ll_words(ot->rec, DBENG_FM);

      if (field_num > nwords)
         process = FALSE;

      if (process)
         {
         ret = dbeng_get_field(ot, field_data, field_num);

         if (ret != DBENG_OK)
            {
            free(field_data);
            dbeng_io_code_log(mname, "bad ret[get_field]", ret);
            return(ret);
            }

         field_len = strlen(field_data);

         if (field_len < len_find_str)
            process = FALSE;
         }

      if (process)
         {
         for(i = 0; i < field_len; i++)
            {
            if (strlen(&field_data[i]) < len_find_str)
               break;

            if (cs_flag)
               ret = strnicmp(&field_data[i], (char *)find_str, len_find_str);
            else
               ret = strncmp(&field_data[i], find_str, len_find_str);

            if (!ret)
               {
               free(field_data);
               dbeng_io_code_log(mname, "match found,normal exit", DBENG_OK);
               return(DBENG_OK);
               }
            }
         }

      free(field_data);
      ret = dbeng_get_recd(ot);

      if (ret != DBENG_OK)
         done = TRUE;
      }

   dbeng_io_code_log(mname, "no match,normal exit", ret);
   return(ret);
}

int dbeng_nfields(struct dbeng_table *ot, int *nfields)
{
   /* Count the number of fields in the current record. Number
      of fields is loaded into 'nfields' upon success. Function
      returns 'DBENG_OK' upon success, an error code otherwise. */

   char mname[50];
   int ret;

   strcpy(mname, "dbeng_nfields");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (nfields == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[nfields]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *nfields = 0;

   if ((ret = dbeng_is_active_rec(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   *nfields = ll_words(ot->rec, DBENG_FM);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_nsubfields(struct dbeng_table *ot, int fieldno, int *nsubfields)
{
   /* Count the number of sub-fields in the 'fieldno' field. Number
      of sub-fields is loaded into 'nsubfields' upon success. Function
      returns 'DBENG_OK' upon success, an error code otherwise. */

   char mname[] = "dbeng_nsubfields", *thefield;
   int ret, fsize;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (fieldno <= 0)
      {
      dbeng_io_code_log(mname, "invalid[fieldno]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (nsubfields == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[nsubfields]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *nsubfields = 0;

   if ((ret = dbeng_is_active_rec(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   /* attempt to get the size of the requested field */

   if ((ret = dbeng_field_size(ot, fieldno, &fsize)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_field_size", ret);
      return(ret);
      }

   if ((thefield = (char *)malloc(fsize + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[thefield]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if ((ret = dbeng_get_field(ot, thefield, fieldno)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret from dbeng_get_field", ret);
      free(thefield);
      return(ret);
      }

   *nsubfields = ll_words(thefield, DBENG_SFM);
   free(thefield);
   logman("%s:normal exit:nsubfields=%d", mname, *nsubfields);
   return(DBENG_OK);
}

int dbeng_nsubsubfields(struct dbeng_table *ot, int fieldno,
                        int sfieldno, int *nsubsubfields)
{
   /* Count the number of sub-sub-fields in the 'sfieldno' sub-field. Number
      of sub-sub-fields is loaded into 'nsubsubfields' upon success. Function
      returns 'DBENG_OK' upon success, an error code otherwise. */

   char mname[] = "dbeng_nsubsubfields", *thesfield;
   int ret, sfsize;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (fieldno <= 0)
      {
      dbeng_io_code_log(mname, "invalid[fieldno]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (sfieldno <= 0)
      {
      dbeng_io_code_log(mname, "invalid[sfieldno]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (nsubsubfields == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[nsubsubfields]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *nsubsubfields = 0;

   if ((ret = dbeng_is_active_rec(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   /* attempt to get the size of the requested sub-field */

   if ((ret = dbeng_subfield_size(ot, fieldno, sfieldno, &sfsize)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_subfield_size", ret);
      return(ret);
      }

   if ((thesfield = (char *)malloc(sfsize + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[thesfield]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if ((ret = dbeng_get_subfield(ot, thesfield, fieldno, sfieldno)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret from dbeng_get_subfield", ret);
      free(thesfield);
      return(ret);
      }

   *nsubsubfields = ll_words(thesfield, DBENG_SSFM);
   free(thesfield);
   logman("%s:normal exit:nsubsubfields=%d", mname, *nsubsubfields);
   return(DBENG_OK);
}

int dbeng_count_rec(struct dbeng_table *table, long *active_count,
                    long *deleted_count)
{
   /* Physically count the number active and deleted records in 'table'
      and return upon success. Function returns 'DBENG_OK'
      upon success, an error code otherwise. */

   char mname[] = "dbeng_count_rec";
   long active_num = 0L;
   long deleted_num = 0L;
   int done = FALSE;
   int ret, delete_flag;

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FILE_NAME);
      return(DBENG_INVALID_FILE_NAME);
      }

   if (active_count == (long *)NULL)
      {
      dbeng_io_code_log(mname, "null[active_count]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (deleted_count == (long *)NULL)
      {
      dbeng_io_code_log(mname, "null[deleted_count]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *active_count = *deleted_count = 0L;
   logman("%s:enter:tid=%d", mname, table->tid);

   if (table->enforce_change_rec_flag && table->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   /* get current value of table 'delete_flag' */

   (void)dbeng_delete_flag(table, &delete_flag);

   /* set delete flag */

   (void)dbeng_set_delete_flag(table, TRUE);
   ret = dbeng_goto_top(table);

   if (ret != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error going top", ret);
      return(ret);
      }

   while(!done)
      {
      ret = dbeng_get_recd(table);

      if (ret == DBENG_EOF)
         done = TRUE;
      else
         if (ret != DBENG_OK)
            return(ret);
         else
            table->record_status ? active_num++ : deleted_num++;
      }

   /* set flag back to original value */

   (void)dbeng_set_delete_flag(table, delete_flag);
   logman("%s:normal exit:%ld active,%ld deleted records counted", 
                 mname, active_num, deleted_num);
   *active_count = active_num;
   *deleted_count = deleted_num;
   return(DBENG_OK);
}

int dbeng_rec_count(struct dbeng_table *ot, long *active_count,
                    long *deleted_count)
{
   /* Obtain the current active and deleted record counts. Function returns the
      record count values and 'DBENG_OK' upon success, an error
      code otherwise. */

   char mname[75];

   strcpy(mname, "dbeng_record_count");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (active_count == (long *)NULL)
      {
      dbeng_io_code_log(mname, "null[active_count]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (deleted_count == (long *)NULL)
      {
      dbeng_io_code_log(mname, "null[deleted_count]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *active_count = ot->active_record_count;
   *deleted_count = ot->deleted_record_count;
   logman("%s:normal exit[ok]", mname);
   return(DBENG_OK);
}

int dbeng_goto_record(struct dbeng_table *table, long rec_num)
{
   /* Goto a specific record in the file. */

   char mname[50];
   long i;
   int ret;

   strcpy(mname, "dbeng_goto_record");

   if (table == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[table]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (rec_num <= 0L)
      {
      dbeng_io_code_log(mname, "out of range[rec_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   logman("%s:enter:rn=%ld", mname, rec_num);

   if (table->enforce_change_rec_flag && table->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   ret = dbeng_goto_top(table);

   if (ret != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error going top", ret);
      return(ret);
      }

   ret = dbeng_get_recd(table);

   if (ret != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret first[get_recd]", ret);
      return(ret);
      }

   for(i = 1L; i < rec_num; i++)
      if (dbeng_get_recd(table) == DBENG_EOF && i < rec_num)
         {
         dbeng_io_code_log(mname, "exit with eof", DBENG_EOF);
         return(DBENG_EOF);
         }

   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_get_recd(struct dbeng_table *ot)
{
   /* Obtain the next record in the file. */

   char mname[] = "dbeng_get_recd";
   char ferror[128], rec_status;
   long file_pos;
   int bytes_read, len;
   int done = FALSE, process;
   int ret;

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   /* loop to get record */

   while(!done)
      {
      file_pos = ftell(ot->handle);
      /* logman("%s:fp=%ld,op=%ld,cp=%ld", mname, file_pos,
             ot->orig_position, ot->current_position); */

      /* read and verify record header */

      if ((ret = dbeng_record_header(ot, &len, &rec_status)) != DBENG_OK)
         {
         if (ret == DBENG_FILE_DATA_ERROR)
            {
            sprintf(ferror, "%s:error in header of "
                    "record %ld,tid=%d", mname,
                    ot->record_number + 1L, ot->tid);
            dbeng_ferror(ferror);
            }

         /* goto top of table on any error code */

         (void)dbeng_goto_top(ot);
         return(ret);
         }

      /* store starting position of this record */

      ot->orig_position = file_pos;

      if (DBENG_MAXRECFIELD > 0 && len >= DBENG_MAXRECFIELD)
         {
         sprintf(ferror, "%s:error:record %ld too large(%d)\n"
                       "must be no greater than %d,table=%s",
                       mname, ot->record_number, len, DBENG_MAXRECFIELD,
                       ot->name);
         dbeng_ferror(ferror);
         return(DBENG_RECORD_TOO_LARGE);
         }

      /* get status and process only if active record considering the
         'process_deleted' flag */

      process = (rec_status == 'A') ? TRUE : FALSE;
      ot->record_status = process;

      if (!process)
         if (rec_status == 'D' && ot->process_deleted)
            process = TRUE;

      /* if marked for deletion, skip */

      if (!process && rec_status == 'D')
         {
         ret = dbeng_rec_resize(ot, len);

         if (ret != DBENG_OK)
            return(ret);

         bytes_read = fread(ot->rec, 1, len, ot->handle);

         if (bytes_read < len)
            {
            sprintf(ferror, "%s:unexpected end of file reading record %ld"
                    " of table %s", mname, ot->record_number + 1L,
                    ot->name);
            dbeng_ferror(ferror);
            return(DBENG_FILE_DATA_ERROR);
            }

         /* error if record mark is not the last byte */

         if ((unsigned char)ot->rec[len - 1] != DBENG_RM)
            {
            sprintf(ferror, "%s:error, no record mark "
                    "on record %ld,table=%s", mname, ot->record_number + 1L,
                    ot->name);
            dbeng_ferror(ferror);
            return(DBENG_FILE_DATA_ERROR);
            }
         }

      if (process)
         {
         ret = dbeng_rec_resize(ot, len);

         if (ret != DBENG_OK)
            return(ret);

         done = TRUE;
         bytes_read = fread(ot->rec, 1, len, ot->handle);

         if (bytes_read < len)
            {
            sprintf(ferror, "%s:unexpected end of file reading record %ld"
                          ",table=%s", mname, ot->record_number + 1L,
                          ot->name);
            dbeng_ferror(ferror);
            return(DBENG_FILE_DATA_ERROR);
            }

         if ((unsigned char)ot->rec[len - 1] != DBENG_RM)
            {
            sprintf(ferror, "%s:error, no record mark on record %ld,"
                          "table=%s", mname, ot->record_number + 1L,
                          ot->name);
            dbeng_ferror(ferror);
            return(DBENG_FILE_DATA_ERROR);
            }

         ot->rec[len - 1] = EOS;        /* remove record separator */
         ot->current_position = ftell(ot->handle);
         // logman("%s:cp=%ld", mname, ot->current_position);
         ot->record_number++;
         }
      }

   return(DBENG_OK);
}

int dbeng_rec_resize(struct dbeng_table *ot, const int new_size)
{
   /* Resize the table record to 'new_size'. Function returns
      'DBENG_OK' upon success, an error code otherwise. */

   char mname[50];

   strcpy(mname, "dbeng_rec_resize");

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_size <= 0)
      {
      dbeng_io_code_log(mname, "out of range[new_size]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* delete current record, if one present */

   if (dbeng_is_active_rec(ot) == DBENG_OK)
      free(ot->rec);

   /* allocate new record */

   if ((ot->rec = malloc(new_size + 1)) == (char *)NULL)
      return(DBENG_MEMORY_FAIL);

   return(DBENG_OK);
}

int dbeng_rec_size(struct dbeng_table *ot, int *rec_size)
{
   /* Get the current record size or length. Upon success,
      'rec_size' will be loaded with the size and function
      will return 'DBENG_OK'. Function will return an
      error code (and 'rec_size' will be set to zero) upon
      failure. */

   char mname[50];
   int ret;

   strcpy(mname, "dbeng_rec_size");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (rec_size == NULL)
      {
      dbeng_io_code_log(mname, "null[rec_size]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *rec_size = 0;

   if ((ret = dbeng_is_active_rec(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   *rec_size = strlen(ot->rec);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_field_size(struct dbeng_table *ot, const int field_num,
                     int *field_size)
{
   /* Get the size or length of field 'field_num' in the current
      record. Variable 'field_size' will be loaded with the
      field size upon success. Function returns 'DBENG_OK'
      upon success, an error code otherwise. */

   char mname[] = "dbeng_field_size";
   int ret;
   int nfields;

   logman("%s:enter:fn=%d", mname, field_num);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (field_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[field_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (field_size == NULL)
      {
      dbeng_io_code_log(mname, "null[field_size]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *field_size = 0;

   if ((ret = dbeng_is_active_rec(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   if ((ret = dbeng_nfields(ot, &nfields)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret[nfields]", ret);
      return(ret);
      }

   if (field_num > nfields)
      {
      dbeng_io_code_log(mname, "field out of range", DBENG_NO_SUCH_FIELD);
      return(DBENG_NO_SUCH_FIELD);
      }

   *field_size = ll_wordlen(ot->rec, field_num, DBENG_FM);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_subfield_size(struct dbeng_table *ot, int field_num,
                        int subfield_num, int *subfield_size)
{
   /* Get the size or length of sub-field 'subfield_num'
      within the field 'field_num' in the current
      record. Variable 'subfield_size' will be loaded with the
      sub-field size upon success. Function returns 'DBENG_OK'
      upon success, an error code otherwise. */

   char mname[] = "dbeng_subfield_size", *thefield;
   int ret, nsubfields, fsize;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (field_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[field_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subfield_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[subfield_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subfield_size == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[subfield_size]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *subfield_size = 0;

   if ((ret = dbeng_is_active_rec(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   if ((ret = dbeng_nsubfields(ot, field_num, &nsubfields)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_nsubfields", ret);
      return(ret);
      }

   if (subfield_num > nsubfields)
      {
      dbeng_io_code_log(mname, "out of range[subfield_num]",
                        DBENG_NO_SUCH_SUBFIELD);
      return(DBENG_NO_SUCH_SUBFIELD);
      }

   /* attempt to get the size of the requested field */

   if ((ret = dbeng_field_size(ot, field_num, &fsize)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_field_size", ret);
      return(ret);
      }

   if ((thefield = (char *)malloc(fsize + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[thefield]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if ((ret = dbeng_get_field(ot, thefield, field_num)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret from dbeng_get_field", ret);
      free(thefield);
      return(ret);
      }

   *subfield_size = ll_wordlen(thefield, subfield_num, DBENG_SFM);
   free(thefield);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_subsubfield_size(struct dbeng_table *ot, int field_num,
                           int subfield_num, int subsubfield_num,
                           int *subsubfield_size)
{
   /* Get the size or length of sub-sub-field 'subsubfield_num'
      within the sub-field 'subfield_num' in the current
      record. Variable 'subsubfield_size' will be loaded with the
      sub-sub-field size upon success. Function returns 'DBENG_OK'
      upon success, an error code otherwise. */

   char mname[] = "dbeng_subsubfield_size", *thesubfield;
   int ret, nsubsubfields, sfsize;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (field_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[field_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subfield_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[subfield_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subsubfield_num <= 0)
      {
      dbeng_io_code_log(mname, "out of range[subsubfield_num]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (subsubfield_size == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[subsubfield_size]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *subsubfield_size = 0;

   if ((ret = dbeng_is_active_rec(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   if ((ret = dbeng_nsubsubfields(ot, field_num, subfield_num,
        &nsubsubfields)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_nsubsubfields", ret);
      return(ret);
      }

   if (subsubfield_num > nsubsubfields)
      {
      dbeng_io_code_log(mname, "out of range[subsubfield_num]",
                        DBENG_NO_SUCH_SUBSUBFIELD);
      return(DBENG_NO_SUCH_SUBSUBFIELD);
      }

   /* attempt to get the size of the requested sub-field */

   if ((ret = dbeng_subfield_size(ot, field_num, subfield_num,
        &sfsize)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_subfield_size", ret);
      return(ret);
      }

   if ((thesubfield = (char *)malloc(sfsize + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[thesubfield]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if ((ret = dbeng_get_subfield(ot, thesubfield, field_num,
        subfield_num)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret from dbeng_get_subfield", ret);
      free(thesubfield);
      return(ret);
      }

   *subsubfield_size = ll_wordlen(thesubfield, subsubfield_num, DBENG_SSFM);
   free(thesubfield);
   logman("%s:normal exit", mname);
   return(DBENG_OK);
}

int dbeng_rec_num(struct dbeng_table *ot, long *rec_num)
{
   /* Obtain the current record number. Function returns the
      current record number in 'rec_num' and 'DBENG_OK' upon
      success, an error code otherwise. */

   char mname[50];

   strcpy(mname, "dbeng_rec_num");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (rec_num == NULL)
      {
      dbeng_io_code_log(mname, "null[rec_num]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *rec_num = 0L;

   if (ot->record_number <= 0L)
      {
      dbeng_io_code_log(mname, "no current rec", DBENG_NO_RECORD);
      return(DBENG_NO_RECORD);
      }

   *rec_num = ot->record_number;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_pos(struct dbeng_table *ot, long *pos)
{
   /* Obtain the current file position. Function returns the
      position of the start of the current record in 'pos' and
      'DBENG_OK' upon success, an error code otherwise. */

   char mname[50];

   strcpy(mname, "dbeng_pos");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (pos == NULL)
      {
      dbeng_io_code_log(mname, "null[pos]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *pos = 0L;

   if (ot->orig_position < 0L)
      {
      dbeng_io_code_log(mname, "no current rec", DBENG_NO_RECORD);
      return(DBENG_NO_RECORD);
      }

   *pos = ot->orig_position;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_set_pos(struct dbeng_table *ot, long pos)
{
   /* Set the current file position. 'dbeng' structure
      member 'orig_position' will be updated along with
      moving the file pointer. BE VERY CAREFUL. You
      be sure there is a record at the specified
      'pos'. Current record contents are
      not changed. Only the file pointer position is moved.
      A record is not read into the buffer. Function
      returns 'DBENG_OK' upon success, an error code
      otherwise. */

   char mname[] = "dbeng_set_pos";

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (pos < 0L)
      {
      dbeng_io_code_log(mname, "illegal[pos]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (ot->enforce_change_rec_flag && ot->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   ot->orig_position = ot->current_position = pos;
   fseek(ot->handle, ot->orig_position, 0);
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_change_rec_flag(struct dbeng_table *ot, int *flag)
{
   /* Obtain the current 'change_rec_flag' value. Function returns the
      value of the flag in 'flag' and 'DBENG_OK' upon success, an error
      code otherwise. */

   char mname[50];
   int ret;

   strcpy(mname, "dbeng_change_rec_flag");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag == NULL)
      {
      dbeng_io_code_log(mname, "null[flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *flag = 0;

   if ((ret = dbeng_is_active_rec(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   *flag = ot->change_rec_flag;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_set_change_rec_flag(struct dbeng_table *ot, int flag)
{
   /* Set a new 'change_rec_flag' value. Function returns 'DBENG_OK' upon
      success, an error code otherwise. Flag must be either 'TRUE' or
      'FALSE'. */

   char mname[50];
   int ret;

   strcpy(mname, "dbeng_set_change_rec_flag");
   logman("%s:enter", mname);

   if (ot == NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag != TRUE && flag != FALSE)
      {
      dbeng_io_code_log(mname, "invalid[flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_is_active_rec(ot)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "no current rec", ret);
      return(ret);
      }

   ot->change_rec_flag = flag;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_delete_flag(struct dbeng_table *ot, int *flag)
{
   /* Obtain the current 'process_deleted' value. Function returns the
      value of the flag in 'flag' and 'DBENG_OK' upon success, an error
      code otherwise. */

   char mname[50];

   strcpy(mname, "dbeng_delete_flag");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag == NULL)
      {
      dbeng_io_code_log(mname, "null[flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *flag = ot->process_deleted;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_set_delete_flag(struct dbeng_table *ot, int flag)
{
   /* Set a new 'process_deleted' value. Function returns 'DBENG_OK' upon
      success, an error code otherwise. Flag must be either 'TRUE' or
      'FALSE'. */

   char mname[50];

   strcpy(mname, "dbeng_set_delete_flag");
   logman("%s:enter", mname);

   if (ot == NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag != TRUE && flag != FALSE)
      {
      dbeng_io_code_log(mname, "invalid[flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   ot->process_deleted = flag;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_enf_change_rec_flag(struct dbeng_table *ot, int *flag)
{
   /* Obtain the current 'enforce_change_rec_flag' value. Function
      returns the value of the flag in 'flag' and 'DBENG_OK' upon
      success, an error code otherwise. */

   char mname[50];

   strcpy(mname, "dbeng_enf_change_rec_flag");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag == NULL)
      {
      dbeng_io_code_log(mname, "null[flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *flag = ot->enforce_change_rec_flag;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_set_enf_change_rec_flag(struct dbeng_table *ot, int flag)
{
   /* Set a new 'enforce_change_rec_flag' value. Function returns
      'DBENG_OK' upon success, an error code otherwise. 'Flag' must
      be either 'TRUE' or 'FALSE'. */

   char mname[50];

   strcpy(mname, "dbeng_set_enf_change_rec_flag");
   logman("%s:enter", mname);

   if (ot == NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag != TRUE && flag != FALSE)
      {
      dbeng_io_code_log(mname, "invalid[flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   ot->enforce_change_rec_flag = flag;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_autopack(struct dbeng_table *ot, int *pvalue)
{
   /* Obtain the current 'autopack' value. Function returns the
      value in 'pvalue' and 'DBENG_OK' upon success, an error
      code otherwise. */

   char mname[] = "dbeng_autopack";
   int ret;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (pvalue == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[pvalue]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *pvalue = ot->autopack;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_set_autopack(struct dbeng_table *ot, int pvalue)
{
   /* Set a new 'autopack' value. Function returns 'DBENG_OK' upon
      success, an error code otherwise. */

   char mname[] = "dbeng_set_autopack";
   int ret;

   logman("%s:enter", mname);

   if (ot == NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (pvalue < 0)
      {
      dbeng_io_code_log(mname, "out of range[pvalue]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   ot->autopack = pvalue;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_is_table_locked(struct dbeng_table *ot, int *flag)
{
   /* Obtain the current 'is_table_locked' value. Function
      returns the value of the flag in 'flag' and 'DBENG_OK' upon
      success, an error code otherwise. */

   char mname[25];

   strcpy(mname, "dbeng_is_table_locked");
   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag == NULL)
      {
      dbeng_io_code_log(mname, "null[flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *flag = ot->is_table_locked;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_set_is_table_locked(struct dbeng_table *ot, int flag)
{
   /* Set a new 'is_table_locked' value. Function returns
      'DBENG_OK' upon success, an error code otherwise. 'Flag' must
      be either 'TRUE' or 'FALSE'. */

   char mname[30];
   int ret;

   strcpy(mname, "dbeng_set_is_table_locked");
   logman("%s:enter", mname);

   if (ot == NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag != TRUE && flag != FALSE)
      {
      dbeng_io_code_log(mname, "invalid[flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag && ot->is_table_locked)
      {
      dbeng_io_code_log(mname, "table already locked", DBENG_TABLE_LOCKED);
      return(DBENG_TABLE_LOCKED);
      }

   if (flag == ot->is_table_locked)
      {
      dbeng_io_code_log(mname, "flag is already same value", DBENG_OK);
      return(DBENG_OK);
      }

   if (flag)
      ret = dbeng_lock_table(ot);
   else
      ret = dbeng_unlock_table(ot);

   dbeng_io_code_log(mname, "normal exit", ret);
   return(ret);
}

int dbeng_lock_table(struct dbeng_table *ot)
{
   /* Lock a table. This low level function should only be called
      by the function 'dbeng_set_is_table_locked'. This function
      does not check the status of the flag first. Every table with
      the exact file name will be locked. Function returns
      'DBENG_OK' upon success, an error code otherwise. */

   struct dbeng_table *rov;
   int done = FALSE;

   if (dbeng_head == DBENG_OT_NULL)
      return(DBENG_NOT_EXIST);

   rov = dbeng_head;

   while(!done)
      {
      if (rov == DBENG_OT_NULL)
         done = TRUE;
      else
         {
         if (!strcmp(rov->name, ot->name))
            rov->is_table_locked = TRUE;
         }

      if (!done)
         rov = rov->next;
      }

   return(DBENG_OK);
}

int dbeng_unlock_table(struct dbeng_table *ot)
{
   /* Unlock a table. This low level function should only be called
      by the function 'dbeng_set_is_table_locked'. This function
      does not check the status of the flag first. Every table with
      the exact file name will be unlocked. Function returns
      'DBENG_OK' upon success, an error code otherwise. */

   struct dbeng_table *rov;
   int done = FALSE;

   if (dbeng_head == DBENG_OT_NULL)
      return(DBENG_NOT_EXIST);

   rov = dbeng_head;

   while(!done)
      {
      if (rov == DBENG_OT_NULL)
         done = TRUE;
      else
         {
         if (!strcmp(rov->name, ot->name))
            rov->is_table_locked = FALSE;
         }

      if (!done)
         rov = rov->next;
      }

   return(DBENG_OK);
}

int dbeng_tmp_systable(int *tid)
{
   /* Create a new system table with a unique file name.
      Function exists with the table open and the table
      tid placed into 'tid'. Function returns a 'dbeng' code. */

   char mname[] = "dbeng_tmp_systable", *tmp_path, rname[13];
   char *table_name;
   int ret, tmp_len;

   logman("%s:enter", mname);

   /* get temporary path from config data */

   if ((tmp_path = (char *)malloc(DBENG_PATH_LIMIT)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[tmp_path]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if ((ret = dbeng_config_get_tmp_path(tmp_path)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error getting tmp path", DBENG_INTERNAL_ERROR);
      free(tmp_path);
      return(DBENG_INTERNAL_ERROR);
      }

   logman("%s:before unique", mname);

   /* generate a unique file name */

   if (!unique(tmp_path, rname))
      {
      logman("%s:cannot create tmp table[%s%c%s]", mname, tmp_path,
              PATH_SEP, rname);
      free(tmp_path);
      return(DBENG_CANNOT_CREATE_TABLE);
      }

   logman("%s:after unique", mname);
   tmp_len = strlen(tmp_path) + strlen(rname) + 2;

   if ((table_name = (char *)malloc(tmp_len)) == (char *) NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[table_name]",
                        DBENG_MEMORY_FAIL);
      free(tmp_path);
      return(DBENG_MEMORY_FAIL);
      }

   /* build complete path and file name */

   sprintf(table_name, "%s%c%s", tmp_path, PATH_SEP, rname);
   free(tmp_path);
   logman("%s:tmp table is %s", mname, table_name);

   /* indicate to also open table in 'dbeng_new_systable' */

   *tid = 1;

   /* create new empty temporary system table */

   if ((ret = dbeng_new_systable(table_name, tid)) != DBENG_OK)
      {
      logman("%s:new table[%s] create error", mname, table_name);
      free(table_name);
      return(ret);
      }

   free(table_name);
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_new_systable(char *fname, int *tid)
{
   /* Create a new system table. If the 'tid' is a positive
      integer, also open the table. Function returns
      the table 'tid' upon success. Function returns
      a 'dbeng' code. */

   char mname[] = "dbeng_new_systable";
   int ret;

   if (fname == (char *)NULL || !strlen(fname))
      {
      dbeng_io_code_log(mname, "fname parameter error",
                        DBENG_INVALID_FILE_NAME);
      return(DBENG_INVALID_FILE_NAME);
      }

   logman("%s:enter,fname=%s", mname, fname);

   if (tid == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[tid]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_ll_new_table(fname, tid, TRUE)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_ll_new_table", ret);
      return(ret);
      }

   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_new_table(char *fname, int *tid)
{
   /* Create a new table. If the 'tid' is a positive
      integer, also open the table. Function returns
      the table 'tid' upon success. Function returns
      a 'dbeng' code. */

   char mname[] = "dbeng_new_table";
   int ret;

   if (fname == (char *)NULL || !strlen(fname))
      {
      dbeng_io_code_log(mname, "fname parameter error",
                        DBENG_INVALID_FILE_NAME);
      return(DBENG_INVALID_FILE_NAME);
      }

   logman("%s:enter,fname=%s", mname, fname);

   if (tid == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[tid]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_ll_new_table(fname, tid, FALSE)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_ll_new_table", ret);
      return(ret);
      }

   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

static int dbeng_ll_new_table(char *table_name, int *tid,
                              int systable_flag)
{
   /* Create a new empty table. If 'tid' is a positive
      number, the table will be opened and the TID will be
      returned in same. 'table_name' may be in two parts separated
      by a comma which will indicate the logical table name
      followed by the physical name, ie:

         test.primary,/hd1/bbuuzzb/tables/test.buz

      Do not put spaces before or after the comma.
      If the two part form is used, an entry in the system
      catalog will be made (whether or not the catalog flag
      is on). An entry with the same logical name must not
      already exist. If the two part form is not used, a
      physical file name is assumed (do not prefix
      the physical file name with 'DBENG_CATALOG_FNAME_IND'
      as in 'dbeng_open_table'. Function returns 'DBENG_OK' upon
      success, an engine i/o code otherwise. */

   char mname[] = "dbeng_ll_new_table";
   char *ntable;
   int ret;

   logman("%s:enter", mname);

   if (table_name == (char *)NULL || !strlen(table_name))
      {
      dbeng_io_code_log(mname, "null or empty[table_name]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (tid == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[tid]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* add an extra byte to the output table name in case the form
      '%name' is returned */

   if ((ntable = (char *)malloc(strlen(table_name) + 2)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[ntable]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   /* derive proper name that will be given to 'new.table',
      create the new empty file and catalog entry (if required) */

   if ((ret = dbeng_catalog_new_table(table_name, ntable, systable_flag)) 
      != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_catalog_new_table", ret);
      free(ntable);
      return(ret);
      }

   /* strcpy(ntable, table_name);

   if (!zcreate(ntable))
      {
      dbeng_io_code_log(mname, "error creating empty table",
                        DBENG_CANNOT_CREATE_TABLE);
      free(ntable);
      return(DBENG_CANNOT_CREATE_TABLE);
      } */

   if (*tid > 0)
      {
      ret = dbeng_ll_open_table(ntable, tid, systable_flag);
      dbeng_io_code_log(mname, "normal exit[with open]", ret);
      free(ntable);
      return(ret);
      }

   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   free(ntable);
   return(DBENG_OK);
}

int dbeng_delete_table(char *fname)
{
   /* Delete a 'dbeng' (Bbuuzzb) table by name. The catalog will be
      used to obtain the physical file name if the catalog
      flag is high. Both catalog entry and physical file
      will be deleted. The table must not be in current use by
      any other 'tid'. Function returns a 'dbeng' code. */

   struct dbeng_table *ot;
   char mname[] = "dbeng_delete_table", *pname;
   int ret, tid, catalog_flag;

   logman("%s:enter", mname);

   if (fname == (char *)NULL || !strlen(fname))
      {
      dbeng_io_code_log(mname, "null or empty[fname]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* verify table (and possibly catalog entry) by attempting
      to open the table */

   if ((ret = dbeng_open_table(fname, &tid)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_open_table", ret);
      return(ret);
      }

   /* get pointer to structure and get physical file name */

   if ((ot = dbeng_atid_lookup(tid)) == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "error getting table pointer",
                        DBENG_INTERNAL_ERROR);
      return(DBENG_INTERNAL_ERROR);
      }

   /* copy physical file name */

   if (ot->name == (char *)NULL || !strlen(ot->name))
      {
      dbeng_io_code_log(mname, "null or empty[ot->name]", DBENG_INTERNAL_ERROR);
      return(DBENG_INTERNAL_ERROR);
      }

   if ((pname = initstring(ot->name)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[pname]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   /* close table (if the session flag is high the physical file
      will already have been closed) */

   if ((ret = dbeng_close_table(ot)) != DBENG_OK)
      /* we might get a non-ok if the session table is active */

      dbeng_io_code_log(mname, "bad rc from dbeng_close_table", ret);
    
   /* make sure table is not in use by other tid's */

   if (dbeng_table_in_use(pname))
      {
      free(pname);
      dbeng_io_code_log(mname, "unable to delete table", DBENG_TABLE_IN_USE);
      return(DBENG_TABLE_IN_USE);
      }

   unlink(pname);
   free(pname);

   /* if catalog flag is high, attempt to obtain and delete
      the catalog entry (might fail if only the physical
      file name was supplied) */

   (void)dbeng_config_get_catalog_flag(&catalog_flag);

   if (catalog_flag)
      if ((ret = dbeng_catalog_delete(fname)) != DBENG_OK)
         dbeng_io_code_log(mname, "bad rc from dbeng_catalog_delete", ret);

   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_open_table_list(char *pat, char *list_out)
{
   /* Return the list of open tables. List is
      returned in 'list_out' which must be already
      allocated to sufficient size. The list is delimited
      by 'DEBNG_LIST_DELIM'. Each entry in the list contains
      the tid in use followed by the file path/name. The two
      fields in the list are delimited by a comma.
      The parameter 'pat' may contain a table path/file pattern
      acceptable to the 'pmatch' function. If 'pat' is
      'NULL' or empty, all tables in use
      will be returned. Function returns a 'dbeng' code. */

   struct dbeng_table *rov;
   char *before, *after, *wdata;
   char mname[] = "dbeng_open_table_list";
   int cnt, is_pat, process;

   logman("%s:enter", mname);

   if (list_out == (char *)NULL) 
      { 
      dbeng_io_code_log(mname, "null[list_out]", DBENG_INVALID_FUNCTION); 
      return(DBENG_INVALID_FUNCTION);
      }

   is_pat = (pat == (char *)NULL || !strlen(pat)) ? FALSE : TRUE;
   
   if (is_pat)
      logman("%s:pat=%s", mname, pat);

   list_out[0] = EOS;
   rov = dbeng_head;

   /* it is not an error if the list is empty */

   if (rov == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "list is empty", DBENG_OK);
      return(DBENG_OK);
      }

   if ((before = (char *)malloc(DBENG_MAXRECFIELD)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[before]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if ((after = (char *)malloc(DBENG_MAXRECFIELD)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[after]", DBENG_MEMORY_FAIL);
      free(before);
      return(DBENG_MEMORY_FAIL);
      }

   if ((wdata = (char *)malloc(1023)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[wdata]", DBENG_MEMORY_FAIL);
      free(before);
      free(after);
      return(DBENG_MEMORY_FAIL);
      }

   before[0] = after[0] = EOS;
   cnt = 1;

   while(rov != DBENG_OT_NULL)
      {
      process = TRUE;

      /* match physical file name to pattern (if supplied) */

      if (is_pat)
         if (!pmatch(pat, rov->name))
            {
            process = FALSE;
            logman("%s:%s is no match", mname, rov->name);
            }
        else
           logman("%s:%s is a match", mname, rov->name);

      if (process)
         {
         sprintf(wdata, "%d,%s", rov->tid, rov->name);

         if (!ll_wordput(before, after, wdata, cnt, DBENG_LIST_DELIM))
            {
            dbeng_io_code_log(mname, "error putting list item",
                              DBENG_INTERNAL_ERROR);
            free(before);
            free(after);
            free(wdata);
            return(DBENG_INTERNAL_ERROR);
            }

         cnt++;
         strcpy(before, after);
         }

      rov = rov->next;
      }

   strcpy(list_out, after);
   free(before);
   free(after);
   free(wdata);
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_exist(char *tname, int *exist_flag)
{
   /* Determine whether a database table exists. Naming
      convention is the same as for 'dbeng_open_table'.
      The flag 'exist_flag' will be loaded with the
      exist flag (TRUE=table exists). Function returns
      a 'dbeng' code. */

   struct dbeng_table *ot;
   char mname[] = "dbeng_exist";
   int ret, tid;

   logman("%s:enter", mname);

   if (tname == (char *)NULL || !strlen(tname))
      {
      dbeng_io_code_log(mname, "null or empty[tname]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (exist_flag == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[exist_flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *exist_flag = FALSE;

   if ((ret = dbeng_open_table(tname, &tid)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:table does not exist", DBENG_OK);
      return(DBENG_OK);
      }

   if ((ot = dbeng_atid_lookup(tid)) == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_atid_lookup",
                        DBENG_INTERNAL_ERROR);
      return(DBENG_INTERNAL_ERROR);
      }

   (void)dbeng_close_table(ot);
   *exist_flag = TRUE;
   dbeng_io_code_log(mname, "exit:table does exist", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_clear_table(char *tname)
{
   /* Clear a table of all records by deleting the table and
      re-creating it. The table must not already be in use.
      Function returns a 'dbeng' code. */

   struct dbeng_table *rov;
   char mname[] = "dbeng_clear_table", *fname;
   int ret, tid, len, systable_flag = FALSE;

   logman("%s:enter", mname);

   if (tname == (char *)NULL || !strlen(tname))
      {
      dbeng_io_code_log(mname, "null or empty[tname]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

#ifdef MULTIUSER
   // check for session table

   if (dbeng_config_is_session(tname))
      systable_flag = TRUE;
#endif

   // check for catalog

   if (!systable_flag && dbeng_config_is_catalog(tname))
      systable_flag = TRUE;

   if (systable_flag)
      {
      /* if it is either the session table or the catalog,
         the name is already the physical file name, just
         copy it */

      if ((fname = (char *)malloc(strlen(tname) + 1)) == (char *)NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[fname]", DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      strcpy(fname, tname);
      }

   /* open the table to make sure we access it's physical file name */

   if (!systable_flag)
      {
      if ((ret = dbeng_open_table(tname, &tid)) != DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad rc from dbeng_open_table", ret);
         return(ret);
         }

      if ((rov = dbeng_atid_lookup(tid)) == DBENG_OT_NULL)
         {
         dbeng_io_code_log(mname, "bad rc[NULL] from dbeng_atid_lookup",
                           DBENG_INTERNAL_ERROR);
         return(DBENG_INTERNAL_ERROR);
         }

      if ((len = strlen(rov->name)) == 0)
         {
         dbeng_io_code_log(mname, "table physical file name is empty",
                           DBENG_INTERNAL_ERROR);
         return(DBENG_INTERNAL_ERROR);
         }

      if ((fname = (char *)malloc(len + 1)) == (char *)NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[fname]", DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      strcpy(fname, rov->name);
      (void)dbeng_close_table(rov);
      }

   /* if the table is in use by other tid's, refuse */

   if (dbeng_table_in_use(fname))
      {
      free(fname);
      dbeng_io_code_log(mname, "unable to clear table", DBENG_TABLE_IN_USE);
      return(DBENG_TABLE_IN_USE);
      }
      
   unlink(fname);

   if (!zcreate(fname))
      {
      dbeng_io_code_log(mname, "error creating new empty table",
                        DBENG_INTERNAL_ERROR);
      free(fname);
      return(DBENG_INTERNAL_ERROR);
      }

   free(fname);
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_ll_open(struct dbeng_table *ot)
{
   /* Open the file associated with the table 'ot'.
      File will only be open if the 'handle' is
      null. Function returns 'DBENG_OK' upon
      success, an engine i/o code otherwise. */

   char mname[] = "dbeng_ll_open";

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "exit:null ot", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (ot->handle != (FILE *)NULL)
      {
      dbeng_io_code_log(mname, "exit:file already open",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ot->handle = fopen(ot->name, "rb+")) == (FILE *)NULL)
      {
      dbeng_io_code_log(mname, "exit:cannot open file", DBENG_UNABLE_TO_OPEN);
      return(DBENG_UNABLE_TO_OPEN);
      }

   /* assign a buffer */

   /* if (setvbuf(ot->handle, NULL, _IOFBF, DBENG_IO_BUF_SIZE))
      {
      fclose(ot->handle);
      dbeng_io_code_log(mname, "exit:error 'setvbuf' on file",
                        DBENG_UNABLE_TO_OPEN);
      return(DBENG_UNABLE_TO_OPEN);
      } */

   /* reset file position */

   if (ot->current_position > 0L)
      fseek(ot->handle, ot->current_position, 0);

   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_ll_close(struct dbeng_table *ot)
{
   /* Close the file handle associated with the table 'ot'.
      Function returns 'DBENG_OK' upon success, an engine
      i/o code otherwise. */

   char mname[] = "dbeng_ll_close";

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "exit:null ot", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   logman("%s:enter:tid=%d", mname, ot->tid);

   if (ot->handle == (FILE *)NULL)
      {
      dbeng_io_code_log(mname, "exit:file not open", DBENG_NO_SUCH_FILE);
      return(DBENG_NO_SUCH_FILE);
      }

   fclose(ot->handle);
   ot->handle = (FILE *)NULL;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_is_systable(struct dbeng_table *ot, int *flag)
{
   /* Obtain the current 'is_systable' value. Function
      returns the value of the flag in 'flag' and 'DBENG_OK' upon
      success, an error code otherwise. */

   char mname[] = "dbeng_is_systable";

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (flag == NULL)
      {
      dbeng_io_code_log(mname, "null[flag]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *flag = ot->is_systable;
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_copy_table(struct dbeng_table *src, struct dbeng_table *dest)
{
   /* Copy a table from source ('src') to destination ('dest'). No
      regard is given to the existing contents of the destination
      table (if any). Function returns a 'dbeng' code. */

   char mname[] = "dbeng_copy_table";
   int ret, session_flag;

   logman("%s:enter", mname);

   if (src == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[src]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (dest == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[dest]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* check for changed records in both source and destination table */

   if (src->enforce_change_rec_flag && src->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

   if (dest->enforce_change_rec_flag && dest->change_rec_flag)
      {
      dbeng_io_code_log(mname, "record changed", DBENG_RECORD_CHANGED);
      return(DBENG_RECORD_CHANGED);
      }

#ifdef MULTIUSER
   /* if the session table is active, the source table will be open
      but not the destination table, we open it here */

   (void)dbeng_config_get_session_flag(&session_flag);

   if (session_flag)
      (void)dbeng_ll_open(dest);
#endif

   /* use 'dbeng_ll_copy_table' to copy */

   if ((ret = dbeng_ll_copy_table(src, dest)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad ret from dbeng_ll_copy_table", ret);
      return(ret);
      }

#ifdef MULTIUSER
   if (session_flag)
      (void)dbeng_ll_close(dest);
#endif

   dbeng_io_code_log(mname, "normal exit", ret);
   return(ret);
}

static int dbeng_record_header(struct dbeng_table *ot, int *len, char *status)
{
   /* Read and verify the record header which is assumed to start
      at the current file position. Upon success, the record
      length is returned in 'len' and the record status is
      returned in 'status'. Function returns a 'dbeng' code. */

   char rec_head[DBENG_REC_HEAD_SIZE + 1];
   char mname[] = "dbeng_record_header";
   int rlen, bytes_read;

   /* logman("%s:enter", mname); */

   if (len == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[len]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (status == (char *)NULL)
      {
      dbeng_io_code_log(mname, "null[status]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* read record header, assume record pointer is at start of header */

   bytes_read = fread(rec_head, 1, DBENG_REC_HEAD_SIZE, ot->handle);

   if (bytes_read < DBENG_REC_HEAD_SIZE)
      {
      logman("%s:exit:eof detected,tid=%d", mname, ot->tid);
      return(DBENG_EOF);
      }

   /* error if field mark not present */

   if ((unsigned char)rec_head[DBENG_REC_HEAD_SIZE - 1] != DBENG_FM)
      {
      rec_head[DBENG_REC_HEAD_SIZE - 1] = EOS;
      logman("%s:no field mark at end of header:tid=%d,rh=%s,l=%d,rc=%d",
                    mname, ot->tid, rec_head, strlen(rec_head),
                    DBENG_FILE_DATA_ERROR);
      return(DBENG_FILE_DATA_ERROR);
      }

   /* remove field separator */

   rec_head[DBENG_REC_HEAD_SIZE - 1] = EOS;

   /* attempt to get record length */

   if (!qatoi(&rec_head[1], &rlen))
      {
      dbeng_io_code_log(mname, "error:header length is non-numeric",
                        DBENG_FILE_DATA_ERROR);
      return(DBENG_FILE_DATA_ERROR);
      }

   *len = rlen;
   *status = rec_head[0];
   /* dbeng_io_code_log(mname, "normal exit", DBENG_OK); */
   return(DBENG_OK);
}

static int dbeng_put_which_field(struct dbeng_table *ot, int fnum,
                                 int *new_fnum)
{
   /* Determine which field to put data into. A positive integer
      is assumed to be the absolute field number. A zero indicates
      to append a new field to the record. Function returns the
      field number loaded into 'new_fnum' upon success. Function
      returns a 'dbeng' code. */

   char mname[] = "dbeng_put_which_field";
   int nfields, ret;

   logman("%s:enter:fnum=%d", mname, fnum);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_fnum == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[new_fnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *new_fnum = 0;

   if (fnum < 0)
      {
      dbeng_io_code_log(mname, "out of range[fnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (fnum > 0)
      {
      logman("%s:normal exit,new_fnum=%d", mname, fnum);
      *new_fnum = fnum;
      return(DBENG_OK);
      }

   if ((ret = dbeng_nfields(ot, &nfields)) != DBENG_OK)
      if (ret == DBENG_NO_RECORD)
         nfields = 0;
      else
         {
         dbeng_io_code_log(mname, "bad rc from dbeng_nfields", ret);
         return(ret);
         }

   logman("%s:normal exit,new_fnum=%d", mname, nfields + 1);
   *new_fnum = nfields + 1;
   return(DBENG_OK);
}

static int dbeng_put_which_subfield(struct dbeng_table *ot, int fnum,
                                    int sfnum, int *new_fnum, int *new_sfnum)
{
   /* Determine which field and subfield to put data into. A positive 
      integer is assumed to be the absolute field/subfield number. A zero 
      indicates to append a new field/subfield to the record. Function 
      returns the field number loaded into 'new_fnum' and the new
      subfield number loaded into 'new_sfnum' upon success. Function
      returns a 'dbeng' code. */

   char mname[] = "dbeng_put_which_subfield";
   int nsubfields, ret;

   logman("%s:enter:fnum=%d,sfnum=%d", mname, fnum, sfnum);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_fnum == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[new_fnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_sfnum == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[new_sfnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *new_fnum = *new_sfnum = 0;

   if (sfnum < 0)
      {
      dbeng_io_code_log(mname, "out of range[sfnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_put_which_field(ot, fnum, new_fnum)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_put_which_field", ret);
      return(ret);
      }

   /* if the field number was zero, set subfield number and exit */

   if (!fnum)
      {
      if (!sfnum)
         *new_sfnum = 1;
      else
         *new_sfnum = sfnum;

      logman("%s:exit[0]:new_fnum=%d,new_sfnum=%d", mname,
                    *new_fnum, *new_sfnum);
      return(DBENG_OK);
      }

   /* if the field number was not zero and subfield was not zero,
      set subfield and exit */

   if (sfnum)
      {
      *new_sfnum = sfnum;
      logman("%s:exit[0]:new_fnum=%d,new_sfnum=%d", mname,
                    *new_fnum, *new_sfnum);
      return(DBENG_OK);
      }
 
   /* field number was not zero and subfield was zero */

   ret = dbeng_nsubfields(ot, *new_fnum, &nsubfields);

   switch(ret)
      {
      case DBENG_OK:
         *new_sfnum = nsubfields + 1;
         break;

      case DBENG_NO_SUCH_FIELD:
      case DBENG_NO_RECORD:
         *new_sfnum = 1;
         ret = DBENG_OK;
         break;

      default:
         dbeng_io_code_log(mname, "bad rc from dbeng_nsubfields", ret);
         *new_fnum = *new_sfnum = 0;
      };


   logman("%s:exit[0]:new_fnum=%d,new_sfnum=%d", mname,
                 *new_fnum, *new_sfnum);
   return(ret);
}

static int dbeng_put_which_subsubfield(struct dbeng_table *ot, int fnum,
                                       int sfnum, int ssfnum, int *new_fnum,
                                       int *new_sfnum, int *new_ssfnum)
{
   /* Determine which field, subfield and subsubfield to put data into. 
      A positive integer is assumed to be the absolute 
      field/subfield/subsubfield number. A zero indicates to append a new 
      field/subfield/subsubfield to the record. Function returns the field 
      number loaded into 'new_fnum', the new subfield number loaded into 
      'new_sfnum' and the new subsubfield number loaded into 'new_ssfnum'
      upon success. Function returns a 'dbeng' code. */

   char mname[] = "dbeng_put_which_subsubfield";
   int nsubsubfields, ret;

   logman("%s:enter:fnum=%d,sfnum=%d,ssfnum=%d", mname, fnum, sfnum,
                 ssfnum);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_fnum == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[new_fnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_sfnum == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[new_sfnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (new_ssfnum == (int *)NULL)
      {
      dbeng_io_code_log(mname, "null[new_ssfnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   *new_fnum = *new_sfnum = *new_ssfnum = 0;

   if (ssfnum < 0)
      {
      dbeng_io_code_log(mname, "out of range[ssfnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if ((ret = dbeng_put_which_subfield(ot, fnum, sfnum, new_fnum, new_sfnum)) 
       != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_put_which_subfield", ret);
      return(ret);
      }

   /* subsubfield number was not zero */

   if (ssfnum)
      {
      *new_ssfnum = ssfnum;
      logman("%s:exit[0]:new_fnum=%d,new_sfnum=%d,new_ssfnum=%d", mname,
                    *new_fnum, *new_sfnum, *new_ssfnum);
      return(DBENG_OK);
      }

   /* subsubfield number zero */

   ret = dbeng_nsubsubfields(ot, *new_fnum, *new_sfnum, &nsubsubfields);

   switch(ret)
      {
      case DBENG_OK:
         *new_ssfnum = nsubsubfields + 1;
         break;

      case DBENG_NO_SUCH_SUBFIELD:
      case DBENG_NO_SUCH_FIELD:
      case DBENG_NO_RECORD:
         *new_ssfnum = 1;
         ret = DBENG_OK;
         break;

      default:
         dbeng_io_code_log(mname, "bad rc from dbeng_nsubsubfields", ret);
         *new_fnum = *new_sfnum = *new_ssfnum = 0;
      };


   logman("%s:exit[0]:new_fnum=%d,new_sfnum=%d,new_ssfnum=%d", mname,
                 *new_fnum, *new_sfnum, *new_ssfnum);
   return(ret);
}

static int dbeng_ll_copy_table(struct dbeng_table *src,
                               struct dbeng_table *dest)
{
   /* Copy a table from source ('src') to destination ('dest').
      Both tables are assumed to be open. No regard is given to
      existing contents of destination table. Records are copied
      in accordance with the current value of the source table
      'process_deleted' flag. Function exits with the tables
      remaining open. Function returns a 'dbeng' code. */

#ifdef MULTIUSER
   struct dbeng_table *rov;
   int pos_changed;
#endif
   char mname[] = "dbeng_ll_copy_table";
   int ret, done;

   logman("%s:enter", mname);

   if (src == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[src]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (dest == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[dest]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   logman("%s:src:tid=%d,name=%s:dest:tid=%d,name=%s", mname,
                 src->tid, src->name, dest->tid, dest->name);

   /* goto top of source table */

   if ((ret = dbeng_goto_top(src)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error going to top", ret);
      return(ret);
      }

   done = FALSE;

   /* get first source record */

   ret = dbeng_get_recd(src);

   if (ret != DBENG_OK)
      if (ret == DBENG_EOF)
         {
         done = TRUE;
         dbeng_io_code_log(mname, "source table empty", ret);
         }
      else
         {
         dbeng_io_code_log(mname, "error getting first sourc rec", ret);
         return(ret);
         }

   /* read/write loop */

   while(!done)
      {
      /* reconcile any other tid pointing to this record
         and fix-up any incorrect file positions */
#ifdef MULTIUSER
      rov = dbeng_head;
      pos_changed = FALSE;

      while(rov != DBENG_OT_NULL)
         {
         /* if the tid's are different and the table file names are the same */

         if (src->tid != rov->tid && !strcmp(src->name, rov->name))
            {
            /* if the start record position is the same */

            if (src->orig_position == rov->orig_position)
               {
               logman("%s:orig_position fix-up:itid=%d,ltid=%d"
                             ",pos=%ld", mname, src->tid, rov->tid,
                             src->orig_position);

               /* change link list start record position to match current
                  output position */

               rov->orig_position = dest->current_position;
               rov->current_position = -1L;
               pos_changed = TRUE;
               }
            }

         rov = rov->next;
         }
#endif
      // write record 

      ret = dbeng_write_string_recd(dest, src->rec);

      if (ret != DBENG_OK)
         {
         dbeng_io_code_log(mname, "error writing record", ret);
         return(ret);
         }

#ifdef MULTIUSER
      /* fix-up link list current file positions, if required */
      if (pos_changed)
         {
         rov = dbeng_head;

         while(rov != DBENG_OT_NULL)
            {
            /* if the start record position has been modified,
               fix-up the current/end of record position */

            if (rov->current_position == -1L)
               {
               logman("%s:current_position fix-up:itid=%d"
                             ",ltid=%d,lopos=%ld,lcpos=%ld", mname, src->tid,
                             rov->tid, rov->orig_position,
                             dest->current_position);

               /* change link list current position to match current
                  output position */

               rov->current_position = dest->current_position;
               }

            rov = rov->next;
            }
         }
#endif

      /* get next source record */

      ret = dbeng_get_recd(src);

      switch(ret)
         {
         case DBENG_EOF:
            done = TRUE;
            break;

         case DBENG_OK:
            done = FALSE;
            break;

         /* we should never get here, just in case */

         default:
            dbeng_io_code_log(mname, "error getting record", ret);
            return(ret);
         };
      }

   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_delete_field(struct dbeng_table *ot, int fnum)
{
   /* Completely remove the 'fnum' field from the current record.
      Note that all field numbers past the field being deleted
      will change. Function returns a 'dbeng' code. */

   char mname[] = "dbeng_delete_field", *new_rec;
   int num_fields;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (fnum <= 0)
      {
      dbeng_io_code_log(mname, "out of range[fnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
   
   if (dbeng_is_active_rec(ot) == DBENG_NO_RECORD)
      {
      dbeng_io_code_log(mname, "no current record", DBENG_NO_RECORD);
      return(DBENG_NO_RECORD);
      }

   num_fields = ll_words(ot->rec, DBENG_FM);

   if (fnum > num_fields)
      {
      dbeng_io_code_log(mname, "not exist[fnum field]", DBENG_NO_SUCH_FIELD);
      return(DBENG_NO_SUCH_FIELD);
      }

   if ((new_rec = initstring(ot->rec)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[new_rec]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }
   
   new_rec[0] = EOS;

   if (!ll_worddel(ot->rec, new_rec, fnum, DBENG_FM))
      {
      dbeng_io_code_log(mname, "bad rc[FALSE] from ll_worddel",
                        DBENG_INTERNAL_ERROR);
      free(new_rec);
      return(DBENG_INTERNAL_ERROR);
      }
   
   free(ot->rec);

   if ((ot->rec = initstring(new_rec)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[ot->rec]", DBENG_MEMORY_FAIL);
      free(new_rec);
      return(DBENG_MEMORY_FAIL);
      }

   free(new_rec);
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_delete_subfield(struct dbeng_table *ot, int fnum, int sfnum)
{
   /* Completely remove a subfield from the current record.
      Function returns a 'dbeng' code. */

   char mname[] = "dbeng_delete_subfield", *tfield, *nfield;
   int ret, num_fields, fsize, num_subfields;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (fnum <= 0)
      {
      dbeng_io_code_log(mname, "out of range[fnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
   
   if (sfnum <= 0)
      {
      dbeng_io_code_log(mname, "out of range[sfnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (dbeng_is_active_rec(ot) == DBENG_NO_RECORD)
      {
      dbeng_io_code_log(mname, "no current record", DBENG_NO_RECORD);
      return(DBENG_NO_RECORD);
      }

   num_fields = ll_words(ot->rec, DBENG_FM);

   if (fnum > num_fields)
      {
      dbeng_io_code_log(mname, "not exist[fnum field]", DBENG_NO_SUCH_FIELD);
      return(DBENG_NO_SUCH_FIELD);
      }

   // obtain the field where the subfield is contained

   if ((ret = dbeng_field_size(ot, fnum, &fsize)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_field_size", ret);
      return(ret);
      }

   if ((tfield = (char *)malloc(fsize + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[tfield]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }
   
   if (!ll_word(ot->rec, tfield, fnum, DBENG_FM))
      {
      dbeng_io_code_log(mname, "bad rc[FALSE] from ll_word",
                        DBENG_INTERNAL_ERROR);
      free(tfield);
      return(DBENG_INTERNAL_ERROR);
      }

   num_subfields = ll_words(tfield, DBENG_SFM);

   if (sfnum > num_subfields)
      {
      dbeng_io_code_log(mname, "not exist[sfnum subfield]", 
                        DBENG_NO_SUCH_SUBFIELD);
      free(tfield);
      return(DBENG_NO_SUCH_SUBFIELD);
      }

   if ((nfield = initstring(tfield)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[nfield]", DBENG_MEMORY_FAIL);
      free(tfield);
      return(DBENG_MEMORY_FAIL);
      }

   nfield[0] = EOS;

   if (!ll_worddel(tfield, nfield, sfnum, DBENG_SFM))
      {
      dbeng_io_code_log(mname, "bad rc[FALSE] from ll_worddel",
                        DBENG_INTERNAL_ERROR);
      free(tfield);
      free(nfield);
      return(DBENG_INTERNAL_ERROR);
      }

   free(tfield);
   
   // put new field back into record

   if ((ret = dbeng_put_field(ot, nfield, fnum)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_put_field", ret);
      free(nfield);
      return(ret);
      }

   free(nfield);
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_delete_subsubfield(struct dbeng_table *ot, int fnum, int sfnum,
                             int ssfnum)
{
   /* Completely remove a subsubfield from the current record.
      Function returns a 'dbeng' code. */

   char mname[] = "dbeng_delete_subsubfield", *tsubfield, *nsubfield;
   int ret, num_subfields, sfsize, num_subsubfields, num_fields;

   logman("%s:enter", mname);

   if (ot == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "null[ot]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (fnum <= 0)
      {
      dbeng_io_code_log(mname, "out of range[fnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
   
   if (sfnum <= 0)
      {
      dbeng_io_code_log(mname, "out of range[sfnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (ssfnum <= 0)
      {
      dbeng_io_code_log(mname, "out of range[ssfnum]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (dbeng_is_active_rec(ot) == DBENG_NO_RECORD)
      {
      dbeng_io_code_log(mname, "no current record", DBENG_NO_RECORD);
      return(DBENG_NO_RECORD);
      }

   num_fields = ll_words(ot->rec, DBENG_FM);

   if (fnum > num_fields)
      {
      dbeng_io_code_log(mname, "not exist[fnum field]", DBENG_NO_SUCH_FIELD);
      return(DBENG_NO_SUCH_FIELD);
      }

   if ((ret = dbeng_nsubfields(ot, fnum, &num_subfields)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_nsubfields", ret);
      return(ret);
      }

   if (sfnum > num_subfields)
      {
      dbeng_io_code_log(mname, "not exist[sfnum subfield]", 
                        DBENG_NO_SUCH_SUBFIELD);
      return(DBENG_NO_SUCH_SUBFIELD);
      }

   // obtain the subfield where the subsubfield is contained

   if ((ret = dbeng_subfield_size(ot, fnum, sfnum, &sfsize)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_subfield_size", ret);
      return(ret);
      }

   if ((tsubfield = (char *)malloc(sfsize + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[tsubfield]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }
   
   if ((ret = dbeng_get_subfield(ot, tsubfield, fnum, sfnum)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_get_subfield", ret);
      free(tsubfield);
      return(ret);
      }

   num_subsubfields = ll_words(tsubfield, DBENG_SSFM);

   if (ssfnum > num_subsubfields)
      {
      dbeng_io_code_log(mname, "not exist[ssfnum subsubfield]", 
                        DBENG_NO_SUCH_SUBSUBFIELD);
      free(tsubfield);
      return(DBENG_NO_SUCH_SUBSUBFIELD);
      }

   if ((nsubfield = initstring(tsubfield)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[nsubfield]", DBENG_MEMORY_FAIL);
      free(tsubfield);
      return(DBENG_MEMORY_FAIL);
      }

   if (!ll_worddel(tsubfield, nsubfield, ssfnum, DBENG_SSFM))
      {
      dbeng_io_code_log(mname, "bad rc[FALSE] from ll_worddel",
                        DBENG_INTERNAL_ERROR);
      free(tsubfield);
      free(nsubfield);
      return(DBENG_INTERNAL_ERROR);
      }

   free(tsubfield);
   
   // put new subfield back into record

   if ((ret = dbeng_put_subfield(ot, nsubfield, fnum, sfnum)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_put_subfield", ret);
      free(nsubfield);
      return(ret);
      }

   free(nsubfield);
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

int dbeng_set_record_count(struct dbeng_table *ot)
{
   /* Check all open tables and adjust the active and deleted
      record count to the current table counts. Function
      returns a 'dbeng' code. */

   struct dbeng_table *rov = dbeng_head;

   // logman("%s:enter:tid=%d", mname, ot->tid);

   while(rov != DBENG_OT_NULL)
      {
      if (ot != rov)
         {
         if (!strcmp(ot->name, rov->name))
            {
            rov->active_record_count = ot->active_record_count;
            rov->deleted_record_count = ot->deleted_record_count;
            }
         }

      rov = rov->next;
      }

   // logman("%s:exit[%d]", mname, DBENG_OK);
   return(DBENG_OK);
}

void dbeng_terminate(void)
{
   /* Close all tables and de-allocate
      all memory in use. */

   struct dbeng_table *t_cur;
   char mname[50];
   int done = FALSE;

   strcpy(mname, "dbeng_terminate");
   logman("%s:enter", mname);

   while(!done)
      {
      t_cur = dbeng_head;

      if (t_cur == DBENG_OT_NULL)
         done = TRUE;
      else
         (void)dbeng_close_table(t_cur);
      }

   logman("%s:normal exit", mname);
}

int dbeng_is_active_rec(struct dbeng_table *ot)
{
   /* Check for active table record. Function returns
      'DBENG_NO_RECORD' if no active table record is
      present, 'DBENG_OK' otherwise. */

   if (ot == DBENG_OT_NULL)
      return(DBENG_NO_RECORD);

   if (ot->rec == (char *)NULL)
      return(DBENG_NO_RECORD);

   if (!strlen(ot->rec))
      return(DBENG_NO_RECORD);

   return(DBENG_OK);
}

int dbeng_initialize(void)
{
   /* Initialize for database use. Must be called at the beginning
      of each session. */

   dbeng_table_count = 0;
   dbeng_head = DBENG_OT_NULL;

   /* load configuration details */

   return(dbeng_config_read(DBENG_CONFIG_FILE));
}

int dbeng_get_tid(void)
{
   /* Obtain the next table ID. The next tid is based on the
      number of tables currently in use. */

   int tid, done = FALSE;

   tid = dbeng_table_count;

   while(!done)
      {
      tid++;

      /* check the tid against all currently open tables */

      if (dbeng_atid_lookup(tid) == DBENG_OT_NULL)
         done = TRUE;
      }

   return(tid);
}

static int dbeng_table_in_use(char *fname)
{
   /* Determine whether a table is in use by its physical file name.
      Function returns 'TRUE' if the table is in use, 'FALSE' otherwise. */

   struct dbeng_table *rov = dbeng_head;
   char mname[] = "dbeng_table_in_use";

   logman("%s:enter:fname=%s", fname);

   while(rov != DBENG_OT_NULL)
      {
      if (!strcmp(rov->name, fname))
         {
         logman("%s:normal exit[1]:found entry:table is in use", mname);
         return(TRUE);
         }

      rov = rov->next;
      }

   logman("%s:normal exit[0]:table not in use", mname);
   return(FALSE);
}

static int dbeng_compare_field(char *field_data, char *find_str, int cs_flag,
                               int *result)
{
   /* Compare the field data to the find string according to the
      case sensitivity flag (1=insensitive,0=sensitive). The
      field data and/or the find string may be empty (but not
      NULL). Function returns a 'dbeng' code with the result
      of the comparison (0=equal) in 'result'. */

   char mname[] = "dbeng_compare_field";
   int len_fd, len_fs;

   if (field_data == (char *)NULL)
      {
      dbeng_io_code_log(mname, "null[field_data]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (find_str == (char *)NULL)
      {
      dbeng_io_code_log(mname, "null[find_str]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   len_fd = strlen(field_data);
   len_fs = strlen(find_str);
   *result = -1;

   /* if lengths are not the same, cannot possibly be the same */

   if (len_fd != len_fs)
      {
      *result = -1;
      return(DBENG_OK);
      }

   /* if the field data is empty and the find string is empty,
      comparison is equal */

   if (!len_fd && !len_fs)
      {
      *result = 0;
      return(DBENG_OK);
      }

   if (cs_flag)
      *result = stricmp(field_data, find_str);
   else
      *result = strcmp(field_data, find_str);

   return(DBENG_OK);
}

struct dbeng_table *dbeng_atid_lookup(int tid)
{
   /* Take a table tid and perform a lookup.
      Function returns a pointer to the found table
      upon success, a 'NULL' pointer otherwise. */

   struct dbeng_table *ot;
   int done = FALSE;
   int found = FALSE;

   if (dbeng_head == DBENG_OT_NULL)
      return((struct dbeng_table *)NULL);

   if (tid <= 0)
      return((struct dbeng_table *)NULL);

   ot = dbeng_head;

   while(!done)
      {
      if (ot == DBENG_OT_NULL)
         done = TRUE;
      else
         {
         if (ot->tid == tid)
            {
            done = TRUE;
            found = TRUE;
            }
         }

      if (!done && !found)
         ot = ot->next;
      }

   if (!found)
      ot = (struct dbeng_table *)NULL;

   return(ot);
}

void dbeng_ferror(char *mess)
{
   /* Output a message via the log manager.
      The message will always be logged and the existing status
      of logging will be preserved. */

   int islog;
   char *apname, *err_log;

   islog = logman_is_active();

   if (!islog)
      {
      if ((apname = (char *)malloc(DBENG_PATH_LIMIT)) == (char *)NULL)
         return;

#ifndef OS_DOS
      if (!appinit_get_name(apname))
         {
         free(apname);
         return;
         }
#else
      apname[0] = EOS;
#endif
         
      if ((err_log = (char *)malloc(DBENG_PATH_LIMIT)) == (char *)NULL)
         {
         free(apname);
         return;
         }

      if (dbeng_config_get_log(err_log) != DBENG_OK)
         {
         free(apname);
         free(err_log);
         return;
         }

      if (logman_start(err_log, apname))
         {
         free(apname);
         free(err_log);
         return;
         }

      free(apname);
      free(err_log);
      }

   logman_nf(mess);

   if (!islog)
      logman_end();
}
