/* Bbuuzzb low level system catalog management module.
   Rick Smereka, Copyright (C) 2001-2004.

   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 QNX version. Nov/2001, Rick Smereka

   Ported to Red Hat Linux, 32-bit Windows and DOS.
   Dec/2001, Rick Smereka

   Changed function 'dbeng_catalog_open' to open the catalog using
   'dbeng_open_systable'. Added a parameter to 'dbeng_catalog_new_table'
   consisting of a 'systable' flag. Feb/2002, Rick Smereka

   Changed function 'dbeng_catalog_new_table' to create the empty table
   and also create the catalog entry (if requested). Mar/2002,
   Rick Smereka

   Added function 'dbeng_catalog_list'. Aug/2002, Rick Smereka

   Ported to Debian Linux. Nov/2002, Rick Smereka

   Added functions 'dbeng_catalog_init_table_details',
   'dbeng_catalog_term_table_details' and 'dbeng_catalog_table_details'.
   Made function 'dbeng_catalog_rep_list' private.
   Changed all logging calls from 'sys_log' to 'logman'.
   Mar/2004, Rick Smereka */

#include "stdhead.h"
#include "dbeng.h"
#include "dbmess.h"
#include "dbengcfg.h"
#include "dbengcat.h"
#include "dbiocode.h"

/* global catalog table pointer */

struct dbeng_table *cat = DBENG_OT_NULL;

/* private functions */

static int dbeng_catalog_rep_list(char *);
static int dbeng_catalog_tfind(char *);
static int dbeng_catalog_ffind(char *);
static int dbeng_catalog_open(void);
static int dbeng_catalog_close(void);

