/*
 * dbreadnext.c
 * File revision 0
 * Read the next record while iterating through a database.
 * (c) 2000 Jacob Lundberg, jacob@chaos2.org
 */


/*
 * 2000.10.24	Initial implementation
 */


#include <stdio.h>
#include <errno.h>
#include "db.h"


int db_read_next(DB_FILE *db_fp, char *key, void *data) {
/*
 * db_read_next()
 * Read the next record iterating through a database.
 * Don't forget to make sure data is large enough...
 */

   int counter;
   db_header header;
   int data_size = 0;
   off_t data_pos = 0;
   db_data_header data_header;
   int key_size = db_key_size(db_fp);

   /* Make sure that dbbeginscan() has been called already. */
   if(db_fp->fpos < DB_HEADER_SIZE) {
      errno = ENOTSUP;
      return(-1);
   }

   /* Take a read lock on the database header. */
   if(db_lock_read(db_fp->file, 0)) return(-1);

   /* Read the header. */
   if(fseek(db_fp->file, 0, SEEK_SET) || !fread(&header, DB_HEADER_SIZE, 1, db_fp->file))
      return(-1);

   /* Drop the read lock from the header. */
   db_unlock_read(db_fp->file, 0);

   if(db_fp->fpos == header.trans_table + DB_LOCK_SIZE + sizeof(off_t) * db_hash_size(db_fp)) {
      /* We're out of records to read. */
      errno = ELNRNG;
      return(1);
   } else if(db_fp->fpos < header.trans_table + DB_LOCK_SIZE ||
         db_fp->fpos > header.trans_table + DB_LOCK_SIZE + sizeof(off_t) * db_hash_size(db_fp)) {
      /*
       * If the hash has been moved (very rare) then
       * the user will have to restart the iteration.
       */
      db_fp->fpos = 0;
      db_fp->ldepth = 0;
      errno = ECANCELED;
      return(-1);
   }

   /* Take a read lock on the lookup table. */
   if(db_lock_read(db_fp->file, header.trans_table)) return(-1);

   /*
    * Scan the table for an entry with data.  Now data_pos
    * changes from the lookup offset to the expected data offset.
    */
   if(fseek(db_fp->file, data_pos, SEEK_SET) || !fread(&data_pos, sizeof(int), 1, db_fp->file))
      return(-1);
   while(data_pos < DB_HEADER_SIZE &&
         db_fp->fpos < header.trans_table + DB_LOCK_SIZE + sizeof(int) * db_hash_size(db_fp)) {
      /* Next hash location. */
      db_fp->fpos += sizeof(int);
      if(fseek(db_fp->file, data_pos, SEEK_SET) || !fread(&data_pos, sizeof(int), 1, db_fp->file))
         return(-1);
   }

   /* Drop the lock from the lookup table. */
   db_unlock_read(db_fp->file, header.trans_table);

   /* Make sure we haven't scanned off the end of the hash table. */
   if(db_fp->fpos >= header.trans_table + DB_LOCK_SIZE + sizeof(int) * db_hash_size(db_fp)) {
      /* We're out of records to read. */
      errno = ELNRNG;
      return(1);
   }

   /* Scan through the potential slink list of data for the next record. */
   counter = db_fp->ldepth;
   do {
      /* Take a read lock on the selected data. */
      if(db_lock_read(db_fp->file, data_pos)) return(-1);

      /* Read the header of the data. */
      if(fseek(db_fp->file, data_pos, SEEK_SET) || !fread(&data_header, DB_DATA_HEADER_SIZE, 1, db_fp->file))
         return(-1);

      if(--counter <= 0) {
         /* Read the key of the data. */
         if(fseek(db_fp->file, data_pos + DB_DATA_HEADER_SIZE, SEEK_SET) ||
               !fread(key, key_size, 1, db_fp->file))
            return(-1);
         /* Read the data. */
         if(fseek(db_fp->file, data_pos + DB_DATA_HEADER_SIZE + key_size, SEEK_SET) ||
               !fread(data, data_header.size, 1, db_fp->file))
            return(-1);
         /* Adjust the read for the next record. */
         db_unlock_read(db_fp->file, data_pos);
         if(!data_header.next) {
            db_fp->fpos += sizeof(int);
            db_fp->ldepth = 1;
         } else
            ++(db_fp->ldepth);
         /* Success!  Drop out and be merry... */
         return(0);
      }

      /* Drop the read lock from the selected data. */
      db_unlock_read(db_fp->file, data_pos);

      /* Move on to the next record. */
      data_pos = data_header.next;

   } while(data_pos);

   /* We are recursive when scan off the end of a chain. */
   db_fp->ldepth = 1;
   db_fp->fpos += sizeof(int);
   return(db_read_next(db_fp, key, data));

}

