#include "dbinc.h"
/*****************************************************************************
*                                                                            *
*      COMMON OCEANOGRAPHIC DATA ACCESS SYSTEM (CODAS)                       *
*                                                                            *
*      WRITTEN BY:  RAMON CABRERA, ERIC FIRING, and JULIE RANADA             *
*                   JOINT INSTITUTE FOR MARINE AND ATMOSPHERIC RESEARCH      *
*                   1000 POPE ROAD  MSB 312                                  *
*                   HONOLULU, HI 96822                                       *
*                                                                            *
*      VERSION:     3.10                                                     *
*                                                                            *
*      DATE:        APRIL 1989, AUGUST 1995                                  *
*                                                                            *
*****************************************************************************/
/*

       FILE:  dbcreate.c

              USER FUNCTIONS FOR CREATING A NEW DATABASE

*/
/*-----------------------------------------------------------------------------

   FUNCTION:  DBCREATE

      It opens a new or existing database for appending.

   PARAMETERS:
   
      db_id = pointer to database number

      db_name = pointer to database name, including path;
                replaced by name without path.

      prd_name = pointer to name of producer definition file

      memory_mode = pointer to memory mode for directories

      ierr = pointer to error code

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void DBCREATE(int *db_id,
              char *db_name, char *prd_name,
              int *memory_mode, int *ierr)
#else
void DBCREATE(db_id, db_name, prd_name, memory_mode, ierr)
int *db_id, *memory_mode, *ierr;
char *db_name, *prd_name;
#endif
{
   int i, id;
   FILE_NAME_TYPE path_name, path, db_root;          /* 80 characters each */

   if ((*ierr = alignment_check()) != 0)
      return;  /**jr++**/
   if (first_database_call)
   {
      for (i = 0;  i < MAX_OPEN_DATABASES;  i++)
         database_table[i] = NULL;
      first_database_call = 0;
   }
   id = *db_id - 1;
   if ((id < 0) || (id >= MAX_OPEN_DATABASES))
   {
      *ierr = INVALID_DATABASE_NUMBER;
      return;
   }
   if (database_table[id])
   {
      db = database_table[id];
      *ierr = DB_ALREADY_OPEN;
      return;
   }
   if ((database_table[id] = (DATABASE_TYPE *) malloc(DATABASE_SIZE)) == NULL)
   {
      db->error_code = INSUFFICIENT_MEMORY;
      db->error_data = BLOCK_DIR;
      goto error_found;
   }

   current_database = id;
   db = database_table[id];
   set_byte((UBYTE *)db, 0, DATABASE_SIZE);
   if (*memory_mode)
      db->memory_directory = 1;
   else
      db->memory_directory = 0;

   strtrim(db->producer_def_file, prd_name, sizeof(FILE_NAME_TYPE));
   tmpnam(db->temporary_data_file);
   strtrim(path_name, db_name, sizeof(FILE_NAME_TYPE));
   if (strlen(path_name) + 8 > sizeof(FILE_NAME_TYPE))
   {                         /* need 8 char for file number, ext, NUL */
      *ierr = PATHNAME_TOO_LONG;
      return;
   }
   split_path(path_name, path, db_root);
   if ((int)strlen(db_root) > MAX_DBNAME_LENGTH)
   {
      *ierr = DBNAME_TOO_LONG;
      return;
   }
   db->path_nchar = strlen(path);

   strcpy(db->block_dir_hdr.block_file_template, db_root);
   strcat(db->block_dir_hdr.block_file_template, "%03u.blk");

   strcpy(db->block_file, path);
   strcpy(db->block_dir_file, path);
   strcat(db->block_dir_file, db_root);
   strcat(db->block_dir_file, "dir.blk");

/*
  ---> ALLOCATE MEMORY FOR PRODUCER DEFINITION
*/
   if ((db->producer = (PRODUCER_DEFINITION_TYPE *)
      malloc(sizeof(PRODUCER_DEFINITION_TYPE))) == NULL)
   {
      db->error_code = INSUFFICIENT_MEMORY;
      db->error_data = PRODUCER_DEF;
      goto error_found;
   }
   db->producer_in_memory = 1;
   set_byte((UBYTE *)db->producer, '\0', sizeof(PRODUCER_DEFINITION_TYPE));
   if (load_producer_definition())
      goto error_found;
   db->access_mode = READ_WRITE;
   db->create_mode = 1;
   if (!(EXISTS(db->block_dir_file)))
      if (create_block_dir())
         goto error_found;
   if (open_block_dir())
      goto error_found;
   if (db->memory_directory)
   {
      if (allocate_memory_for_directories())
         goto error_found;
      if (db->block_dir_hdr.dir_nentries)
         if (load_block_dir())
            goto error_found;
      db->block_dir_loaded = 1;
   }
   *ierr = 0;
   return;

   error_found:
      report_db_error("DBCREATE");
      *ierr = db->error_code * 1000 - db->error_data;
      release_database();
}

/*-----------------------------------------------------------------------------

   FUNCTION:  DBNEWBLK

      It creates a new block and leaves the database ready for adding data
      to the block.

   PARAMETER: 
   
      ierr = pointer to error code

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void DBNEWBLK(int *ierr)
#else
void DBNEWBLK(ierr)
int *ierr;
#endif
{
   *ierr = 0;
   if (!(db))
   {
      *ierr = DB_IS_NOT_OPEN;
      return;
   }
   if (!(db->create_mode))
   {
      *ierr = NO_OPEN_FOR_CREATE;
      return;
   }
   if (db->new_block_is_open)
   {
      *ierr = NEW_BLOCK_IS_OPEN;
      return;
   }
   if (db->block_dir_hdr.dir_nentries >= MAX_BLOCK_DIR_SIZE)
   {
      *ierr = BLOCK_DIR_IS_FULL;
      return;
   }
   create_block_dir_entry();
   if (create_block())
      goto error_found;
   if (open_block())
      goto error_found;
   if (load_data_list())
      goto error_found;
   /*jr+ -- position block file pointer past structure definitions */
   if (fseek(db->fpblkdata, db->block_hdr.dir_ofs, 0))
   {
      db->error_code = SEEK_ERROR;
      db->error_data = BLOCK_FILE;
      goto error_found;
   }