int dbeng_catalog_new(char *tname, char *fname)
{
   /* Create a new entry in the system catalog. The table name (logical)
      is expected in 'tname' and the physical file name is
      expected in 'fname'. The table name must be unique. Function returns
      'DBENG_OK' if the catalog entry was successfully added,
      an engine i/o code otherwise. */
      
   char mname[] = "dbeng_catalog_new";
   int ret;
   
   logman("%s:enter", mname);
   
   if (tname == (char *)NULL || !strlen(tname))
      {
      dbeng_io_code_log(mname, "exit:null or empty[tname]", 
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
    
   if (fname == (char *)NULL || !strlen(fname))
      {
      dbeng_io_code_log(mname, "exit:null or empty[fname]", 
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* physical file must exist, not */
   
   /* if (!exist(fname))
      {
      dbeng_io_code_log(mname, "exit:file not found", DBENG_NO_SUCH_FILE);
      return(DBENG_NO_SUCH_FILE);
      } */
      
   /* open catalog */
   
   if ((ret = dbeng_catalog_open()) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_catalog_open", ret);
      return(ret);
      }
      
   /* attempt to locate an existing catalog entry with the same table name */

   if (dbeng_catalog_tfind(tname) == DBENG_OK)   
      {
      dbeng_io_code_log(mname, "exit:catalog rec already exists",
                        DBENG_CATALOG_ENTRY_EXISTS);
      (void)dbeng_catalog_close();
      return(DBENG_CATALOG_ENTRY_EXISTS);
      }
   
   /* add new catalog record */
   
   if ((ret = dbeng_new(cat)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_new", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }
      
   if ((ret = dbeng_put_field(cat, tname, DBENG_CATALOG_FIELD_TNAME)) 
       != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_put_field[tname]", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }
   
   if ((ret = dbeng_put_field(cat, fname, DBENG_CATALOG_FIELD_FNAME)) 
       != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_put_field[fname]", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }
    
   if ((ret = dbeng_write_recd(cat)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_write_recd", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }
      
   /* close catalog */
   
   if ((ret = dbeng_catalog_close()) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_catalog_close", ret);
      return(ret);
      }

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

int dbeng_catalog_new_table(char *iname, char *oname, int systable_flag)
{
   /* Translate new table name(s) to the proper output
      name. 'iname' may contain a single name or two
      names comma delimited. A single name is assumed
      to be the table physical file name. Two names are
      assumed to be the table logical name followed by
      the physical name. Upon success, the translated
      name is placed into 'oname' which must be already
      allocated to sufficient size. Function returns a
      'dbeng' code. */

   char mname[] = "dbeng_catalog_new_table", *pname, *lname;
   int nwords, catalog_flag, ret;

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

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

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

   oname[0] = EOS;
   (void)dbeng_config_get_catalog_flag(&catalog_flag);
   nwords = ll_words(iname, ',');

   if (nwords == 1)
      {
      /* only physical file name given */

      if (systable_flag)
         /* if its a system table, use the same name */

         strcpy(oname, iname);

      if (!systable_flag)
         if (catalog_flag)
            /* if catalog is on, prefix the name with the file indicator */

            sprintf(oname, "%c%s", DBENG_CATALOG_FNAME_IND, iname); 
         else
            /* if catalog is not active, use same name */

            strcpy(oname, iname);

      if (!zcreate(iname))
         {
         dbeng_io_code_log(mname, "exit:cannot create table",
                           DBENG_CANNOT_CREATE_TABLE);
         ret = DBENG_CANNOT_CREATE_TABLE;
         }
      else
         {
         if (systable_flag)
            dbeng_io_code_log(mname, "exit:created systen table", DBENG_OK);
         else
            dbeng_io_code_log(mname, "exit:created table", DBENG_OK);

         ret = DBENG_OK;
         }

      return(ret);
      }

   /* both logical and physical file names given, separate the names */

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

   if ((lname = initstring(iname)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[lname]", DBENG_MEMORY_FAIL);
      free(pname);
      return(DBENG_MEMORY_FAIL);
      }

   pname[0] = EOS;
   lname[0] = EOS;
         
   if (!ll_word(iname, lname, 1, ','))
      {
      dbeng_io_code_log(mname, "bad rc[FALSE] from ll_word[lname]",
                        DBENG_INTERNAL_ERROR);
      free(lname);
      free(pname);
      return(DBENG_INTERNAL_ERROR);
      }

   if (!ll_word(iname, pname, 2, ','))
      {
      dbeng_io_code_log(mname, "bad rc[FALSE] from ll_word[pname]",
                        DBENG_INTERNAL_ERROR);
      free(lname);
      free(pname);
      return(DBENG_INTERNAL_ERROR);
      }

   /* attempt to create the new table file using the physical file name */

   if (!zcreate(pname))
      {
      dbeng_io_code_log(mname, "exit:error creating empty table",
                        DBENG_CANNOT_CREATE_TABLE);
      free(lname);
      free(pname);
      return(DBENG_CANNOT_CREATE_TABLE);
      }

   /* attempt to create the catalog entry */

   if ((ret = dbeng_catalog_new(lname, pname)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_catalog_new", ret);
      free(lname);
      unlink(pname);
      free(pname);
      return(ret);
      }
 
   if (catalog_flag)
      /* output logical name (into 'oname') if catalog is on */

      strcpy(oname, lname);
   else
      /* output physical file name (no indicator) if catalog is off */

      strcpy(oname, pname);

   free(lname);
   free(pname);
   logman("%s:normal exit[0],oname=%s,l=%d", mname, oname, 
                 strlen(oname));
   return(DBENG_OK);
}

int dbeng_catalog_delete(char *tname)
{
   /* Delete the catalog entry with the table name 'tname'.
      Function returns 'DBENG_OK' upon success, an engine
      i/o code otherwise. */
      
   char mname[] = "dbeng_catalog_delete";
   int ret;
   
   logman("%s:enter", mname);
   
   if (tname == (char *)NULL || !strlen(tname))
      {
      dbeng_io_code_log(mname, "exit:[tname]null or empty",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
      
   /* open catalog */
   
   if ((ret = dbeng_catalog_open()) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_catalog_open", ret);
      return(ret);
      }

   /* find table name in catalog */
   
   if ((ret = dbeng_catalog_tfind(tname)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:catalog entry not found", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }
      
   if ((ret = dbeng_delete_recd(cat)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_delete_recd", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }
       
   if ((ret = dbeng_catalog_close()) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_catalog_close", ret);
      return(ret);
      }

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

int dbeng_catalog_init_table_details(char *tname, int systable_flag,
                                     struct dbengcat_details **det)
{
   // Prepare the catalog details structure for use.

   struct dbengcat_details *ot;
   char mname[] = "dbeng_catalog_init_table_details";
   int size;

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

   if (det == (struct dbengcat_details **)NULL)
      {
      dbeng_io_code_log(mname, "exit:null[**det]",
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
      
   size = sizeof(struct dbengcat_details);

   // allocate structure

   if ((ot = (struct dbengcat_details *)malloc(size)) ==
       (struct dbengcat_details *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[ot]", DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }

   if ((ot->logical_name = (char *)malloc(strlen(tname) + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[ot->logical_name]", 
                        DBENG_MEMORY_FAIL);
      free(ot);
      return(DBENG_MEMORY_FAIL);
      }
      
   strcpy(ot->logical_name, tname);
   ot->systable_flag = systable_flag;
   ot->physical_name = (char *)NULL;
   ot->apack = 0;
#ifdef MULTIUSER
   ot->rep_list = (char *)NULL;
#endif
   *det = ot;
   return(DBENG_OK);
}

int dbeng_catalog_term_table_details(struct dbengcat_details **det)
{
   // De-allocate the entire catalog details structure.

   struct dbengcat_details *ot;
   char mname[] = "dbeng_catalog_term_table_details";
   int ret;

   if (det == (struct dbengcat_details **)NULL)
      {
      dbeng_io_code_log(mname, "null[**det]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (*det == (struct dbengcat_details *)NULL)
      {
      dbeng_io_code_log(mname, "null[*det]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   ot = *det;
   free(ot->logical_name);

   if (ot->physical_name != (char *)NULL)
      free(ot->physical_name);

#ifdef MULTIUSER
   if (ot->rep_list != (char *)NULL)
      free(ot->rep_list);
#endif

   free(*det);
   *det = (struct dbengcat_details *)NULL;
   return(DBENG_OK);
}

int dbeng_catalog_table_details(struct dbengcat_details *det)
{
   /* Find a catalog entry by its logical table name and return all
      catalog details. Function returns a 'dbeng' code. */

   char mname[] = "dbeng_catalog_table_details", tmp[25];
   int ret, catalog_flag, len;

   if (det == (struct dbengcat_details *)NULL)
      {
      dbeng_io_code_log(mname, "null or empty[det]", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

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

   logman("%s:enter", mname);
   (void)dbeng_config_get_catalog_flag(&catalog_flag);

   /* first, resolve supplied table name (could be logical or physical)
      to the physical OS path/file name */

   // if its a system table, copy name

   if (det->systable_flag)
      {
      if ((det->physical_name = (char *)malloc(strlen(det->logical_name) + 1))
          == (char *)NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[det->physical_name][1]",
                           DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      strcpy(det->physical_name, det->logical_name);
      dbeng_io_code_log(mname, "found system table", DBENG_OK);
      return(DBENG_OK);
      }

   // if the catalog flag is not on, copy name
   
   if (!catalog_flag)
      {
      if ((det->physical_name = (char *)malloc(strlen(det->logical_name) + 1))
          == (char *)NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[det->physical_name][2]",
                           DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      strcpy(det->physical_name, det->logical_name);
      dbeng_io_code_log(mname, "catalog is not on", DBENG_OK);
      return(DBENG_OK);
      }

   /* if the logical name starts with the file name indicator, copy
      the same name minus the indicator */

   if (det->logical_name[0] == DBENG_CATALOG_FNAME_IND)
      {
      if ((det->physical_name = (char *)malloc(strlen(det->logical_name) + 1))
          == (char *)NULL)
         {
         dbeng_io_code_log(mname, "alloc fail[det->physical_name][3]",
                           DBENG_MEMORY_FAIL);
         return(DBENG_MEMORY_FAIL);
         }

      strcpy(det->physical_name, &det->logical_name[1]);
      dbeng_io_code_log(mname, "exit:found physical file name", DBENG_OK);
      return(DBENG_OK);
      }

   /* only other possibility is a catalog table name which we
      look-up and get the physical file name */

   if (cat != DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "exit:catalog already open", 
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   /* open catalog */
   
   if ((ret = dbeng_catalog_open()) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_catalog_open", ret);
      return(ret);
      }

   // find catalog entry by its logical name

   if ((ret = dbeng_catalog_tfind(det->logical_name)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_catalog_tfind", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }

   // get size of physical name

   if ((ret = dbeng_field_size(cat, DBENG_CATALOG_FIELD_FNAME, &len)) !=
       DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_field_size", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }

   if ((det->physical_name = (char *)malloc(len + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[det->physical_name][4]",
                        DBENG_MEMORY_FAIL);
      (void)dbeng_catalog_close();
      return(DBENG_MEMORY_FAIL);
      }

   // load physical file name
   
   if ((ret = dbeng_get_field(cat, det->physical_name, 
       DBENG_CATALOG_FIELD_FNAME)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_get_field", ret);
      free(det->physical_name);
      det->physical_name = (char *)NULL;
      (void)dbeng_catalog_close();
      return(ret);
      }

   // load autopack value (ok if not present)

   if ((ret = dbeng_get_field(cat, tmp, DBENG_CATALOG_FIELD_AUTOPACK)) !=
       DBENG_OK)
      if (ret == DBENG_NO_SUCH_FIELD)
         {
         det->apack = 0;
         ret = DBENG_OK;
         }
      else
         {
         dbeng_io_code_log(mname, "exit:bad rc from dbeng_get_field", ret);
         (void)dbeng_catalog_close();
         return(ret);
         }
   else
      {
      if (!qatoi(tmp, &len))
         {
         dbeng_io_code_log(mname, "exit:autopack value not a number",
                           DBENG_INVALID_FUNCTION);
         (void)dbeng_catalog_close();
         return(DBENG_INVALID_FUNCTION);
         }

      det->apack = len;
      }

   // load replication list if present (database server only)

#ifdef MULTIUSER
   if ((det->rep_list = (char *)malloc(DBENG_MAXRECFIELD)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[det->rep_list]", 
                        DBENG_MEMORY_FAIL);
      (void)dbeng_catalog_close();
      return(DBENG_MEMORY_FAIL);
      }

   if ((ret = dbeng_catalog_rep_list(det->rep_list)) != DBENG_OK)
      {
      // ok if no replication entry

      if (ret == DBENG_NO_REPLICATION_ENTRY)
         {
         dbeng_io_code_log(mname, "normal exit:no rep entry", DBENG_OK);
         (void)dbeng_catalog_close();
         free(det->rep_list);
         det->rep_list = (char *)NULL;
         return(DBENG_OK);
         }
 
      dbeng_io_code_log(mname, "exit:bad rc from dbeng_catalog_rep_list", ret);
      (void)dbeng_catalog_close();
      free(det->rep_list);
      det->rep_list = (char *)NULL;
      return(ret);
      }
#endif

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

int dbeng_catalog_fname(char *tname, char *fname)
{
   /* Given a table name ('tname'), locate and return the physical
      name ('fname') from the system catalog. Upon success, the
      physical file name will be loaded into 'fname' which must
      already be allocated to sufficient size. Note that this
      function assumes the catalog is currently closed. Function
      returns 'DBENG_OK' upon success, an engine i/o code
      otherwise. */
      
   char mname[] = "dbeng_catalog_fname";
   int ret;
   
   logman("%s:enter", mname);

   if (tname == (char *)NULL || !strlen(tname))
      {
      dbeng_io_code_log(mname, "exit:'tname' null or empty", 
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
      
   if (cat != DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "exit:catalog already open", 
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
      
   if (fname == (char *)NULL)
      {
      dbeng_io_code_log(mname, "exit:'fname' null", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
      
   fname[0] = EOS;

   /* open catalog */
   
   if ((ret = dbeng_catalog_open()) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_catalog_open", ret);
      return(ret);
      }

   /* find table name in catalog */
   
   if ((ret = dbeng_catalog_tfind(tname)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad rc from dbeng_catalog_tfind", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }

   /* load physical file name into 'fname' */
   
   if ((ret = dbeng_get_field(cat, fname, DBENG_CATALOG_FIELD_FNAME)) !=
      DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_get_field", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }

   if ((ret = dbeng_catalog_close()) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_catalog_close", ret);
      return(ret);
      }

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

static int dbeng_catalog_rep_list(char *rep_list)
{
   /* Get and return the replication list for the table. Upon success, the
      replication list will be loaded into 'rep_list' which must
      already be allocated to sufficient size. The format of
      the replication list is:

         port,tname

      There may be multiple entries in the replication list.
      Each entry will be delimited by 'DBENG_LIST_DELIM'.
      Note that this function assumes the catalog is 
      currently open and the catalog file pointer is pointing
      at the record in question. Function returns 'DBENG_OK' upon 
      success, an engine i/o code otherwise. */
      
   char mname[] = "dbeng_catalog_rep_list";
   char *before, *after, *wdata, *tname, port_char[10];
   int ret, nentries, i, entry_size, nsubsubfields, port;
   
   logman("%s:enter", mname);

   if (rep_list == (char *)NULL)
      {
      dbeng_io_code_log(mname, "exit:'rep_list' null", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
      
   rep_list[0] = EOS;

   /* get number of entries in replication list */
   
   if ((ret = dbeng_nsubfields(cat, DBENG_CATALOG_FIELD_REPLICATE, &nentries)) 
       != DBENG_OK)
      {
      if (ret == DBENG_NO_SUCH_FIELD)
         {
         dbeng_io_code_log(mname, "no replication entry", 
                           DBENG_NO_REPLICATION_ENTRY);
         return(DBENG_NO_REPLICATION_ENTRY);
         }

      dbeng_io_code_log(mname, "bad rc from dbeng_nsubfields", ret);
      return(ret);
      }

   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);
      }

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

   /* loop through all replication entries */

   for(i = 1; i <= nentries; i++)
      {
      if ((ret = dbeng_subfield_size(cat, DBENG_CATALOG_FIELD_REPLICATE, i,
                                     &entry_size)) != DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad rc from dbeng_subfield_size", ret);
         free(before);
         free(after);
         return(ret);
         }

      if (entry_size <= 0)
         {
         logman("%s:entry[%d]:empty catalog entry,ignoring", mname, i);
         continue;
         }

      /* must have exactly two subsubfields (port and table name) */

      if ((ret = dbeng_nsubsubfields(cat, DBENG_CATALOG_FIELD_REPLICATE, i,
                                     &nsubsubfields)) != DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad rc from dbeng_nsubsubfields", ret);
         free(before);
         free(after);
         return(ret);
         }

      if (nsubsubfields != 2)
         {
         logman("%s:replication entry invalid", mname);
         continue;
         }

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

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

      if ((ret = dbeng_get_subsubfield(cat, port_char, 
                                       DBENG_CATALOG_FIELD_REPLICATE, i,
                                       DBENG_CATALOG_SSF_PORT)) != DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad rc from dbeng_get_subsubfield[port]", 
                           ret);
         free(tname);
         free(wdata);
         free(before);
         free(after);
         return(ret);
         }

      /* port number must be valid and in range */

      if (!qatoi(port_char, &port))
         {
         logman("%s:entry[%d]:non-numeric port,ignoring", mname, i);
         free(tname);
         free(wdata);
         continue;
         }

      if (port <= 0)
         {
         logman("%s:entry[%d]:out of range[port],ignoring", mname, i);
         free(tname);
         free(wdata);
         continue;
         }
       
      if ((ret = dbeng_get_subsubfield(cat, tname, 
                                       DBENG_CATALOG_FIELD_REPLICATE, i,
                                       DBENG_CATALOG_SSF_TNAME)) != DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad rc from dbeng_get_subsubfield[tname]", 
                           ret);
         free(tname);
         free(wdata);
         free(before);
         free(after);
         return(ret);
         }

      sprintf(wdata, "%d,%s", port, tname);
      free(tname);
      
      if (!ll_wordput(before, after, wdata, i, 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);
         }

      free(wdata);
      strcpy(before, after);
      }

   strcpy(rep_list, after);
   free(before);
   free(after);

   if (!strlen(rep_list))
      {
      dbeng_io_code_log(mname, "exit:list is empty", 
                        DBENG_NO_REPLICATION_ENTRY);
      return(DBENG_NO_REPLICATION_ENTRY);
      }

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

int dbeng_catalog_list(char *pat, char *list_out)
{
   /* Return a list of matching table names (logical) from
      the system catalog. 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 logical name 
      followed by the file path/name (physical name). The two
      fields in the list are delimited by a comma. 
      The parameter 'pat' may contain a pattern
      acceptable to the 'pmatch' function. If 'pat' is
      'NULL' or empty, all entries from the catalog
      will be returned. Function returns a 'dbeng' code. */

   char *before, *after, *wdata, *tname, *fname;
   char mname[] = "dbeng_catalog_list";
   long rec_cnt, dummy;
   int cnt, is_pat, ret, process;

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

   if (list_out == 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;
   list_out[0] = EOS;

   /* open catalog */

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

   /* get the number of records */

   if ((ret = dbeng_rec_count(cat, &rec_cnt, &dummy)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "bad rc from dbeng_count_rec(cat)", ret);
      (void)dbeng_catalog_close();
      return(ret);
      }

   /* it is not an error if there are no records */

   if (rec_cnt == 0L)
      {
      dbeng_io_code_log(mname, "list is empty", DBENG_OK);
      (void)dbeng_catalog_close();
      return(DBENG_OK);
      }

   if ((before = (char *)malloc(DBENG_MAXRECFIELD)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "alloc fail[before]", DBENG_MEMORY_FAIL);
      (void)dbeng_catalog_close();
      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);
      (void)dbeng_catalog_close();
      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);
      (void)dbeng_catalog_close();
      return(DBENG_MEMORY_FAIL);
      }

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

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

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

   if ((ret = dbeng_goto_top(cat)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error going top of catalog", ret);
      free(before);
      free(after);
      free(wdata);
      free(tname);
      free(fname);
      (void)dbeng_catalog_close();
      return(ret);
      }
      
   /* get first record */

   if ((ret = dbeng_get_recd(cat)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "error getting first record", ret);
      free(before);
      free(after);
      free(wdata);
      free(tname);
      free(fname);
      (void)dbeng_catalog_close();
      return(ret);
      }
      
   while(ret == DBENG_OK)
      {
      if ((ret = dbeng_get_field(cat, tname, DBENG_CATALOG_FIELD_TNAME)) !=
          DBENG_OK)
         {
         dbeng_io_code_log(mname, "bad rc from dbeng_get_field(tname)", ret);
         free(before);
         free(after);
         free(wdata);
         free(tname);
         free(fname);
         (void)dbeng_catalog_close();
         return(ret);
         }

      process = TRUE;

      /* match to pattern (if supplied) */

      if (is_pat)
         if (!pmatch(pat, tname))
            process = FALSE;

      if (process)
         {
         if ((ret = dbeng_get_field(cat, fname, DBENG_CATALOG_FIELD_FNAME)) !=
             DBENG_OK)
            {
            dbeng_io_code_log(mname, "bad rc from dbeng_get_field(fname)", ret);
            free(before);
            free(after);
            free(wdata);
            free(tname);
            free(fname);
            (void)dbeng_catalog_close();
            return(ret);
            }

         sprintf(wdata, "%s,%s", tname, fname);

         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);
            free(tname);
            free(fname);
            (void)dbeng_catalog_close();
            return(DBENG_INTERNAL_ERROR);
            }

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

      /* get next record */

      ret = dbeng_get_recd(cat);
      }

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

/* private functions */

static int dbeng_catalog_tfind(char *tname)
{
   /* Locate the table by table name ('tname') within the
      system catalog. The system catalog table is assumed
      to be already open. It is an error if the catalog
      is found to be closed. Function returns 'DBENG_OK' if
      the table name was found, an engine i/o code
      otherwise. */
      
   char mname[] = "dbeng_catalog_tfind";
   int ret;
   
   logman("%s:enter", mname);
   
   if (tname == (char *)NULL || !strlen(tname))
      {
      dbeng_io_code_log(mname, "exit:'tname' null or empty", 
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (cat == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "exit:catalog not open", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
      
   if ((ret = dbeng_goto_top(cat)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_goto_top", ret);
      return(ret);
      }
      
   if ((ret = dbeng_find_field(cat, 0, tname, DBENG_CATALOG_FIELD_TNAME))
        == DBENG_EOF)
      ret = DBENG_NO_CATALOG_ENTRY;

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

static int dbeng_catalog_ffind(char *fname)
{
   /* Locate the table by file name ('fname') within the
      system catalog. The system catalog table is assumed
      to be already open. It is an error if the catalog
      is found to be closed. Function returns 'DBENG_OK' if
      the file name was found, an engine i/o code
      otherwise. */
      
   char mname[] = "dbeng_catalog_ffind";
   int ret;
   
   logman("%s:enter", mname);
   
   if (fname == (char *)NULL || !strlen(fname))
      {
      dbeng_io_code_log(mname, "exit:'fname' null or empty", 
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }

   if (cat == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "exit:catalog not open", DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
      
   if ((ret = dbeng_goto_top(cat)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:bad ret from dbeng_goto_top", ret);
      return(ret);
      }
      
   if ((ret = dbeng_find_field(cat, 0, fname, DBENG_CATALOG_FIELD_FNAME))
        == DBENG_EOF)
      ret = DBENG_NO_CATALOG_ENTRY;

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

static int dbeng_catalog_open(void)
{
   /* Open the catalog table and load the 'cat' pointer. Function
      returns 'DBENG_OK' upon success, an engine i/o code
      otherwise. */
   
   char mname[] = "dbeng_catalog_open";
   char *cat_table;
   int ret;
   int tid;
   
   logman("%s:enter", mname);
      
   if (cat != DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "exit:catalog already open", 
                        DBENG_INVALID_FUNCTION);
      return(DBENG_INVALID_FUNCTION);
      }
   
   if ((cat_table = (char *)malloc(DBENG_PATH_LIMIT + 1)) == (char *)NULL)
      {
      dbeng_io_code_log(mname, "exit:alloc fail[cat_table]",
                        DBENG_MEMORY_FAIL);
      return(DBENG_MEMORY_FAIL);
      }
   
   /* get catalog table name */
   
   if ((ret = dbeng_config_get_catalog(cat_table)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:error getting catalog name", ret);
      free(cat_table);
      return(ret);
      }
      
   if ((ret = dbeng_open_systable(cat_table, &tid)) != DBENG_OK)
      {
      dbeng_io_code_log(mname, "exit:error opening catalog", ret);
      free(cat_table);
      return(ret);
      }
      
   free(cat_table);

   /* load table pointer from tid */
   
   if ((cat = dbeng_atid_lookup(tid)) == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "exit:dbeng_atid_lookup error on catalog table",
                        DBENG_UNABLE_TO_OPEN);
      return(DBENG_UNABLE_TO_OPEN);
      }
      
   dbeng_io_code_log(mname, "normal exit", DBENG_OK);
   return(DBENG_OK);
}

static int dbeng_catalog_close(void)
{
   /* Close the system catalog table. Function returns 'DBENG_OK'
      upon success, an engine i/o code otherwise. */
      
   char mname[] = "dbeng_catalog_close";
   int ret;
   
   logman("%s:enter", mname);
   
   if (cat == DBENG_OT_NULL)
      {
      dbeng_io_code_log(mname, "exit:catalog table not open", DBENG_NO_SUCH_FILE);
      return(DBENG_NO_SUCH_FILE);
      }
      
   ret = dbeng_close_table(cat);
   cat = DBENG_OT_NULL;
   dbeng_io_code_log(mname, "normal exit", ret);
   return(ret);
}