/*
 ---> OPEN TEMPORARY FILE FOR PROFILE DATA
*/
   if (EXISTS(db->temporary_data_file))
   {
      if ((db->fptmpdata = open_binary_file(db->temporary_data_file, READ_WRITE))
         == NULL)
      {
         db->error_code = UNABLE_TO_OPEN;
         db->error_data = TEMP_DATA_FILE;
         goto error_found;
      }
   }
   else if ((db->fptmpdata = fopen(db->temporary_data_file, "w+b")) == NULL)
   {
      db->error_code = FILE_PROTECTION_ERROR;
      db->error_data = TEMP_DATA_FILE;
      goto error_found;
   }
   db->new_block_is_open = 1;
   if (db->profile_dir_in_memory)
      db->profile_dir_loaded = 1;
   db->temp_data_ofs = 0;
   return;

   error_found:
      if (db->block_is_open)
         release_block();
      report_db_error("DBNEWBLK");
      *ierr = db->error_code * 1000 - db->error_data;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  DBNEWPRF


      It creates a new profile and leaves the database ready for adding data
      to the profile.

   PARAMETERS:
   
      prf_time = pointer to structure containing profile time

      ierr = pointer to error code

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void DBNEWPRF(YMDHMS_TIME_TYPE *prf_time, int *ierr)
#else
void DBNEWPRF(prf_time, ierr)
YMDHMS_TIME_TYPE *prf_time;
int *ierr;
#endif
{
   if (invalid_time(prf_time))
   {
      *ierr = INVALID_TIME;
      return;
   }
   if ((*ierr = dbadd_flags()) != 0)
      return;
   if (db->new_profile_is_open)
   {
      *ierr = NEW_PROFILE_IS_OPEN;
      return;
   }
   if (db->block_hdr.dir_nentries >= MAX_PROFILE_DIR_SIZE)
   {
      *ierr = BLOCK_IS_FULL;
      return;
   }
   if (!(db->block_hdr.dir_nentries))
   {
      db->block_hdr.time1 = PCKTIM(prf_time);
      UPCKTIM(&(db->block_base_time), &(db->block_hdr.time1));
   }
   create_profile_dir_entry(prf_time);
   db->new_profile_is_open = 1;
   return;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  DBADD

      It adds data to a profile or block.

   PARAMETERS:
  
      type = pointer to data type

      data = address of user buffer

      nb    = pointer to number of bytes

      ierr = pointer to error code

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void DBADD(int *type, char *data, unsigned int *nb, int *ierr)
#else
void DBADD(type, data, nb, ierr)
int *type, *ierr;
char *data;
unsigned int *nb;
#endif
{
   if ((*ierr = dbadd_flags()) != 0)
      return;
/*
  ---> PROFILE DATA (types 0 - 99)
*/
   if ((*type >= 0) & (*type < (int)db->block_hdr.data_list_nentries))
   {
      if (*nb == 0)
      {
         *ierr = INVALID_BUFFER_SIZE;
         return;
      }
      if (add_profile_data(type, data, nb, ierr))
         goto error_found;
   }
   else switch (*type)
   {
      case POSITION:
         *ierr = set_position((DMSH_POSITION_TYPE *) data);
         break;
      case DATA_PROC_MASK:
         db->block_hdr.data_proc_mask = *(ULONG *) data;
         break;
      case DEPTH_RANGE:
         *ierr = set_depth_range((DEPTH_RANGE_TYPE *) data);
         break;
      default:
         *ierr = INVALID_TYPE_REQUEST;
         break;
   }
   return;

   error_found:
      report_db_error("DBADD");
      *ierr = db->error_code * 1000 - db->error_data;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  DBENDPRF

      It closes a newly created profile.

   PARAMETER:
   
      ierr = pointer to error code

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void DBENDPRF(int *ierr)
#else
void DBENDPRF(ierr)
int *ierr;
#endif
{
   unsigned int nb;
   long ofs;

   *ierr = 0;
   if (!(db))
   {
      *ierr = DB_IS_NOT_OPEN;
      return;
   }
   if (!(db->create_mode))
   {
      *ierr = NO_OPEN_FOR_CREATE;
      return;
   }
   if (!(db->new_profile_is_open))
   {
      *ierr = NO_NEW_PROFILE_IS_OPEN;
      return;
   }
   ofs = db->profile_dir_entry_ptr->ofs;
   nb = db->block_hdr.data_dir_nentries * DATA_DIR_ENTRY_SIZE;
   if ((db->error_code = write_data(db->fptmpdata, (char *) db->data_dir,
      ofs, nb)) != 0)
   {
      db->error_data = TEMP_DATA_FILE;
      goto error_found;
   }
   if (fseek(db->fptmpdata, db->temp_data_ofs, 0))
   {
      db->error_code = SEEK_ERROR;
      db->error_data = TEMP_DATA_FILE;
      goto error_found;
   }
   if (!(db->profile_dir_loaded))
   {
      nb = db->block_hdr.dir_entry_nbytes;
      if (fwrite((char *) db->profile_dir_entry_ptr, 1, nb, db->fpblkdata) != nb)
      {
         db->error_code = WRITE_ERROR;
         db->error_data = PROFILE_DIR;
         goto error_found;
      }
      db->block_hdr.block_nbytes += nb;  /* jr+ 6/18/90 */
   }
   db->block_hdr.dir_nentries++;
   db->new_profile_is_open = 0;
   return;

   error_found:
      report_db_error("DBENDPRF");
      *ierr = db->error_code * 1000 - db->error_data;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  DBENDBLK

      It closes a newly created block.

   PARAMETER:
   
      ierr = pointer to error code

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void DBENDBLK(int *ierr)
#else
void DBENDBLK(ierr)
int *ierr;
#endif
{
   int temp_dir_loaded = 1;
   unsigned int i, j, nb, m;
   long ofs1, ofs2;

   if ((*ierr = dbadd_flags()) != 0)
      return;
   if (db->new_profile_is_open)
   {
      *ierr = NEW_PROFILE_IS_OPEN;
      return;
   }
/*
 ---> IF PROFILE DIR NOT IN MEMORY LOAD IT
*/
   if (!(db->profile_dir_loaded))
   {
      nb = db->block_hdr.dir_nentries * db->block_hdr.dir_entry_nbytes;
      if (nb == 0)
      {
         db->error_code = PROFILE_DIR_IS_EMPTY;
         db->error_data = PROFILE_DIR;
         goto error_found;
      }
      if ((db->profile_dir = malloc(nb)) == NULL)
      {
         db->error_code = INSUFFICIENT_MEMORY;
         db->error_data = PROFILE_DIR;
         goto error_found;
      }
      db->profile_dir_in_memory = 1;
      temp_dir_loaded = 1;
      if (load_profile_dir())
         goto error_found;
   }
/*
 ---> UPDATE BLOCK HEADER WITH POSITION, TIME, AND DEPTH RANGE OF PROFILES
*/
   update_block();
/*
 ---> STORE CONSTANT PROFILE DATA
*/
   ofs2 = (db->block_hdr.dir_ofs)
      + db->block_hdr.dir_nentries * db->block_hdr.dir_entry_nbytes;
   for (i = 0; i < db->block_hdr.data_list_nentries; i++)
      if (db->data_list[i].access_type == BLOCK_VAR_ACCESS_CODE)
      {
         if ((nb = db->data_list[i].access_1) != 0)
         {
            ofs1 = db->data_list[i].access_0;
            if ((db->error_code = copy_data(db->fptmpdata,
               db->fpblkdata, ofs1, ofs2, nb)) != 0)
            {
               db->error_data = PROFILE_DATA;
               goto error_found;
            }
            db->data_list[i].access_0 = ofs2;
            ofs2 += nb;
            db->block_hdr.block_nbytes += nb;  /* jr+ */
         }
      }
   if (write_data_list())
      goto error_found;
/*
  ---> STORE DIR PROFILE DATA
*/
   m = db->block_hdr.data_dir_nentries * DATA_DIR_ENTRY_SIZE;
   for (i = 0; i < db->block_hdr.dir_nentries; i++)
   {
      db->profile_dir_entry_ptr = (PROFILE_DIR_3_ENTRY_TYPE *)
         (db->profile_dir + i * db->block_hdr.dir_entry_nbytes);
      ofs1 = db->profile_dir_entry_ptr->ofs;
      nb = m;
      if ((db->error_code = read_data(db->fptmpdata, (char *) db->data_dir,
         ofs1, nb)) != 0)
      {
         db->error_data = DATA_DIR;
         goto error_found;
      }
      for (j = 0;  j < db->block_hdr.data_dir_nentries;  j++)
         nb += db->data_dir[j].nbytes;
      if ((db->error_code = copy_data(db->fptmpdata, db->fpblkdata,
         ofs1, ofs2, nb)) != 0)
      {
         db->error_data = PROFILE_DATA;
         goto error_found;
      }
      db->profile_dir_entry_ptr->ofs = ofs2;
      ofs2 += nb;
      db->block_hdr.block_nbytes += nb;  /* jr+ */
   }
/*
  ---> REWRITE BLOCK HDR AND PROFILE DIR
*/
   if (write_block_hdr())
      goto error_found;
   if (write_profile_dir())
      goto error_found;
/*
  ---> CLOSE BLOCK
*/
   db->block_modified = 0;
   if (close_block())
      goto error_found;
   db->new_block_is_open = 0;
   fclose(db->fptmpdata);
/*
  ---> ADD BLOCK DIR ENTRY
*/
   update_block_dir_entry();
   if (add_block_dir_entry())
      goto error_found;
   db->new_block_is_open = 0;
   db->block_dir_modified = 1;
   if (temp_dir_loaded)
   {
      free(db->profile_dir);
      db->profile_dir_in_memory = 0;
      db->profile_dir_loaded = 0;
   }
   return;

   error_found:
      report_db_error("DBENDBLK");
      if (temp_dir_loaded)
      {
         db->profile_dir_in_memory = 0;
         free(db->profile_dir);
         db->profile_dir_loaded = 0;
      }
      *ierr = db->error_code * 1000 - db->error_data;
}
/*-----------------------------------------------------------------------------

   FUNCTION:  create_block_dir

      It attempts to create a new block directory file.  If successful,
      it writes the header and an empty directory, and then closes the file.

   RETURNS:  0 if successful
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int create_block_dir(void)
#else
int create_block_dir()
#endif
{
   int index;
/*
 ---> ATTEMPT TO CREATE A STREAM FILE FOR BLOCK DIRECTORY
*/
   if ((db->fpblkdir = fopen(db->block_dir_file, "w+b")) == NULL)
   {
      db->error_code = FILE_PROTECTION_ERROR;
      db->error_data = BLOCK_DIR_FILE;
      goto error_found;
   }
/*
 ---> INITIALIZE FIELDS FOR AN EMPTY DIRECTORY
*/
   strncpy(db->block_dir_hdr.db_version, DATABASE_SYSTEM_VERSION, 4); /**jr+**/
   strcpy((char *)&(db->block_dir_hdr.dataset_id),
      (char *)&(db->producer->dataset_id));
   db->block_dir_hdr.time1 = MAXULONG;
   db->block_dir_hdr.time2 = MINULONG;
   db->block_dir_hdr.lat1 = MAXLONG;
   db->block_dir_hdr.lon1 = MAXLONG;
   db->block_dir_hdr.depth1 = MAXSHORT;
   db->block_dir_hdr.lat2 = MINLONG;
   db->block_dir_hdr.lon2 = MINLONG;
   db->block_dir_hdr.depth2 = MINSHORT;
   db->block_dir_hdr.dir_type = db->producer->block_dir_type;
   db->block_dir_hdr.dir_entry_nbytes = BLOCK_DIR_ENTRY_SIZE;
   db->block_dir_hdr.dir_nentries = 0;
   db->block_dir_hdr.next_seq_number = 1;
   db->block_dir_hdr.data_mask = 0;
   db->block_dir_hdr.data_proc_mask = 0;
   db->block_dir_hdr.unused1 = 0;
   db->block_dir_hdr.unused2 = 0;
   db->block_dir_hdr.unused3 = 0;
   db->block_dir_hdr.unused4 = 0;
/*
 ---> IDENTIFY MACHINE SIGNATURE     -- JR 95/07: unused5 is now ms_code
*/
   if ((index = identify_ms((char *) MACHINE_SIGNATURE)) == UNKNOWN_HOST)
   {
      db->error_code = UNKNOWN_HOST_ERROR;
      db->error_data = BLOCK_DIR_FILE;
      goto error_found;
   }
   strncpy(db->block_dir_hdr.ms_code, MS_TABLE[index].ms_code, 4);
/*
 ---> WRITE HEADER
*/
   if (write_block_dir_hdr())
      goto error_found;
/*
 ---> CLOSE BLOCK DIR FILE
*/
   if (fclose(db->fpblkdir))
   {
      db->error_code = CLOSE_ERROR;
      db->error_data = BLOCK_DIR_FILE;
      goto error_found;
   }
   return(0);

   error_found:
      report_db_error("create_block_dir");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  create_block

      It attempts to create a new block file.  If successful, it writes
      the header and the data_list, and then closes the file.

   RETURNS:  0 if successful
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int create_block(void)
#else
int create_block()
#endif
{
   int index;
/*
  ---> SET NAME OF BLOCK FILE
*/
   sprintf( (db->block_file + db->path_nchar),
      db->block_dir_hdr.block_file_template,
      db->block_dir_entry_ptr->file_id);
/*
 ---> IF FILE DOES NOT EXIST, CREATE IT.
*/
   if (!(EXISTS(db->block_file)))
   {
      if ((db->fpblkdata = fopen(db->block_file, "w+b")) == NULL)
      {
         db->error_code = FILE_PROTECTION_ERROR;
         db->error_data = BLOCK_FILE;
         goto error_found;
      }
   }
   else if ((db->fpblkdata = open_binary_file(db->block_file,
      db->access_mode)) == NULL)
   {
      db->error_code = UNABLE_TO_OPEN;
      db->error_data = BLOCK_FILE;
      goto error_found;
   }
   db->block_is_open = 1;
/*
 ---> IDENTIFY MACHINE SIGNATURE
      Note:  95/08 JR Leave this in place for now (instead of copying
      from block_dir_hdr.ms_code) in case new blocks are added
      to an existing database where the block_dir_hdr.ms_code
      has not been properly initialized (used to be unused5).
*/
   if ((index = identify_ms((char *) MACHINE_SIGNATURE)) == UNKNOWN_HOST)
   {
      db->error_code = UNKNOWN_HOST_ERROR;
      db->error_data = BLOCK_FILE;
      goto error_found;
   }
/*
 ---> INITIALIZE FIELDS FOR AN EMPTY BLOCK
*/
   strncpy(db->block_hdr.db_version, DATABASE_SYSTEM_VERSION, 4);
   strncpy(db->block_hdr.producer_host, MS_TABLE[index].ms_name, 4);
   strcpy((char *)&(db->block_hdr.dataset_id),
      (char *)&(db->producer->dataset_id));
   strcpy((char *)&(db->block_hdr.producer_id),
      (char *)&(db->producer->producer_id));
   db->block_hdr.time1 = MAXULONG;
   db->block_hdr.time2 = MINULONG;
   db->block_hdr.lon1 = MAXLONG;
   db->block_hdr.lon2 = MINLONG;
   db->block_hdr.lat1 = MAXLONG;
   db->block_hdr.lat2 = MINLONG;
   db->block_hdr.depth1 = MAXSHORT;
   db->block_hdr.depth2 = MINSHORT;
   db->block_hdr.data_list_ofs = BLOCK_HDR_SIZE;
   db->block_hdr.data_list_nentries = db->producer->data_list_max_nentries;
   db->block_hdr.struct_def_nentries = db->producer->struct_def_max_nentries;
   db->block_hdr.dir_ofs = (db->block_hdr.data_list_ofs)
      + db->block_hdr.data_list_nentries * DATA_LIST_ENTRY_SIZE
      + db->block_hdr.struct_def_nentries * STRUCT_DEF_ENTRY_SIZE;
   db->block_hdr.block_nbytes = db->block_hdr.dir_ofs;
   db->block_hdr.dir_time_flag = db->producer->dir_time_flag;
   db->block_hdr.dir_type = db->producer->profile_dir_type;
   switch(db->block_hdr.dir_type)
   {
      case 0:
         db->block_hdr.dir_entry_nbytes = PROFILE_DIR_0_ENTRY_SIZE; break;
      case 1:
         db->block_hdr.dir_entry_nbytes = PROFILE_DIR_1_ENTRY_SIZE; break;
      case 2:
         db->block_hdr.dir_entry_nbytes = PROFILE_DIR_2_ENTRY_SIZE; break;
      case 3:
         db->block_hdr.dir_entry_nbytes = PROFILE_DIR_3_ENTRY_SIZE; break;
   }
   db->block_hdr.dir_nentries = 0;
   db->block_hdr.data_dir_nentries = db->producer->profile_var_max_nentries;
   db->block_hdr.data_mask = 0;
   db->block_hdr.data_proc_mask = 0;
   strncpy(db->block_hdr.ms_code, MS_TABLE[index].ms_code, 4);
/*
 ---> WRITE TO DISK
*/
   if (write_block_hdr())
      goto error_found;
   if (write_data_list())
      goto error_found;
   if (write_block_strdef())
      goto error_found;
/*
 ---> CLOSE BLOCK FILE
*/
   if (fclose(db->fpblkdata))
   {
      db->error_code = CLOSE_ERROR;
      db->error_data = BLOCK_FILE;
      goto error_found;
   }
   db->block_is_open = 0;
   return(0);

   error_found:
      report_db_error("create_block");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  create_block_dir_entry

      It initializes the fields of a block directory entry.

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void create_block_dir_entry(void)
#else
void create_block_dir_entry()
#endif
{
   int nb;

   db->block_dir_index = db->block_dir_hdr.dir_nentries;
   if (db->block_dir_in_memory)
   {
      nb = db->block_dir_hdr.dir_entry_nbytes;
      db->block_dir_entry_ptr = (BLOCK_DIR_ENTRY_TYPE *)
         (db->block_dir + nb * db->block_dir_index);
   }
   else db->block_dir_entry_ptr = &(db->block_dir_entry);
   strcpy((char *)&(db->block_dir_entry_ptr->producer_id),
      (char *) &(db->producer->producer_id));
   db->block_dir_entry_ptr->time1 = MAXULONG;
   db->block_dir_entry_ptr->time2 = MINULONG;
   db->block_dir_entry_ptr->lon1 = MAXLONG;
   db->block_dir_entry_ptr->lon2 = MINLONG;
   db->block_dir_entry_ptr->lat1 = MAXLONG;
   db->block_dir_entry_ptr->lat2 = MINLONG;
   db->block_dir_entry_ptr->depth1 = MAXSHORT;
   db->block_dir_entry_ptr->depth2 = MINSHORT;
   db->block_dir_entry_ptr->block_nbytes = 0;
   db->block_dir_entry_ptr->file_id = db->block_dir_hdr.next_seq_number;
   db->block_dir_entry_ptr->data_mask = 0;
   db->block_dir_entry_ptr->data_proc_mask = 0;
   db->block_dir_entry_ptr->unassigned = 0; /**jr+**/
}

/*-----------------------------------------------------------------------------

   FUNCTION:  create_profile_dir_entry

      It initializes the fields of a profile directory entry.

   PARAMETER:

      prf_time = time stamp for the profile directory entry to be created

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void create_profile_dir_entry(YMDHMS_TIME_TYPE *prf_time)
#else
void create_profile_dir_entry(prf_time)
YMDHMS_TIME_TYPE *prf_time;
#endif
{
   unsigned int nb;

   db->profile_dir_index = db->block_hdr.dir_nentries;
   if (db->profile_dir_in_memory)
   {
      nb = db->block_hdr.dir_entry_nbytes;
      db->profile_dir_entry_ptr = (PROFILE_DIR_3_ENTRY_TYPE *)
         (db->profile_dir + nb * db->profile_dir_index);
   }
   else db->profile_dir_entry_ptr = &(db->profile_dir_entry);
   db->profile_dir_entry_ptr->ofs = db->temp_data_ofs;
   if (db->block_hdr.dir_time_flag == DIR_TIME_IN_SECONDS)
      db->profile_dir_entry_ptr->time = TIMDIF(&(db->block_base_time), prf_time);
   else
      db->profile_dir_entry_ptr->time = HTIMDIF(&(db->block_base_time), prf_time);
   if ((db->block_hdr.dir_type == 1) || (db->block_hdr.dir_type == 3))
   {
      db->profile_dir_entry_ptr->lon = MAXLONG;
      db->profile_dir_entry_ptr->lat = MAXLONG;
      db->profile_dir_entry_ptr->spare_key = 0;  /**jr+**/
   }
   if ((db->block_hdr.dir_type == 2) || (db->block_hdr.dir_type == 3))
   {
      db->profile_dir_entry_ptr->depth1 = MAXSHORT;
      db->profile_dir_entry_ptr->depth2 = MINSHORT;
      db->profile_dir_entry_ptr->spare_key = 0;  /**jr+**/
   }
   nb = db->block_hdr.data_dir_nentries * DATA_DIR_ENTRY_SIZE;
   set_byte((UBYTE *)db->data_dir, '\0', nb);
   store_temp_data((char *) db->data_dir, nb);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  add_profile_aux

      It performs the actual writing of additional profile data to
      the temporary file.

   PARAMETERS:

      type = pointer to data type

      data = array/structure containing the data to be added

      nb = number of bytes to be written

      ind = index into data directory

   RETURNS:  0 if okay
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int add_profile_aux(int *type, char *data, unsigned int nb, int ind)
#else
int add_profile_aux(type, data, nb, ind)
int *type, ind;
char *data;
unsigned int nb;
#endif
{
   static ULONG BIT[] =
   {
      BIT0, BIT1, BIT2, BIT3, BIT4, BIT5, BIT6, BIT7, BIT8, BIT9,
      BIT10, BIT11, BIT12, BIT13, BIT14, BIT15, BIT16, BIT17, BIT18, BIT19,
      BIT20, BIT21, BIT22, BIT23, BIT24, BIT25, BIT26, BIT27, BIT28, BIT29,
      BIT30, BIT31
   };

   if (store_temp_data(data, nb))
      goto error_found;
   if (db->data_list[*type].access_type == PROFILE_VAR_ACCESS_CODE)
      db->data_dir[ind].nbytes = nb;
   if (*type < 32)
      db->block_hdr.data_mask = db->block_hdr.data_mask | BIT[*type];
   return(0);

   error_found:
      report_db_error("add_profile_aux");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  add_profile_data

      It adds data to a profile.

   PARAMETERS:

      type = pointer to data type to be added

      data = pointer to start of profile data to be added

      nb   = pointer to number of bytes to be added

      ierr = pointer to error code

   RETURNS:  0 if okay or if NO_NEW_PROFILE_IS_OPEN or if INVALID_TYPE_REQUEST
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int add_profile_data(int *type, char *data, unsigned int *nb, int *ierr)
#else
int add_profile_data(type, data, nb, ierr)
int *type, *ierr;
char *data;
unsigned int *nb;
#endif
{
   int ind;

   set_access_param(type, *nb, &ind, ierr);
   if (*ierr)
      return(0);
   if (add_profile_aux(type, data, *nb, ind))
      goto error_found;
   return(0);

   error_found:
      report_db_error("add_profile_data");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  dbadd_flags

      It checks the necessary database status flags prior to adding
      data to the database using DBADD and DBADD_F.

   RETURNS:  0 if okay
             error code otherwise

*/
#if PROTOTYPE_ALLOWED
int dbadd_flags(void)
#else
int dbadd_flags()
#endif
{
   if (!(db))
      return(DB_IS_NOT_OPEN);
   if (!(db->create_mode))
      return(NO_OPEN_FOR_CREATE);
   if (!(db->new_block_is_open))
      return(NO_NEW_BLOCK_IS_OPEN);
   return(0);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  set_position

      It sets the position parameter in the block/profile directory.

   PARAMETER:

      data = array containing latitude and longitude

   RETURNS:  0 if okay
             INVALID_POSITION or NO_NEW_PROFILE_IS_OPEN otherwise

*/
#if PROTOTYPE_ALLOWED
int set_position(DMSH_POSITION_TYPE *data)
#else
int set_position(data)
DMSH_POSITION_TYPE *data;
#endif
{
   DMSH_POSITION_TYPE *lat, *lon;

   lon = &data[0];
   lat = &data[1];
   if (invalid_position(lat, 1) || invalid_position(lon, 0))
      return(INVALID_POSITION);
   if ((db->block_hdr.dir_type == 0) || (db->block_hdr.dir_type == 2))
   {
      db->block_hdr.lon1 = POSHUN(lon);
      if (db->block_hdr.lon1 < 0)
         db->block_hdr.lon1 += 129600000L;
      db->block_hdr.lon2 = db->block_hdr.lon1;
      db->block_hdr.lat1 = POSHUN(lat);
      db->block_hdr.lat2 = db->block_hdr.lat1;
   }
   else if ((db->block_hdr.dir_type == 1) || (db->block_hdr.dir_type == 3))
   {
      if (!(db->new_profile_is_open))
         return(NO_NEW_PROFILE_IS_OPEN);
      db->profile_dir_entry_ptr->lon = POSHUN(lon);
      if (db->profile_dir_entry_ptr->lon < 0)
         db->profile_dir_entry_ptr->lon += 129600000L;
      db->profile_dir_entry_ptr->lat = POSHUN(lat);
   }
   return(0);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  set_depth_range

      It sets the depth range parameter in the block/profile directory.

   PARAMETER:

      data = array containing the minimum and maximum depths

   RETURNS:  0 if okay
             NO_NEW_PROFILE_IS_OPEN otherwise

*/
#if PROTOTYPE_ALLOWED
int set_depth_range(DEPTH_RANGE_TYPE *data)
#else
int set_depth_range(data)
DEPTH_RANGE_TYPE *data;
#endif
{

   if ((db->block_hdr.dir_type == 0) || (db->block_hdr.dir_type == 1))
   {
      db->block_hdr.depth1 = data->min_depth;
      db->block_hdr.depth2 = data->max_depth;
   }
   else if ((db->block_hdr.dir_type == 2) || (db->block_hdr.dir_type == 3))
   {
      if (!(db->new_profile_is_open))
         return(NO_NEW_PROFILE_IS_OPEN);
      db->profile_dir_entry_ptr->depth1 = data->min_depth;
      db->profile_dir_entry_ptr->depth2 = data->max_depth;
   }
   return(0);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  set_access_param

      Given the data type and the number of bytes to be added to a profile,
      it sets the appropriate access fields in the data directory/data list.

   PARAMETERS:

      type = pointer to data type

      nb = number of bytes to be added

      ind = pointer to data direectory index

      ierr = pointer to error code

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void set_access_param(int *type, unsigned int nb, int *ind, int *ierr)
#else
void set_access_param(type, nb, ind, ierr)
int *type, *ind, *ierr;
unsigned int nb;
#endif
{
   *ierr = 0;
   if (db->data_list[*type].access_type == PROFILE_VAR_ACCESS_CODE)
   {
      if (db->new_profile_is_open)
      {
         *ind = (int) (db->data_list[*type].access_0);
         db->data_dir[*ind].ofs = (db->temp_data_ofs)
            - db->profile_dir_entry_ptr->ofs;
      }
      else
         *ierr = NO_NEW_PROFILE_IS_OPEN;
   }
   else if (db->data_list[*type].access_type == BLOCK_VAR_ACCESS_CODE)
   {
      if (!(db->new_profile_is_open))
      {
         db->data_list[*type].access_0 = db->temp_data_ofs;
         db->data_list[*type].access_1 = nb;
      }
      else *ierr = NEW_PROFILE_IS_OPEN;
   }
   else
      *ierr = INVALID_TYPE_REQUEST;
   return;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  store_temp_data

      It writes block data in temporary data file.

   PARAMETERS:

      data = address of buffer containing data to be written

      total_nb = number of bytes to be written to temporary file

   RETURNS:  0 if okay
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int store_temp_data(char *data, unsigned int total_nb)
#else
int store_temp_data(data, total_nb)
char *data;
unsigned int total_nb;
#endif
{
   unsigned int nb;

   nb = total_nb;
/*
 ---> WRITE DATA
*/
   while (nb > MAXINT)
   {
      if (fwrite(data, 1, MAXINT, db->fptmpdata) != MAXINT)
         goto error_found;
      nb -= MAXINT;
   }
   if (fwrite(data, 1, nb, db->fptmpdata) != nb)
   {
      db->error_code = WRITE_ERROR;
      db->error_data = TEMP_DATA_FILE;
      goto error_found;
   }
   db->temp_data_ofs += total_nb;
   return(0);

   error_found:
      report_db_error("store_temp_data");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  flush_block_dir_file

      It flushes the block directory file buffer by closing the file.
      It then reopens the file.

   RETURNS:  0 if successful
             CLOSE_ERROR or UNABLE_TO_OPEN otherwise

*/
#if PROTOTYPE_ALLOWED
int flush_block_dir_file(void)
#else
int flush_block_dir_file()
#endif
{
   if (fclose(db->fpblkdir))
   {
      db->error_code = CLOSE_ERROR;
      db->error_data = BLOCK_DIR_FILE;
      goto error_found;
   }
   if ((db->fpblkdir = open_binary_file(db->block_dir_file,
      db->access_mode)) == NULL)
   {
      db->error_code = UNABLE_TO_OPEN;
      db->error_data = BLOCK_DIR_FILE;
      goto error_found;
   }
   return(0);

   error_found:
      report_db_error("flush_block_dir_file");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  write_data_list

      It rewrites the block data access list.  The block must be open.

   RETURNS:  0 if successful
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int write_data_list(void)
#else
int write_data_list()
#endif
{
   unsigned int nb;
   long ofs;

   ofs = db->block_hdr.data_list_ofs;
   nb = db->block_hdr.data_list_nentries * DATA_LIST_ENTRY_SIZE;
   if ((db->error_code = write_data(db->fpblkdata,
      (char *) db->data_list, ofs, nb)) != 0)
   {
      db->error_data = DATA_LIST;
      goto error_found;
   }
   return(0);

   error_found:
      report_db_error("write_data_list");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  write_block_strdef

      It writes an array of STRUCT_DEF_ENTRY_TYPE structures to disk.

   RETURNS:  0 if successful
             WRITE_ERROR otherwise

*/
#if PROTOTYPE_ALLOWED
int write_block_strdef(void)
#else
int write_block_strdef()
#endif
{
   unsigned int nb;
   long ofs;

   nb = db->block_hdr.struct_def_nentries * STRUCT_DEF_ENTRY_SIZE;
   ofs = db->block_hdr.data_list_ofs + db->block_hdr.data_list_nentries *
      DATA_LIST_ENTRY_SIZE;
   if ((db->error_code = write_data(db->fpblkdata, (char *) db->block_strdef,
      ofs, nb)) != 0)
   {
      db->error_data = STRUCTURE_DEF;
      goto error_found;
   }
   return(0);

   error_found:
      report_db_error("write_block_strdef");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  add_block_dir_entry

      It adds the current entry to the block directory.

   NOTE:
   
      - Block directory must already be open.
      - Header must be in db->block_dir_hdr.
      - Entry must be in *db->block_dir_entry_ptr.
      - On return, db->block_dir_index points to the new entry.
      - Entry is added to the end so the directory must be re-sorted.

   RETURNS:  0 if okay
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int add_block_dir_entry(void)
#else
int add_block_dir_entry()
#endif
{
   unsigned int nb;
   long ofs;

/*
 ---> WRITE ENTRY
*/
   nb = db->block_dir_hdr.dir_entry_nbytes;
   ofs = BLOCK_DIR_HDR_SIZE + nb * db->block_dir_hdr.dir_nentries;
   if ((db->error_code = write_data(db->fpblkdir,
      (char *) db->block_dir_entry_ptr, ofs, nb)) != 0)
   {
      db->error_data = BLOCK_DIR;
      goto error_found;
   }
/*
 ---> REWRITE DIRECTORY HEADER
*/
   db->block_dir_hdr.dir_nentries++;
   db->block_dir_hdr.next_seq_number++;
   if (write_block_dir_hdr())
      goto error_found;
   if (flush_block_dir_file())
      goto error_found;
   return(0);

   error_found:
      report_db_error("add_block_dir_entry");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  load_producer_definition

      It loads the database definition from a given producer definition file.

   RETURNS:

      0 if okay
      db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int load_producer_definition(void)
#else
int load_producer_definition()
#endif
{
   char keyword[80], test[3];
#if (COMPILER == VAX_VMS_C)
   FILE *fptemp;
#endif
/*
 ---> OPEN PRODUCER DEFINITION FILE
*/
   if ((db->fpprddef = fopen(db->producer_def_file, "rb")) == NULL)
   {
      db->error_code = UNABLE_TO_OPEN;
      db->error_data = PRODUCER_DEF_FILE;
      goto error_found;
   }
#if (COMPILER == VAX_VMS_C)
   if ((fptemp = fopen("TEMP.DEF", "w")) == NULL)
   {
      db->error_code = UNABLE_TO_OPEN;
      db->error_data = TEMP_DATA_FILE;
      goto error_found;
   }
   strip_comments(db->fpprddef, fptemp);
   fclose(db->fpprddef);
   fclose(fptemp);
   if ((db->fpprddef = fopen("TEMP.DEF", "r")) == NULL)
   {
      db->error_code = UNABLE_TO_OPEN;
      db->error_data = TEMP_DATA_FILE;
      goto error_found;
   }
#endif
   /* set default directory time unit: */
   db->producer->dir_time_flag = DIR_TIME_IN_SECONDS;
/*
 ---> SCAN PRODUCER DEFINITION FILE FOR REQUIRED DATABASE PARAMETERS
*/
   if (!getword_nc(db->fpprddef, keyword, 80))
      goto read_error;
   if (strcmp("DATASET_ID", keyword))
      goto read_error;
   get2eol_nc(db->fpprddef, (char *) &(db->producer->dataset_id),
      sizeof(DATASET_ID_TYPE));

   if (!getword_nc(db->fpprddef, keyword, 80))
      goto read_error;
   if (strcmp("PRODUCER_ID", keyword))
      goto read_error;
   get2eol_nc(db->fpprddef, (char *) &(db->producer->producer_id),
      sizeof(PRODUCER_ID_TYPE));

   if (getwords_nc(db->fpprddef, keyword, 80, 2) != 2)
      goto read_error;
   if (sscanf(keyword, " BLOCK_DIR_TYPE %ld %2s",
      &(db->producer->block_dir_type), test) != 1)
      goto read_error;
   if (getwords_nc(db->fpprddef, keyword, 80, 2) != 2)
      goto read_error;
   if (sscanf(keyword, " PROFILE_DIR_TYPE %ld %2s",
      &(db->producer->profile_dir_type), test) != 1)
      goto read_error;

/*
 ---> ALLOCATE MEMORY FOR STRUCTURE DEFINITION
*/
   if ((db->block_strdef = (STRUCT_DEF_HDR_TYPE *)
      calloc(1, STRUCT_DEF_ENTRY_SIZE)) == NULL)
   {
      db->error_code = INSUFFICIENT_MEMORY;
      goto def_error;
   }

/*
 ---> SCAN REMAINDER OF PRODUCER DEFINITION FILE FOR OPTIONAL PARAMETERS
*/
   while (getword_nc(db->fpprddef, keyword, 80))
   {
      if (!strcmp(keyword, "UNUSED"))
         get2eol_nc(db->fpprddef, keyword, 79); /* ignore rest-of-line */
      else if (!strcmp(keyword, "PROFILE_VAR"))
      {
         if ((db->error_code = define_data(PROFILE_VAR_ACCESS_CODE)) != 0)
            goto def_error;
      }
      else if (!strcmp(keyword, "BLOCK_VAR"))
      {
         if ((db->error_code = define_data(BLOCK_VAR_ACCESS_CODE)) != 0)
            goto def_error;
      }
      else if (!strcmp(keyword, "DEFINE_STRUCT"))
      {
         if ((db->error_code = define_struct(db->fpprddef, &(db->block_strdef),
            &(db->producer->struct_def_max_nentries))) != 0)
            goto def_error;
      }
      else if (!strcmp(keyword, "DIR_TIME_IN_HUNDREDTHS"))
      {
         db->producer->dir_time_flag = DIR_TIME_IN_HUNDREDTHS;
      }
      else if (!strcmp(keyword, "DIR_TIME_IN_SECONDS"))
      {
         /* default; do nothing. */
      }
      else
      {
         printf("\n ERROR: Unrecognized parameter ");
         goto read_error;
      }
   }

   fclose(db->fpprddef);
#if (COMPILER == VAX_VMS_C)
   unlink("TEMP.DEF");
#endif
   db->producer->data_list_max_nentries++;   /* largest id found + 1 */

   if ((db->block_strdef = (STRUCT_DEF_HDR_TYPE *)
      realloc((char *) db->block_strdef,
      (1 + db->producer->struct_def_max_nentries) * STRUCT_DEF_ENTRY_SIZE))
      == NULL)
   {
      db->error_code = INSUFFICIENT_MEMORY;
      goto def_error;
   }
   set_byte((UBYTE *) (db->block_strdef + db->producer->struct_def_max_nentries),
      0, STRUCT_DEF_ENTRY_SIZE);
   db->producer->struct_def_max_nentries++;  /* add entry for NULL terminator */
   db->producer_loaded = 1;
   return(0);

   read_error:
      db->error_code = READ_ERROR;
      printf("\n SCANNING: %s \n", keyword);

   def_error:
      printf("\n ERROR: In producer definition file");
      fclose(db->fpprddef);
      if (db->error_code == INSUFFICIENT_MEMORY)
         db->error_data = STRUCTURE_DEF;
      else
         db->error_data = PRODUCER_DEF;

   error_found:
      report_db_error("load_producer_definition");
      return(db->error_code);
}

extern NAME_LIST_ENTRY_TYPE VALUE_CODE[];

/*------------------------------------------------------------------------------

   FUNCTION:  define_data

      Processes BLOCK_VAR and PROFILE_VAR statements in the producer definition
      file by setting the fields of the appropriate data list entry to the
      values indicated in the producer definition file.

   PARAMETER:

      code = BLOCK_VAR_ACCESS_CODE or PROFILE_VAR_ACCESS_CODE

   RETURNS:

      0 if okay
      READ_ERROR or INVALID_VALUE_TYPE error

*/
#if PROTOTYPE_ALLOWED
int define_data(int code)
#else
int define_data(code)
int code;
#endif
{
   int id, temp;
   char type[12], buff[80], test[3];

   if (!getword_nc(db->fpprddef, buff, 80))
      goto read_error;
   if (sscanf(buff, " %d %2s", &id, test) != 1)
      goto read_error;
   if (getwords_nc(db->fpprddef, buff, 80, 5) != 5)
      goto read_error;
   if (sscanf(buff, " %11s %19s %f %f %11s %2s",
      type, db->data_list[id].name, &(db->data_list[id].offset),
      &(db->data_list[id].scale), db->data_list[id].units, test) != 5)
      goto read_error;
   if ((temp = get_code(VALUE_CODE, type)) == BADINT)
   {
      printf("\n SCANNING: %s\n", buff);
      return(INVALID_VALUE_TYPE);
   }
   db->data_list[id].value_type = temp;
   db->data_list[id].access_type = code;
   if (code == PROFILE_VAR_ACCESS_CODE)
   {
      db->data_list[id].access_0 = db->data_list[id].access_1 =
         db->producer->profile_var_max_nentries;
      (db->producer->profile_var_max_nentries)++;
   }
   db->data_list[id].unused = 0;
   if (id > (int)db->producer->data_list_max_nentries)
      db->producer->data_list_max_nentries = id;
   return(0);

   read_error:
      printf("\n SCANNING: %s \n", buff);
      return(READ_ERROR);
}

